Browse Source

fix: revert SameParty cookie attribute removal (#40526)

Keeley Hammond 1 year ago
parent
commit
33b39fb63d

+ 1 - 0
patches/chromium/.patches

@@ -137,3 +137,4 @@ fix_handle_no_top_level_aura_window_in_webcontentsimpl.patch
 feat_allow_passing_of_objecttemplate_to_objecttemplatebuilder.patch
 chore_remove_check_is_test_on_script_injection_tracker.patch
 crash_gpu_process_and_clear_shader_cache_when_skia_reports.patch
+revert_same_party_cookie_attribute_removal.patch

+ 5410 - 0
patches/chromium/revert_same_party_cookie_attribute_removal.patch

@@ -0,0 +1,5410 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: VerteDinde <[email protected]>
+Date: Mon, 13 Nov 2023 21:47:22 -0800
+Subject: fix: revert sameparty cookie attribute removal
+
+The SameParty cookie attribute, which is used for same-party, cross-site
+contexts with First Party Sets, has been removed upstream by Chrome, but is
+depended on by some Electron consumers. This patch is meant to restore the
+removed SameParty cookie origin trail functionality and supported methods, while
+Chrome completes other API options to use in place of SameParty cookies.
+
+This patch can be removed when Storage Access API cookie support or
+equivalent support is completed upstream.
+
+diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
+index 9ab4bb62b9f7e2e74e5624ba280aec08a680df2f..dcb0de28c7d649f017246ef5d6840ef308c69c28 100644
+--- a/chrome/browser/about_flags.cc
++++ b/chrome/browser/about_flags.cc
+@@ -9001,6 +9001,10 @@ const FeatureEntry kFeatureEntries[] = {
+      flag_descriptions::kChromeRefresh2023TopChromeFontDescription, kOsDesktop,
+      FEATURE_VALUE_TYPE(features::kChromeRefresh2023TopChromeFont)},
+ 
++    {"enable-first-party-sets", flag_descriptions::kEnableFirstPartySetsName,
++     flag_descriptions::kEnableFirstPartySetsDescription, kOsAll,
++     FEATURE_VALUE_TYPE(features::kFirstPartySets)},
++
+ #if BUILDFLAG(IS_ANDROID)
+     {"autofill-enable-offers-in-clank-keyboard-accessory",
+      flag_descriptions::kAutofillEnableOffersInClankKeyboardAccessoryName,
+diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
+index eb7b5332f5e4c70e14b2816dbb662d35e7016e05..87e26b19c4ed16132813899b5c0cb282d1cf74fb 100644
+--- a/chrome/browser/chrome_content_browser_client.cc
++++ b/chrome/browser/chrome_content_browser_client.cc
+@@ -7897,8 +7897,7 @@ bool ChromeContentBrowserClient::ShouldDisableOriginAgentClusterDefault(
+ 
+ bool ChromeContentBrowserClient::WillProvidePublicFirstPartySets() {
+ #if BUILDFLAG(ENABLE_COMPONENT_UPDATER)
+-  return !is_minimal_mode_ &&
+-         !base::CommandLine::ForCurrentProcess()->HasSwitch(
++  return !base::CommandLine::ForCurrentProcess()->HasSwitch(
+              switches::kDisableComponentUpdate) &&
+          base::FeatureList::IsEnabled(features::kFirstPartySets);
+ #else
+@@ -8195,10 +8194,6 @@ bool ChromeContentBrowserClient::
+   return true;
+ }
+ 
+-void ChromeContentBrowserClient::SetIsMinimalMode(bool minimal) {
+-  is_minimal_mode_ = minimal;
+-}
+-
+ #if !BUILDFLAG(IS_ANDROID)
+ void ChromeContentBrowserClient::BindVideoEffectsManager(
+     const std::string& device_id,
+diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
+index d3451e33c645abebd1a38c633e16543a82a29a58..fae1e57305b861abf97bad15123801be9ee71bc2 100644
+--- a/chrome/browser/chrome_content_browser_client.h
++++ b/chrome/browser/chrome_content_browser_client.h
+@@ -952,8 +952,6 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient {
+   bool ShouldAllowBackForwardCacheForCacheControlNoStorePage(
+       content::BrowserContext* browser_context) override;
+ 
+-  void SetIsMinimalMode(bool minimal) override;
+-
+ #if !BUILDFLAG(IS_ANDROID)
+   void BindVideoEffectsManager(
+       const std::string& device_id,
+@@ -1116,10 +1114,6 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient {
+   std::string GetChildProcessSuffix(int child_flags) override;
+ #endif  // BUILDFLAG(IS_MAC)
+ 
+-  // Tracks whether the browser was started in "minimal" mode (as opposed to
+-  // full browser mode), where most subsystems are not initialized.
+-  bool is_minimal_mode_ = false;
+-
+   base::WeakPtrFactory<ChromeContentBrowserClient> weak_factory_{this};
+ };
+ 
+diff --git a/chrome/browser/component_updater/first_party_sets_component_installer.cc b/chrome/browser/component_updater/first_party_sets_component_installer.cc
+index af69cae172317e657ed069b4fb65162919d9f2d3..b0d202ff7047cc57644e675c46f48d4f5c9465c0 100644
+--- a/chrome/browser/component_updater/first_party_sets_component_installer.cc
++++ b/chrome/browser/component_updater/first_party_sets_component_installer.cc
+@@ -42,7 +42,7 @@ constexpr uint8_t kFirstPartySetsPublicKeySHA256[32] = {
+     0xff, 0x1c, 0x65, 0x66, 0x14, 0xa8, 0x46, 0x37, 0xe6, 0xeb, 0x80,
+     0x8b, 0x8f, 0xb0, 0xb6, 0x18, 0xa7, 0xcd, 0x3d, 0xbb, 0xfb};
+ 
+-constexpr char kFirstPartySetsManifestName[] = "Related Website Sets";
++constexpr char kFirstPartySetsManifestName[] = "First-Party Sets";
+ 
+ constexpr base::FilePath::CharType kFirstPartySetsRelativeInstallDir[] =
+     FILE_PATH_LITERAL("FirstPartySetsPreloaded");
+@@ -155,8 +155,8 @@ void FirstPartySetsComponentInstallerPolicy::ComponentReady(
+   if (install_dir.empty() || GetConfigPathInstance().has_value())
+     return;
+ 
+-  VLOG(1) << "Related Website Sets Component ready, version "
+-          << version.GetString() << " in " << install_dir.value();
++  VLOG(1) << "First-Party Sets Component ready, version " << version.GetString()
++          << " in " << install_dir.value();
+ 
+   GetConfigPathInstance() =
+       std::make_pair(GetInstalledPath(install_dir), version);
+@@ -200,12 +200,12 @@ void FirstPartySetsComponentInstallerPolicy::ResetForTesting() {
+ }
+ 
+ void RegisterFirstPartySetsComponent(ComponentUpdateService* cus) {
+-  VLOG(1) << "Registering Related Website Sets component.";
++  VLOG(1) << "Registering First-Party Sets component.";
+ 
+   auto policy = std::make_unique<FirstPartySetsComponentInstallerPolicy>(
+       /*on_sets_ready=*/base::BindOnce([](base::Version version,
+                                           base::File sets_file) {
+-        VLOG(1) << "Received Related Website Sets";
++        VLOG(1) << "Received First-Party Sets";
+         content::FirstPartySetsHandler::GetInstance()->SetPublicFirstPartySets(
+             version, std::move(sets_file));
+       }));
+diff --git a/chrome/browser/first_party_sets/first_party_sets_policy_service.cc b/chrome/browser/first_party_sets/first_party_sets_policy_service.cc
+index 869638cc9700b7253d574bf7075bd48d7539082a..12589a819c216213ea237f2ff397569420e2cbec 100644
+--- a/chrome/browser/first_party_sets/first_party_sets_policy_service.cc
++++ b/chrome/browser/first_party_sets/first_party_sets_policy_service.cc
+@@ -131,6 +131,7 @@ void FirstPartySetsPolicyService::Init() {
+ void FirstPartySetsPolicyService::ComputeFirstPartySetMetadata(
+     const net::SchemefulSite& site,
+     const net::SchemefulSite* top_frame_site,
++    const std::set<net::SchemefulSite>& party_context,
+     base::OnceCallback<void(net::FirstPartySetMetadata)> callback) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+   if (!is_enabled()) {
+@@ -142,17 +143,18 @@ void FirstPartySetsPolicyService::ComputeFirstPartySetMetadata(
+     on_ready_callbacks_.push_back(base::BindOnce(
+         &FirstPartySetsPolicyService::ComputeFirstPartySetMetadataInternal,
+         weak_factory_.GetWeakPtr(), site, base::OptionalFromPtr(top_frame_site),
+-        std::move(callback)));
++        party_context, std::move(callback)));
+     return;
+   }
+ 
+   content::FirstPartySetsHandler::GetInstance()->ComputeFirstPartySetMetadata(
+-      site, top_frame_site, *config_, std::move(callback));
++      site, top_frame_site, party_context, *config_, std::move(callback));
+ }
+ 
+ void FirstPartySetsPolicyService::ComputeFirstPartySetMetadataInternal(
+     const net::SchemefulSite& site,
+     const absl::optional<net::SchemefulSite>& top_frame_site,
++    const std::set<net::SchemefulSite>& party_context,
+     base::OnceCallback<void(net::FirstPartySetMetadata)> callback) const {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+   CHECK(config_.has_value());
+@@ -163,7 +165,8 @@ void FirstPartySetsPolicyService::ComputeFirstPartySetMetadataInternal(
+   }
+ 
+   content::FirstPartySetsHandler::GetInstance()->ComputeFirstPartySetMetadata(
+-      site, base::OptionalToPtr(top_frame_site), *config_, std::move(callback));
++      site, base::OptionalToPtr(top_frame_site), party_context, *config_,
++      std::move(callback));
+ }
+ 
+ void FirstPartySetsPolicyService::AddRemoteAccessDelegate(
+diff --git a/chrome/browser/first_party_sets/first_party_sets_policy_service.h b/chrome/browser/first_party_sets/first_party_sets_policy_service.h
+index 9bc786819114dc1f12e590afa3323f067c785fad..4a1915904119bd2b0a7027ffd5046404123a0311 100644
+--- a/chrome/browser/first_party_sets/first_party_sets_policy_service.h
++++ b/chrome/browser/first_party_sets/first_party_sets_policy_service.h
+@@ -50,6 +50,7 @@ class FirstPartySetsPolicyService : public KeyedService {
+   void ComputeFirstPartySetMetadata(
+       const net::SchemefulSite& site,
+       const net::SchemefulSite* top_frame_site,
++      const std::set<net::SchemefulSite>& party_context,
+       base::OnceCallback<void(net::FirstPartySetMetadata)> callback);
+ 
+   // Stores `access_delegate` in a RemoteSet for later IPC calls on it when this
+@@ -168,6 +169,7 @@ class FirstPartySetsPolicyService : public KeyedService {
+   void ComputeFirstPartySetMetadataInternal(
+       const net::SchemefulSite& site,
+       const absl::optional<net::SchemefulSite>& top_frame_site,
++      const std::set<net::SchemefulSite>& party_context,
+       base::OnceCallback<void(net::FirstPartySetMetadata)> callback) const;
+ 
+   // Clears the content settings associated with `profile` that were
+diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
+index 895098c0bd46209a030a4be5f15509f6081859b9..f603f9a34aea1229f568aea1ebd0a1a9675fb64b 100644
+--- a/chrome/browser/flag-metadata.json
++++ b/chrome/browser/flag-metadata.json
+@@ -2672,6 +2672,11 @@
+     "owners": [ "[email protected]", "[email protected]" ],
+     "expiry_milestone": 130
+   },
++  {
++    "name": "enable-first-party-sets",
++    "owners": [ "[email protected]" ],
++    "expiry_milestone": 120
++  },
+   {
+     "name": "enable-follow-IPH-exp-params",
+     "owners": [ "[email protected]", "[email protected]" ],
+diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
+index 0d17970001d18960ee11fba6b3a5cd0ab275994b..6cdc7d1d1e1d5e9d1577172152869d673935b9a3 100644
+--- a/chrome/browser/flag_descriptions.cc
++++ b/chrome/browser/flag_descriptions.cc
+@@ -386,6 +386,11 @@ const char kUseDnsHttpsSvcbAlpnDescription[] =
+     "When enabled, Chrome may try QUIC on the first connection using the ALPN"
+     " information in the DNS HTTPS record.";
+ 
++const char kEnableFirstPartySetsName[] = "Enable First-Party Sets";
++const char kEnableFirstPartySetsDescription[] =
++    "When enabled, Chrome will enable First-Party Sets and the Storage Access "
++    "API.";
++
+ const char kSHA1ServerSignatureName[] = "Allow SHA-1 server signatures in TLS.";
+ const char kSHA1ServerSignatureDescription[] =
+     "When enabled, Chrome will allow the use of SHA-1 in signatures from the "
+diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
+index 64e4e09d02a1dbb7bf017cb07b5a065a39f449d7..499ce1f77019e775120b34e00e01f95c5609001c 100644
+--- a/chrome/browser/flag_descriptions.h
++++ b/chrome/browser/flag_descriptions.h
+@@ -506,6 +506,9 @@ extern const char kUseDnsHttpsSvcbAlpnDescription[];
+ extern const char kEditContextName[];
+ extern const char kEditContextDescription[];
+ 
++extern const char kEnableFirstPartySetsName[];
++extern const char kEnableFirstPartySetsDescription[];
++
+ extern const char kSHA1ServerSignatureName[];
+ extern const char kSHA1ServerSignatureDescription[];
+ 
+diff --git a/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc b/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc
+index ce5b56d772a81eb8cbad354bf53c8b53c459239f..be3f50d95024d1ef84857d047f0b1208710e72da 100644
+--- a/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc
++++ b/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc
+@@ -298,7 +298,8 @@ void StorageAccessGrantPermissionContext::DecidePermission(
+   first_party_sets::FirstPartySetsPolicyServiceFactory::GetForBrowserContext(
+       browser_context())
+       ->ComputeFirstPartySetMetadata(
+-          requesting_site, &embedding_site,
++          net::SchemefulSite(requesting_origin), &embedding_site,
++          /*party_context=*/{},
+           base::BindOnce(&StorageAccessGrantPermissionContext::
+                              CheckForAutoGrantOrAutoDenial,
+                          weak_factory_.GetWeakPtr(), std::move(request_data),
+diff --git a/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context.cc b/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context.cc
+index e004114e17b2b8f6ca80c6e1e8711c6d1b15264b..7f157bd3fb974d711ba22fe211c9b2d33a4720e0 100644
+--- a/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context.cc
++++ b/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context.cc
+@@ -94,7 +94,8 @@ void TopLevelStorageAccessPermissionContext::DecidePermission(
+   first_party_sets::FirstPartySetsPolicyServiceFactory::GetForBrowserContext(
+       browser_context())
+       ->ComputeFirstPartySetMetadata(
+-          requesting_site, &embedding_site,
++          net::SchemefulSite(requesting_origin), &embedding_site,
++          /*party_context=*/{},
+           base::BindOnce(&TopLevelStorageAccessPermissionContext::
+                              CheckForAutoGrantOrAutoDenial,
+                          weak_factory_.GetWeakPtr(), std::move(request_data),
+diff --git a/content/app/content_main_runner_impl.cc b/content/app/content_main_runner_impl.cc
+index 5b6c92952de30bb9a80b9595ab57344323484eea..caa5767ca6d042022c562006629d19dc8c3fdaac 100644
+--- a/content/app/content_main_runner_impl.cc
++++ b/content/app/content_main_runner_impl.cc
+@@ -60,8 +60,6 @@
+ #include "content/browser/browser_main.h"
+ #include "content/browser/browser_process_io_thread.h"
+ #include "content/browser/browser_thread_impl.h"
+-#include "content/browser/first_party_sets/first_party_sets_handler_impl.h"
+-#include "content/browser/first_party_sets/local_set_declaration.h"
+ #include "content/browser/gpu/gpu_main_thread_factory.h"
+ #include "content/browser/renderer_host/render_process_host_impl.h"
+ #include "content/browser/scheduler/browser_task_executor.h"
+@@ -1256,14 +1254,8 @@ int ContentMainRunnerImpl::RunBrowser(MainFunctionParams main_params,
+     AndroidBatteryMetrics::CreateInstance();
+ #endif
+ 
+-    GetContentClient()->browser()->SetIsMinimalMode(start_minimal_browser);
+-    if (start_minimal_browser) {
++    if (start_minimal_browser)
+       ForceInProcessNetworkService();
+-      // Minimal browser mode doesn't initialize First-Party Sets the "usual"
+-      // way, so we do it manually.
+-      content::FirstPartySetsHandlerImpl::GetInstance()->Init(
+-          base::FilePath(), LocalSetDeclaration());
+-    }
+ 
+     discardable_shared_memory_manager_ =
+         std::make_unique<discardable_memory::DiscardableSharedMemoryManager>();
+diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
+index 0757d84fc6da0be30d2497dae36b0170a33808f7..713190fa1b037df00c2f5e9761572873ea1a3538 100644
+--- a/content/browser/BUILD.gn
++++ b/content/browser/BUILD.gn
+@@ -999,9 +999,8 @@ source_set("browser") {
+     "first_party_sets/first_party_set_parser.h",
+     "first_party_sets/first_party_sets_handler_database_helper.cc",
+     "first_party_sets/first_party_sets_handler_database_helper.h",
++    "first_party_sets/first_party_sets_handler_impl.cc",
+     "first_party_sets/first_party_sets_handler_impl.h",
+-    "first_party_sets/first_party_sets_handler_impl_instance.cc",
+-    "first_party_sets/first_party_sets_handler_impl_instance.h",
+     "first_party_sets/first_party_sets_loader.cc",
+     "first_party_sets/first_party_sets_loader.h",
+     "first_party_sets/first_party_sets_site_data_remover.cc",
+diff --git a/content/browser/cookie_store/cookie_change_subscription.cc b/content/browser/cookie_store/cookie_change_subscription.cc
+index dbe3c5b8a6c83c5e8d26b109f24e77b4ab2e604e..fd51ab0f749b63fda2a7848594bb616fe6a48730 100644
+--- a/content/browser/cookie_store/cookie_change_subscription.cc
++++ b/content/browser/cookie_store/cookie_change_subscription.cc
+@@ -9,8 +9,10 @@
+ #include "content/browser/cookie_store/cookie_change_subscriptions.pb.h"
+ #include "content/public/browser/content_browser_client.h"
+ #include "content/public/common/content_client.h"
++#include "net/base/features.h"
+ #include "net/cookies/cookie_constants.h"
+ #include "net/cookies/cookie_util.h"
++#include "net/first_party_sets/same_party_context.h"
+ #include "services/network/public/cpp/is_potentially_trustworthy.h"
+ 
+ namespace content {
+@@ -169,17 +171,26 @@ bool CookieChangeSubscription::ShouldObserveChangeTo(
+       break;
+   }
+ 
+-  // We assume that this is a same-site context.
++  // We assume that this is a same-site, same-party context.
+   net::CookieOptions net_options;
+   net_options.set_same_site_cookie_context(
+       net::CookieOptions::SameSiteCookieContext::MakeInclusive());
++  net_options.set_same_party_context(net::SamePartyContext::MakeInclusive());
++  // It doesn't matter which we choose here, since both SameParty and SameSite
++  // semantics should allow this access. But we make a choice to be explicit.
++  net_options.set_is_in_nontrivial_first_party_set(true);
+ 
+   return cookie
+-      .IncludeForRequestURL(url_, net_options,
+-                            net::CookieAccessParams{
+-                                access_semantics,
+-                                network::IsUrlPotentiallyTrustworthy(url_),
+-                            })
++      .IncludeForRequestURL(
++          url_, net_options,
++          net::CookieAccessParams{
++              access_semantics,
++              network::IsUrlPotentiallyTrustworthy(url_),
++              net::cookie_util::GetSamePartyStatus(
++                  cookie, net_options,
++                  base::FeatureList::IsEnabled(
++                      net::features::kSamePartyAttributeEnabled)),
++          })
+       .status.IsInclude();
+ }
+ 
+diff --git a/content/browser/first_party_sets/first_party_sets_handler_impl_instance.cc b/content/browser/first_party_sets/first_party_sets_handler_impl.cc
+similarity index 75%
+rename from content/browser/first_party_sets/first_party_sets_handler_impl_instance.cc
+rename to content/browser/first_party_sets/first_party_sets_handler_impl.cc
+index 0855f5f8d6d092de96e6b1dc7c2e49402351ea5b..caae433e3d14eae35c73873c6a1fcab74d3dcb2e 100644
+--- a/content/browser/first_party_sets/first_party_sets_handler_impl_instance.cc
++++ b/content/browser/first_party_sets/first_party_sets_handler_impl.cc
+@@ -1,8 +1,8 @@
+-// Copyright 2023 The Chromium Authors
++// Copyright 2022 The Chromium Authors
+ // Use of this source code is governed by a BSD-style license that can be
+ // found in the LICENSE file.
+ 
+-#include "content/browser/first_party_sets/first_party_sets_handler_impl_instance.h"
++#include "content/browser/first_party_sets/first_party_sets_handler_impl.h"
+ 
+ #include <memory>
+ #include <string>
+@@ -18,7 +18,6 @@
+ #include "base/types/optional_util.h"
+ #include "base/values.h"
+ #include "content/browser/first_party_sets/first_party_set_parser.h"
+-#include "content/browser/first_party_sets/first_party_sets_handler_impl.h"
+ #include "content/browser/first_party_sets/first_party_sets_loader.h"
+ #include "content/browser/first_party_sets/first_party_sets_site_data_remover.h"
+ #include "content/browser/first_party_sets/local_set_declaration.h"
+@@ -79,16 +78,15 @@ void FirstPartySetsHandlerImpl::SetInstanceForTesting(
+ 
+ // static
+ FirstPartySetsHandler* FirstPartySetsHandler::GetInstance() {
+-  if (g_test_instance) {
++  if (g_test_instance)
+     return g_test_instance;
+-  }
+ 
+   return FirstPartySetsHandlerImpl::GetInstance();
+ }
+ 
+ // static
+ FirstPartySetsHandlerImpl* FirstPartySetsHandlerImpl::GetInstance() {
+-  static base::NoDestructor<FirstPartySetsHandlerImplInstance> instance(
++  static base::NoDestructor<FirstPartySetsHandlerImpl> instance(
+       GetContentClient()->browser()->IsFirstPartySetsEnabled(),
+       GetContentClient()->browser()->WillProvidePublicFirstPartySets());
+   if (g_impl_test_instance) {
+@@ -113,19 +111,17 @@ FirstPartySetsHandler::ValidateEnterprisePolicy(
+ }
+ 
+ // static
+-FirstPartySetsHandlerImplInstance
+-FirstPartySetsHandlerImplInstance::CreateForTesting(
++FirstPartySetsHandlerImpl FirstPartySetsHandlerImpl::CreateForTesting(
+     bool enabled,
+     bool embedder_will_provide_public_sets) {
+-  return FirstPartySetsHandlerImplInstance(enabled,
+-                                           embedder_will_provide_public_sets);
++  return FirstPartySetsHandlerImpl(enabled, embedder_will_provide_public_sets);
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::GetContextConfigForPolicy(
++void FirstPartySetsHandlerImpl::GetContextConfigForPolicy(
+     const base::Value::Dict* policy,
+     base::OnceCallback<void(net::FirstPartySetsContextConfig)> callback) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+-  if (!policy) {
++  if (!policy || !enabled_) {
+     std::move(callback).Run(net::FirstPartySetsContextConfig());
+     return;
+   }
+@@ -138,49 +134,57 @@ void FirstPartySetsHandlerImplInstance::GetContextConfigForPolicy(
+   // of First-Party Sets has been fully initialized.
+   EnqueuePendingTask(
+       base::BindOnce(
+-          &FirstPartySetsHandlerImplInstance::GetContextConfigForPolicyInternal,
++          &FirstPartySetsHandlerImpl::GetContextConfigForPolicyInternal,
+           // base::Unretained(this) is safe here because this is a static
+           // singleton.
+           base::Unretained(this), policy->Clone(), base::ElapsedTimer())
+           .Then(std::move(callback)));
+ }
+ 
+-FirstPartySetsHandlerImplInstance::FirstPartySetsHandlerImplInstance(
+-    bool enabled,
+-    bool embedder_will_provide_public_sets)
+-    : enabled_(enabled) {
+-  if (enabled) {
+-    on_sets_ready_callbacks_ =
+-        std::make_unique<base::circular_deque<base::OnceClosure>>();
+-    sets_loader_ = std::make_unique<FirstPartySetsLoader>(
+-        base::BindOnce(&FirstPartySetsHandlerImplInstance::SetCompleteSets,
+-                       // base::Unretained(this) is safe here because
+-                       // this is a static singleton.
+-                       base::Unretained(this)));
+-    if (!embedder_will_provide_public_sets) {
+-      sets_loader_->SetComponentSets(base::Version(), base::File());
+-    }
+-  } else {
+-    SetCompleteSets(net::GlobalFirstPartySets());
+-    CHECK(global_sets_.has_value());
+-  }
++net::FirstPartySetsContextConfig
++FirstPartySetsHandlerImpl::ComputeEnterpriseContextConfig(
++    const net::GlobalFirstPartySets& global_sets,
++    const FirstPartySetParser::ParsedPolicySetLists& policy) {
++  return global_sets.ComputeConfig(
++      /*replacement_sets=*/policy.replacements,
++      /*addition_sets=*/
++      policy.additions);
+ }
+ 
+-FirstPartySetsHandlerImplInstance::~FirstPartySetsHandlerImplInstance() =
+-    default;
++FirstPartySetsHandlerImpl::FirstPartySetsHandlerImpl(
++    base::PassKey<ScopedMockFirstPartySetsHandler>,
++    bool enabled,
++    bool embedder_will_provide_public_sets)
++    : FirstPartySetsHandlerImpl(enabled, embedder_will_provide_public_sets) {}
+ 
+-absl::optional<net::GlobalFirstPartySets>
+-FirstPartySetsHandlerImplInstance::GetSets(
+-    base::OnceCallback<void(net::GlobalFirstPartySets)> callback) {
++FirstPartySetsHandlerImpl::FirstPartySetsHandlerImpl(
++    bool enabled,
++    bool embedder_will_provide_public_sets)
++    : enabled_(enabled),
++      embedder_will_provide_public_sets_(enabled &&
++                                         embedder_will_provide_public_sets),
++      sets_loader_(std::make_unique<FirstPartySetsLoader>(
++          base::BindOnce(&FirstPartySetsHandlerImpl::SetCompleteSets,
++                         // base::Unretained(this) is safe here because
++                         // this is a static singleton.
++                         base::Unretained(this)))) {}
++
++FirstPartySetsHandlerImpl::~FirstPartySetsHandlerImpl() = default;
++
++absl::optional<net::GlobalFirstPartySets> FirstPartySetsHandlerImpl::GetSets(
++    SetsReadyOnceCallback callback) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+-  if (global_sets_.has_value()) {
+-    return global_sets_->Clone();
++  if (!IsEnabled()) {
++    return net::GlobalFirstPartySets();
+   }
++  CHECK(IsEnabled());
++  if (global_sets_.has_value())
++    return global_sets_->Clone();
+ 
+   if (!callback.is_null()) {
+     // base::Unretained(this) is safe here because this is a static singleton.
+     EnqueuePendingTask(
+-        base::BindOnce(&FirstPartySetsHandlerImplInstance::GetGlobalSetsSync,
++        base::BindOnce(&FirstPartySetsHandlerImpl::GetGlobalSetsSync,
+                        base::Unretained(this))
+             .Then(std::move(callback)));
+   }
+@@ -188,33 +192,35 @@ FirstPartySetsHandlerImplInstance::GetSets(
+   return absl::nullopt;
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::Init(
+-    const base::FilePath& user_data_dir,
+-    const LocalSetDeclaration& local_set) {
++void FirstPartySetsHandlerImpl::Init(const base::FilePath& user_data_dir,
++                                     const LocalSetDeclaration& local_set) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+-  if (initialized_) {
+-    return;
+-  }
++  CHECK(!initialized_);
++  CHECK(sets_loader_);
+ 
+   initialized_ = true;
+   SetDatabase(user_data_dir);
+ 
+-  if (sets_loader_) {
++  if (IsEnabled()) {
+     sets_loader_->SetManuallySpecifiedSet(local_set);
++    if (!embedder_will_provide_public_sets_) {
++      sets_loader_->SetComponentSets(base::Version(), base::File());
++    }
++  } else {
++    SetCompleteSets(net::GlobalFirstPartySets());
+   }
+ }
+ 
+-bool FirstPartySetsHandlerImplInstance::IsEnabled() const {
++bool FirstPartySetsHandlerImpl::IsEnabled() const {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+   return enabled_;
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::SetPublicFirstPartySets(
++void FirstPartySetsHandlerImpl::SetPublicFirstPartySets(
+     const base::Version& version,
+     base::File sets_file) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+-  if (!sets_loader_) {
+-    FirstPartySetsLoader::DisposeFile(std::move(sets_file));
++  if (!enabled_ || !embedder_will_provide_public_sets_ || !sets_loader_) {
+     return;
+   }
+ 
+@@ -222,7 +228,7 @@ void FirstPartySetsHandlerImplInstance::SetPublicFirstPartySets(
+   sets_loader_->SetComponentSets(version, std::move(sets_file));
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::GetPersistedSetsForTesting(
++void FirstPartySetsHandlerImpl::GetPersistedSetsForTesting(
+     const std::string& browser_context_id,
+     base::OnceCallback<
+         void(absl::optional<std::pair<net::GlobalFirstPartySets,
+@@ -241,7 +247,7 @@ void FirstPartySetsHandlerImplInstance::GetPersistedSetsForTesting(
+       .Then(std::move(callback));
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::HasBrowserContextClearedForTesting(
++void FirstPartySetsHandlerImpl::HasBrowserContextClearedForTesting(
+     const std::string& browser_context_id,
+     base::OnceCallback<void(absl::optional<bool>)> callback) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+@@ -257,17 +263,18 @@ void FirstPartySetsHandlerImplInstance::HasBrowserContextClearedForTesting(
+       .Then(std::move(callback));
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::SetCompleteSets(
++void FirstPartySetsHandlerImpl::SetCompleteSets(
+     net::GlobalFirstPartySets sets) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+   CHECK(!global_sets_.has_value());
++  CHECK(sets_loader_);
+   global_sets_ = std::move(sets);
+   sets_loader_.reset();
+ 
+   InvokePendingQueries();
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::SetDatabase(
++void FirstPartySetsHandlerImpl::SetDatabase(
+     const base::FilePath& user_data_dir) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+   CHECK(db_helper_.is_null());
+@@ -282,26 +289,22 @@ void FirstPartySetsHandlerImplInstance::SetDatabase(
+                      user_data_dir.Append(kFirstPartySetsDatabase));
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::EnqueuePendingTask(
+-    base::OnceClosure run_task) {
++void FirstPartySetsHandlerImpl::EnqueuePendingTask(base::OnceClosure run_task) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+   CHECK(!global_sets_.has_value());
+-  CHECK(on_sets_ready_callbacks_);
+ 
+   if (!first_async_task_timer_.has_value()) {
+     first_async_task_timer_ = base::ElapsedTimer();
+   }
+ 
+-  on_sets_ready_callbacks_->push_back(std::move(run_task));
++  on_sets_ready_callbacks_.push_back(std::move(run_task));
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::InvokePendingQueries() {
++void FirstPartySetsHandlerImpl::InvokePendingQueries() {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ 
+   base::circular_deque<base::OnceClosure> queue;
+-  if (on_sets_ready_callbacks_) {
+-    queue.swap(*on_sets_ready_callbacks_);
+-  }
++  queue.swap(on_sets_ready_callbacks_);
+ 
+   base::UmaHistogramCounts10000(
+       "Cookie.FirstPartySets.Browser.DelayedQueriesCount", queue.size());
+@@ -315,11 +318,9 @@ void FirstPartySetsHandlerImplInstance::InvokePendingQueries() {
+     queue.pop_front();
+     std::move(callback).Run();
+   }
+-  on_sets_ready_callbacks_.reset();
+ }
+ 
+-absl::optional<net::FirstPartySetEntry>
+-FirstPartySetsHandlerImplInstance::FindEntry(
++absl::optional<net::FirstPartySetEntry> FirstPartySetsHandlerImpl::FindEntry(
+     const net::SchemefulSite& site,
+     const net::FirstPartySetsContextConfig& config) const {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+@@ -330,14 +331,13 @@ FirstPartySetsHandlerImplInstance::FindEntry(
+   return global_sets_->FindEntry(site, config);
+ }
+ 
+-net::GlobalFirstPartySets FirstPartySetsHandlerImplInstance::GetGlobalSetsSync()
+-    const {
++net::GlobalFirstPartySets FirstPartySetsHandlerImpl::GetGlobalSetsSync() const {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+   CHECK(global_sets_.has_value());
+   return global_sets_->Clone();
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::ClearSiteDataOnChangedSetsForContext(
++void FirstPartySetsHandlerImpl::ClearSiteDataOnChangedSetsForContext(
+     base::RepeatingCallback<BrowserContext*()> browser_context_getter,
+     const std::string& browser_context_id,
+     net::FirstPartySetsContextConfig context_config,
+@@ -360,19 +360,17 @@ void FirstPartySetsHandlerImplInstance::ClearSiteDataOnChangedSetsForContext(
+ 
+   // base::Unretained(this) is safe because this is a static singleton.
+   EnqueuePendingTask(base::BindOnce(
+-      &FirstPartySetsHandlerImplInstance::
+-          ClearSiteDataOnChangedSetsForContextInternal,
++      &FirstPartySetsHandlerImpl::ClearSiteDataOnChangedSetsForContextInternal,
+       base::Unretained(this), browser_context_getter, browser_context_id,
+       std::move(context_config), std::move(callback)));
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::
+-    ClearSiteDataOnChangedSetsForContextInternal(
+-        base::RepeatingCallback<BrowserContext*()> browser_context_getter,
+-        const std::string& browser_context_id,
+-        net::FirstPartySetsContextConfig context_config,
+-        base::OnceCallback<void(net::FirstPartySetsContextConfig,
+-                                net::FirstPartySetsCacheFilter)> callback) {
++void FirstPartySetsHandlerImpl::ClearSiteDataOnChangedSetsForContextInternal(
++    base::RepeatingCallback<BrowserContext*()> browser_context_getter,
++    const std::string& browser_context_id,
++    net::FirstPartySetsContextConfig context_config,
++    base::OnceCallback<void(net::FirstPartySetsContextConfig,
++                            net::FirstPartySetsCacheFilter)> callback) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+   CHECK(global_sets_.has_value());
+   CHECK(!browser_context_id.empty());
+@@ -394,7 +392,7 @@ void FirstPartySetsHandlerImplInstance::
+   base::OnceCallback<void(std::pair<std::vector<net::SchemefulSite>,
+                                     net::FirstPartySetsCacheFilter>)>
+       on_get_sites_to_clear = base::BindOnce(
+-          &FirstPartySetsHandlerImplInstance::OnGetSitesToClear,
++          &FirstPartySetsHandlerImpl::OnGetSitesToClear,
+           // base::Unretained(this) is safe here because this
+           // is a static singleton.
+           base::Unretained(this), browser_context_getter, browser_context_id,
+@@ -408,7 +406,7 @@ void FirstPartySetsHandlerImplInstance::
+       .Then(std::move(on_get_sites_to_clear));
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::OnGetSitesToClear(
++void FirstPartySetsHandlerImpl::OnGetSitesToClear(
+     base::RepeatingCallback<BrowserContext*()> browser_context_getter,
+     const std::string& browser_context_id,
+     net::FirstPartySetsContextConfig context_config,
+@@ -434,16 +432,15 @@ void FirstPartySetsHandlerImplInstance::OnGetSitesToClear(
+   FirstPartySetsSiteDataRemover::RemoveSiteData(
+       *browser_context->GetBrowsingDataRemover(),
+       std::move(sites_to_clear.first),
+-      base::BindOnce(&FirstPartySetsHandlerImplInstance::
+-                         DidClearSiteDataOnChangedSetsForContext,
+-                     // base::Unretained(this) is safe here because
+-                     // this is a static singleton.
+-                     base::Unretained(this), browser_context_id,
+-                     std::move(context_config),
+-                     std::move(sites_to_clear.second), std::move(callback)));
++      base::BindOnce(
++          &FirstPartySetsHandlerImpl::DidClearSiteDataOnChangedSetsForContext,
++          // base::Unretained(this) is safe here because
++          // this is a static singleton.
++          base::Unretained(this), browser_context_id, std::move(context_config),
++          std::move(sites_to_clear.second), std::move(callback)));
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::DidClearSiteDataOnChangedSetsForContext(
++void FirstPartySetsHandlerImpl::DidClearSiteDataOnChangedSetsForContext(
+     const std::string& browser_context_id,
+     net::FirstPartySetsContextConfig context_config,
+     net::FirstPartySetsCacheFilter cache_filter,
+@@ -473,28 +470,30 @@ void FirstPartySetsHandlerImplInstance::DidClearSiteDataOnChangedSetsForContext(
+   std::move(callback).Run(std::move(context_config), std::move(cache_filter));
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::ComputeFirstPartySetMetadata(
++void FirstPartySetsHandlerImpl::ComputeFirstPartySetMetadata(
+     const net::SchemefulSite& site,
+     const net::SchemefulSite* top_frame_site,
++    const std::set<net::SchemefulSite>& party_context,
+     const net::FirstPartySetsContextConfig& config,
+     base::OnceCallback<void(net::FirstPartySetMetadata)> callback) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+   if (!global_sets_.has_value()) {
+     EnqueuePendingTask(base::BindOnce(
+-        &FirstPartySetsHandlerImplInstance::
+-            ComputeFirstPartySetMetadataInternal,
++        &FirstPartySetsHandlerImpl::ComputeFirstPartySetMetadataInternal,
+         base::Unretained(this), site, base::OptionalFromPtr(top_frame_site),
+-        config.Clone(), base::ElapsedTimer(), std::move(callback)));
++        party_context, config.Clone(), base::ElapsedTimer(),
++        std::move(callback)));
+     return;
+   }
+ 
+-  std::move(callback).Run(
+-      global_sets_->ComputeMetadata(site, top_frame_site, config));
++  std::move(callback).Run(global_sets_->ComputeMetadata(site, top_frame_site,
++                                                        party_context, config));
+ }
+ 
+-void FirstPartySetsHandlerImplInstance::ComputeFirstPartySetMetadataInternal(
++void FirstPartySetsHandlerImpl::ComputeFirstPartySetMetadataInternal(
+     const net::SchemefulSite& site,
+     const absl::optional<net::SchemefulSite>& top_frame_site,
++    const std::set<net::SchemefulSite>& party_context,
+     const net::FirstPartySetsContextConfig& config,
+     const base::ElapsedTimer& timer,
+     base::OnceCallback<void(net::FirstPartySetMetadata)> callback) const {
+@@ -506,11 +505,11 @@ void FirstPartySetsHandlerImplInstance::ComputeFirstPartySetMetadataInternal(
+       timer.Elapsed());
+ 
+   std::move(callback).Run(global_sets_->ComputeMetadata(
+-      site, base::OptionalToPtr(top_frame_site), config));
++      site, base::OptionalToPtr(top_frame_site), party_context, config));
+ }
+ 
+ net::FirstPartySetsContextConfig
+-FirstPartySetsHandlerImplInstance::GetContextConfigForPolicyInternal(
++FirstPartySetsHandlerImpl::GetContextConfigForPolicyInternal(
+     const base::Value::Dict& policy,
+     const absl::optional<base::ElapsedTimer>& timer) const {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+@@ -522,22 +521,16 @@ FirstPartySetsHandlerImplInstance::GetContextConfigForPolicyInternal(
+         timer->Elapsed());
+   }
+ 
+-  if (!enabled_) {
+-    return net::FirstPartySetsContextConfig();
+-  }
+-
+   auto [parsed, warnings] =
+       FirstPartySetParser::ParseSetsFromEnterprisePolicy(policy);
+ 
+   return parsed.has_value()
+-             ? global_sets_.value().ComputeConfig(
+-                   /*replacement_sets=*/parsed.value().replacements,
+-                   /*addition_sets=*/
+-                   parsed.value().additions)
++             ? FirstPartySetsHandlerImpl::ComputeEnterpriseContextConfig(
++                   global_sets_.value(), parsed.value())
+              : net::FirstPartySetsContextConfig();
+ }
+ 
+-bool FirstPartySetsHandlerImplInstance::ForEachEffectiveSetEntry(
++bool FirstPartySetsHandlerImpl::ForEachEffectiveSetEntry(
+     const net::FirstPartySetsContextConfig& config,
+     base::FunctionRef<bool(const net::SchemefulSite&,
+                            const net::FirstPartySetEntry&)> f) const {
+diff --git a/content/browser/first_party_sets/first_party_sets_handler_impl.h b/content/browser/first_party_sets/first_party_sets_handler_impl.h
+index 9696bbb0281be8502c78d19de3ca7b9090f8771c..aec4e931eb4e21a660577432d12488c1316e2f2c 100644
+--- a/content/browser/first_party_sets/first_party_sets_handler_impl.h
++++ b/content/browser/first_party_sets/first_party_sets_handler_impl.h
+@@ -5,25 +5,64 @@
+ #ifndef CONTENT_BROWSER_FIRST_PARTY_SETS_FIRST_PARTY_SETS_HANDLER_IMPL_H_
+ #define CONTENT_BROWSER_FIRST_PARTY_SETS_FIRST_PARTY_SETS_HANDLER_IMPL_H_
+ 
++#include <string>
++#include <utility>
++
++#include "base/containers/circular_deque.h"
++#include "base/files/file.h"
+ #include "base/files/file_path.h"
+ #include "base/functional/callback.h"
++#include "base/no_destructor.h"
++#include "base/sequence_checker.h"
++#include "base/thread_annotations.h"
++#include "base/threading/sequence_bound.h"
++#include "base/timer/elapsed_timer.h"
++#include "base/types/pass_key.h"
++#include "base/values.h"
++#include "base/version.h"
++#include "content/browser/first_party_sets/first_party_set_parser.h"
++#include "content/browser/first_party_sets/first_party_sets_handler_database_helper.h"
++#include "content/browser/first_party_sets/first_party_sets_loader.h"
+ #include "content/browser/first_party_sets/local_set_declaration.h"
+ #include "content/common/content_export.h"
+ #include "content/public/browser/first_party_sets_handler.h"
++#include "net/first_party_sets/first_party_sets_cache_filter.h"
++#include "net/first_party_sets/first_party_sets_context_config.h"
+ #include "net/first_party_sets/global_first_party_sets.h"
+ #include "third_party/abseil-cpp/absl/types/optional.h"
+ 
++namespace net {
++class FirstPartySetEntry;
++class SchemefulSite;
++}  // namespace net
++
+ namespace content {
+ 
+-// FirstPartySetsHandlerImpl is an abstract class, encapsulating the
+-// content-internal details of the First-Party Sets infrastructure. This class
+-// is abstract so that it can be mocked during testing.
++class BrowserContext;
++class ScopedMockFirstPartySetsHandler;
++
++// Class FirstPartySetsHandlerImpl is a singleton, it allows an embedder to
++// provide First-Party Sets inputs from custom sources, then parses/merges the
++// inputs to form the current First-Party Sets data, compares them with the
++// persisted First-Party Sets data used during the last browser session to get
++// a list of sites that changed the First-Party Set they are part of, invokes
++// the provided callback with the current First-Party Sets data, and writes
++// the current First-Party Sets data to disk.
+ class CONTENT_EXPORT FirstPartySetsHandlerImpl : public FirstPartySetsHandler {
+  public:
++  using SetsReadyOnceCallback =
++      base::OnceCallback<void(net::GlobalFirstPartySets)>;
++
+   static FirstPartySetsHandlerImpl* GetInstance();
+ 
+   static void SetInstanceForTesting(FirstPartySetsHandlerImpl* test_instance);
+ 
++  ~FirstPartySetsHandlerImpl() override;
++
++  FirstPartySetsHandlerImpl(const FirstPartySetsHandlerImpl&) = delete;
++  FirstPartySetsHandlerImpl& operator=(const FirstPartySetsHandlerImpl&) =
++      delete;
++
+   // This method reads the persisted First-Party Sets from the file under
+   // `user_data_dir` and sets the First-Party Set that was provided via the
+   // flag(s).
+@@ -32,9 +71,14 @@ class CONTENT_EXPORT FirstPartySetsHandlerImpl : public FirstPartySetsHandler {
+   // persisted sets, since we may still need to clear data from a previous
+   // invocation of Chromium which had First-Party Sets enabled.
+   //
+-  // Only the first call has any effect.
+-  void virtual Init(const base::FilePath& user_data_dir,
+-                    const LocalSetDeclaration& local_set) = 0;
++  // Must be called exactly once.
++  void Init(const base::FilePath& user_data_dir,
++            const LocalSetDeclaration& local_set);
++
++  // Factory method that exposes the ctor for testing.
++  static FirstPartySetsHandlerImpl CreateForTesting(
++      bool enabled,
++      bool embedder_will_provide_public_sets);
+ 
+   // Returns the fully-parsed and validated global First-Party Sets data.
+   // Returns the data synchronously via an absl::optional if it's already
+@@ -49,7 +93,163 @@ class CONTENT_EXPORT FirstPartySetsHandlerImpl : public FirstPartySetsHandler {
+   // If First-Party Sets is disabled, this returns a populated optional with an
+   // empty GlobalFirstPartySets instance.
+   [[nodiscard]] virtual absl::optional<net::GlobalFirstPartySets> GetSets(
+-      base::OnceCallback<void(net::GlobalFirstPartySets)> callback) = 0;
++      SetsReadyOnceCallback callback);
++
++  // FirstPartySetsHandler
++  bool IsEnabled() const override;
++  void SetPublicFirstPartySets(const base::Version& version,
++                               base::File sets_file) override;
++  absl::optional<net::FirstPartySetEntry> FindEntry(
++      const net::SchemefulSite& site,
++      const net::FirstPartySetsContextConfig& config) const override;
++  void GetContextConfigForPolicy(
++      const base::Value::Dict* policy,
++      base::OnceCallback<void(net::FirstPartySetsContextConfig)> callback)
++      override;
++  void ClearSiteDataOnChangedSetsForContext(
++      base::RepeatingCallback<BrowserContext*()> browser_context_getter,
++      const std::string& browser_context_id,
++      net::FirstPartySetsContextConfig context_config,
++      base::OnceCallback<void(net::FirstPartySetsContextConfig,
++                              net::FirstPartySetsCacheFilter)> callback)
++      override;
++  void ComputeFirstPartySetMetadata(
++      const net::SchemefulSite& site,
++      const net::SchemefulSite* top_frame_site,
++      const std::set<net::SchemefulSite>& party_context,
++      const net::FirstPartySetsContextConfig& config,
++      base::OnceCallback<void(net::FirstPartySetMetadata)> callback) override;
++  bool ForEachEffectiveSetEntry(
++      const net::FirstPartySetsContextConfig& config,
++      base::FunctionRef<bool(const net::SchemefulSite&,
++                             const net::FirstPartySetEntry&)> f) const override;
++  void GetPersistedSetsForTesting(
++      const std::string& browser_context_id,
++      base::OnceCallback<
++          void(absl::optional<std::pair<net::GlobalFirstPartySets,
++                                        net::FirstPartySetsContextConfig>>)>
++          callback);
++  void HasBrowserContextClearedForTesting(
++      const std::string& browser_context_id,
++      base::OnceCallback<void(absl::optional<bool>)> callback);
++
++  void SynchronouslyResetDBHelperForTesting() {
++    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
++    db_helper_.SynchronouslyResetForTest();  // IN-TEST
++  }
++
++  // Computes information needed by the FirstPartySetsAccessDelegate in order
++  // to update the browser's list of First-Party Sets to respect a profile's
++  // setting for the per-profile FirstPartySetsOverrides policy.
++  static net::FirstPartySetsContextConfig ComputeEnterpriseContextConfig(
++      const net::GlobalFirstPartySets& browser_sets,
++      const FirstPartySetParser::ParsedPolicySetLists& policy);
++
++ protected:
++  FirstPartySetsHandlerImpl(base::PassKey<ScopedMockFirstPartySetsHandler> key,
++                            bool enabled,
++                            bool embedder_will_provide_public_sets);
++
++ private:
++  friend class base::NoDestructor<FirstPartySetsHandlerImpl>;
++
++  FirstPartySetsHandlerImpl(bool enabled,
++                            bool embedder_will_provide_public_sets);
++
++  // Sets the global First-Party Sets data. Must be called exactly once.
++  void SetCompleteSets(net::GlobalFirstPartySets sets);
++
++  // Sets `db_helper_`, which will initialize the underlying First-Party Sets
++  // database under `user_data_dir`. Must be called exactly once.
++  void SetDatabase(const base::FilePath& user_data_dir);
++
++  // Enqueues a task to be performed once initialization is complete.
++  void EnqueuePendingTask(base::OnceClosure run_task);
++
++  // Invokes any pending queries.
++  void InvokePendingQueries();
++
++  // Returns the global First-Party Sets. This clones the underlying
++  // data.
++  //
++  // Must be called after the list has been initialized.
++  net::GlobalFirstPartySets GetGlobalSetsSync() const;
++
++  // Performs the actual state clearing for the given context. Must not be
++  // called until initialization is complete.
++  void ClearSiteDataOnChangedSetsForContextInternal(
++      base::RepeatingCallback<BrowserContext*()> browser_context_getter,
++      const std::string& browser_context_id,
++      net::FirstPartySetsContextConfig context_config,
++      base::OnceCallback<void(net::FirstPartySetsContextConfig,
++                              net::FirstPartySetsCacheFilter)> callback);
++
++  // Like ComputeFirstPartySetMetadata, but passes the result into the provided
++  // callback. Must not be called before `global_sets_` has been set.
++  void ComputeFirstPartySetMetadataInternal(
++      const net::SchemefulSite& site,
++      const absl::optional<net::SchemefulSite>& top_frame_site,
++      const std::set<net::SchemefulSite>& party_context,
++      const net::FirstPartySetsContextConfig& config,
++      const base::ElapsedTimer& timer,
++      base::OnceCallback<void(net::FirstPartySetMetadata)> callback) const;
++
++  // Parses the policy and computes the config that represents the changes
++  // needed to apply `policy` to `global_sets_`.
++  net::FirstPartySetsContextConfig GetContextConfigForPolicyInternal(
++      const base::Value::Dict& policy,
++      const absl::optional<base::ElapsedTimer>& timer) const;
++
++  void OnGetSitesToClear(
++      base::RepeatingCallback<BrowserContext*()> browser_context_getter,
++      const std::string& browser_context_id,
++      net::FirstPartySetsContextConfig context_config,
++      base::OnceCallback<void(net::FirstPartySetsContextConfig,
++                              net::FirstPartySetsCacheFilter)> callback,
++      std::pair<std::vector<net::SchemefulSite>, net::FirstPartySetsCacheFilter>
++          sites_to_clear) const;
++
++  // `failed_data_types` is a bitmask used to indicate data types from
++  // BrowsingDataRemover::DataType enum that were failed to remove. 0 indicates
++  // success.
++  void DidClearSiteDataOnChangedSetsForContext(
++      const std::string& browser_context_id,
++      net::FirstPartySetsContextConfig context_config,
++      net::FirstPartySetsCacheFilter cache_filter,
++      base::OnceCallback<void(net::FirstPartySetsContextConfig,
++                              net::FirstPartySetsCacheFilter)> callback,
++      uint64_t failed_data_types) const;
++
++  // Whether Init has been called already or not.
++  bool initialized_ = false;
++
++  // The global First-Party Sets, after parsing and validation.
++  //
++  // This is nullopt until all of the required inputs have been received.
++  absl::optional<net::GlobalFirstPartySets> global_sets_
++      GUARDED_BY_CONTEXT(sequence_checker_);
++
++  bool enabled_ GUARDED_BY_CONTEXT(sequence_checker_);
++  bool embedder_will_provide_public_sets_ GUARDED_BY_CONTEXT(sequence_checker_);
++
++  // We use a OnceCallback to ensure we only pass along the sets once
++  // during Chrome's lifetime (modulo reconfiguring the network service).
++  base::circular_deque<base::OnceClosure> on_sets_ready_callbacks_
++      GUARDED_BY_CONTEXT(sequence_checker_);
++
++  std::unique_ptr<FirstPartySetsLoader> sets_loader_
++      GUARDED_BY_CONTEXT(sequence_checker_);
++
++  // Timer starting when the first async task was enqueued, if any. Used for
++  // metrics.
++  absl::optional<base::ElapsedTimer> first_async_task_timer_
++      GUARDED_BY_CONTEXT(sequence_checker_);
++
++  // Access the underlying DB on a database sequence to make sure none of DB
++  // operations that support blocking are called directly on the main thread.
++  base::SequenceBound<FirstPartySetsHandlerDatabaseHelper> db_helper_;
++
++  SEQUENCE_CHECKER(sequence_checker_);
+ };
+ 
+ }  // namespace content
+diff --git a/content/browser/first_party_sets/first_party_sets_handler_impl_instance.h b/content/browser/first_party_sets/first_party_sets_handler_impl_instance.h
+deleted file mode 100644
+index a4f5090c8dfffdb8fca5e1e99862b4fa29134557..0000000000000000000000000000000000000000
+--- a/content/browser/first_party_sets/first_party_sets_handler_impl_instance.h
++++ /dev/null
+@@ -1,221 +0,0 @@
+-// Copyright 2023 The Chromium Authors
+-// Use of this source code is governed by a BSD-style license that can be
+-// found in the LICENSE file.
+-
+-#ifndef CONTENT_BROWSER_FIRST_PARTY_SETS_FIRST_PARTY_SETS_HANDLER_IMPL_INSTANCE_H_
+-#define CONTENT_BROWSER_FIRST_PARTY_SETS_FIRST_PARTY_SETS_HANDLER_IMPL_INSTANCE_H_
+-
+-#include <string>
+-#include <utility>
+-
+-#include "base/containers/circular_deque.h"
+-#include "base/files/file.h"
+-#include "base/files/file_path.h"
+-#include "base/functional/callback.h"
+-#include "base/no_destructor.h"
+-#include "base/sequence_checker.h"
+-#include "base/thread_annotations.h"
+-#include "base/threading/sequence_bound.h"
+-#include "base/timer/elapsed_timer.h"
+-#include "base/values.h"
+-#include "base/version.h"
+-#include "content/browser/first_party_sets/first_party_sets_handler_database_helper.h"
+-#include "content/browser/first_party_sets/first_party_sets_handler_impl.h"
+-#include "content/browser/first_party_sets/first_party_sets_loader.h"
+-#include "content/browser/first_party_sets/local_set_declaration.h"
+-#include "content/common/content_export.h"
+-#include "net/first_party_sets/first_party_sets_cache_filter.h"
+-#include "net/first_party_sets/first_party_sets_context_config.h"
+-#include "net/first_party_sets/global_first_party_sets.h"
+-#include "third_party/abseil-cpp/absl/types/optional.h"
+-
+-namespace net {
+-class FirstPartySetEntry;
+-class SchemefulSite;
+-}  // namespace net
+-
+-namespace content {
+-
+-class BrowserContext;
+-
+-// Class FirstPartySetsHandlerImplInstance is a singleton, it allows an embedder
+-// to provide First-Party Sets inputs from custom sources, then parses/merges
+-// the inputs to form the current First-Party Sets data, compares them with the
+-// persisted First-Party Sets data used during the last browser session to get
+-// a list of sites that changed the First-Party Set they are part of, invokes
+-// the provided callback with the current First-Party Sets data, and writes
+-// the current First-Party Sets data to disk.
+-class CONTENT_EXPORT FirstPartySetsHandlerImplInstance
+-    : public FirstPartySetsHandlerImpl {
+- public:
+-  ~FirstPartySetsHandlerImplInstance() override;
+-
+-  FirstPartySetsHandlerImplInstance(const FirstPartySetsHandlerImplInstance&) =
+-      delete;
+-  FirstPartySetsHandlerImplInstance& operator=(
+-      const FirstPartySetsHandlerImplInstance&) = delete;
+-
+-  // Factory method that exposes the ctor for testing.
+-  static FirstPartySetsHandlerImplInstance CreateForTesting(
+-      bool enabled,
+-      bool embedder_will_provide_public_sets);
+-
+-  // FirstPartySetsHandlerImpl:
+-  void Init(const base::FilePath& user_data_dir,
+-            const LocalSetDeclaration& local_set) override;
+-  [[nodiscard]] absl::optional<net::GlobalFirstPartySets> GetSets(
+-      base::OnceCallback<void(net::GlobalFirstPartySets)> callback) override;
+-
+-  // FirstPartySetsHandler:
+-  bool IsEnabled() const override;
+-  void SetPublicFirstPartySets(const base::Version& version,
+-                               base::File sets_file) override;
+-  absl::optional<net::FirstPartySetEntry> FindEntry(
+-      const net::SchemefulSite& site,
+-      const net::FirstPartySetsContextConfig& config) const override;
+-  void GetContextConfigForPolicy(
+-      const base::Value::Dict* policy,
+-      base::OnceCallback<void(net::FirstPartySetsContextConfig)> callback)
+-      override;
+-  void ClearSiteDataOnChangedSetsForContext(
+-      base::RepeatingCallback<BrowserContext*()> browser_context_getter,
+-      const std::string& browser_context_id,
+-      net::FirstPartySetsContextConfig context_config,
+-      base::OnceCallback<void(net::FirstPartySetsContextConfig,
+-                              net::FirstPartySetsCacheFilter)> callback)
+-      override;
+-  void ComputeFirstPartySetMetadata(
+-      const net::SchemefulSite& site,
+-      const net::SchemefulSite* top_frame_site,
+-      const net::FirstPartySetsContextConfig& config,
+-      base::OnceCallback<void(net::FirstPartySetMetadata)> callback) override;
+-  bool ForEachEffectiveSetEntry(
+-      const net::FirstPartySetsContextConfig& config,
+-      base::FunctionRef<bool(const net::SchemefulSite&,
+-                             const net::FirstPartySetEntry&)> f) const override;
+-  void GetPersistedSetsForTesting(
+-      const std::string& browser_context_id,
+-      base::OnceCallback<
+-          void(absl::optional<std::pair<net::GlobalFirstPartySets,
+-                                        net::FirstPartySetsContextConfig>>)>
+-          callback);
+-  void HasBrowserContextClearedForTesting(
+-      const std::string& browser_context_id,
+-      base::OnceCallback<void(absl::optional<bool>)> callback);
+-
+-  void SynchronouslyResetDBHelperForTesting() {
+-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+-    db_helper_.SynchronouslyResetForTest();  // IN-TEST
+-  }
+-
+- private:
+-  friend class base::NoDestructor<FirstPartySetsHandlerImplInstance>;
+-
+-  FirstPartySetsHandlerImplInstance(bool enabled,
+-                                    bool embedder_will_provide_public_sets);
+-
+-  // Sets the global First-Party Sets data. Must be called exactly once.
+-  void SetCompleteSets(net::GlobalFirstPartySets sets);
+-
+-  // Sets `db_helper_`, which will initialize the underlying First-Party Sets
+-  // database under `user_data_dir`. Must be called exactly once.
+-  void SetDatabase(const base::FilePath& user_data_dir);
+-
+-  // Enqueues a task to be performed once initialization is complete.
+-  void EnqueuePendingTask(base::OnceClosure run_task);
+-
+-  // Invokes any pending queries.
+-  void InvokePendingQueries();
+-
+-  // Returns the global First-Party Sets. This clones the underlying
+-  // data.
+-  //
+-  // Must be called after the list has been initialized.
+-  net::GlobalFirstPartySets GetGlobalSetsSync() const;
+-
+-  // Performs the actual state clearing for the given context. Must not be
+-  // called until initialization is complete.
+-  void ClearSiteDataOnChangedSetsForContextInternal(
+-      base::RepeatingCallback<BrowserContext*()> browser_context_getter,
+-      const std::string& browser_context_id,
+-      net::FirstPartySetsContextConfig context_config,
+-      base::OnceCallback<void(net::FirstPartySetsContextConfig,
+-                              net::FirstPartySetsCacheFilter)> callback);
+-
+-  // Like ComputeFirstPartySetMetadata, but passes the result into the provided
+-  // callback. Must not be called before `global_sets_` has been set.
+-  void ComputeFirstPartySetMetadataInternal(
+-      const net::SchemefulSite& site,
+-      const absl::optional<net::SchemefulSite>& top_frame_site,
+-      const net::FirstPartySetsContextConfig& config,
+-      const base::ElapsedTimer& timer,
+-      base::OnceCallback<void(net::FirstPartySetMetadata)> callback) const;
+-
+-  // Parses the policy and computes the config that represents the changes
+-  // needed to apply `policy` to `global_sets_`.
+-  net::FirstPartySetsContextConfig GetContextConfigForPolicyInternal(
+-      const base::Value::Dict& policy,
+-      const absl::optional<base::ElapsedTimer>& timer) const;
+-
+-  void OnGetSitesToClear(
+-      base::RepeatingCallback<BrowserContext*()> browser_context_getter,
+-      const std::string& browser_context_id,
+-      net::FirstPartySetsContextConfig context_config,
+-      base::OnceCallback<void(net::FirstPartySetsContextConfig,
+-                              net::FirstPartySetsCacheFilter)> callback,
+-      std::pair<std::vector<net::SchemefulSite>, net::FirstPartySetsCacheFilter>
+-          sites_to_clear) const;
+-
+-  // `failed_data_types` is a bitmask used to indicate data types from
+-  // BrowsingDataRemover::DataType enum that were failed to remove. 0 indicates
+-  // success.
+-  void DidClearSiteDataOnChangedSetsForContext(
+-      const std::string& browser_context_id,
+-      net::FirstPartySetsContextConfig context_config,
+-      net::FirstPartySetsCacheFilter cache_filter,
+-      base::OnceCallback<void(net::FirstPartySetsContextConfig,
+-                              net::FirstPartySetsCacheFilter)> callback,
+-      uint64_t failed_data_types) const;
+-
+-  // Whether Init has been called already or not.
+-  bool initialized_ = false;
+-
+-  // The global First-Party Sets, after parsing and validation.
+-  //
+-  // This is nullopt until all of the required inputs have been received.
+-  absl::optional<net::GlobalFirstPartySets> global_sets_
+-      GUARDED_BY_CONTEXT(sequence_checker_);
+-
+-  // Whether the First-Party Sets feature should behave as "enabled" or not,
+-  // according to the embedder.
+-  const bool enabled_ GUARDED_BY_CONTEXT(sequence_checker_);
+-
+-  // A queue of tasks waiting to run once this instance has the full
+-  // GlobalFirstPartySets instance. If `enabled_` is true, then this queue is
+-  // non-null until `global_sets_` is non-nullopt. Otherwise, it is always
+-  // nullptr.
+-  std::unique_ptr<base::circular_deque<base::OnceClosure>>
+-      on_sets_ready_callbacks_ GUARDED_BY_CONTEXT(sequence_checker_);
+-
+-  // A helper object to handle loading and combining First-Party Sets from
+-  // different sources (i.e. the command-line flag and the list provided by the
+-  // embedder). This is nullptr if `enabled_` is false; and it is nullptr after
+-  // `global_sets_` has been set.
+-  std::unique_ptr<FirstPartySetsLoader> sets_loader_
+-      GUARDED_BY_CONTEXT(sequence_checker_);
+-
+-  // Timer starting when the first async task was enqueued, if any. Used for
+-  // metrics.
+-  absl::optional<base::ElapsedTimer> first_async_task_timer_
+-      GUARDED_BY_CONTEXT(sequence_checker_);
+-
+-  // Access the underlying DB on a database sequence to make sure none of DB
+-  // operations that support blocking are called directly on the main thread.
+-  base::SequenceBound<FirstPartySetsHandlerDatabaseHelper> db_helper_;
+-
+-  SEQUENCE_CHECKER(sequence_checker_);
+-};
+-
+-}  // namespace content
+-
+-#endif  // CONTENT_BROWSER_FIRST_PARTY_SETS_FIRST_PARTY_SETS_HANDLER_IMPL_H_
+diff --git a/content/browser/first_party_sets/first_party_sets_handler_impl_instance_unittest.cc b/content/browser/first_party_sets/first_party_sets_handler_impl_instance_unittest.cc
+deleted file mode 100644
+index 41c4400326de46d2fe623aa58c8495c50aff76e7..0000000000000000000000000000000000000000
+--- a/content/browser/first_party_sets/first_party_sets_handler_impl_instance_unittest.cc
++++ /dev/null
+@@ -1,925 +0,0 @@
+-// Copyright 2023 The Chromium Authors
+-// Use of this source code is governed by a BSD-style license that can be
+-// found in the LICENSE file.
+-
+-#include "content/browser/first_party_sets/first_party_sets_handler_impl_instance.h"
+-
+-#include <string>
+-#include <utility>
+-#include <vector>
+-
+-#include "base/files/file_path.h"
+-#include "base/files/file_util.h"
+-#include "base/files/scoped_temp_dir.h"
+-#include "base/functional/callback_helpers.h"
+-#include "base/json/json_reader.h"
+-#include "base/notreached.h"
+-#include "base/run_loop.h"
+-#include "base/test/bind.h"
+-#include "base/test/gmock_expected_support.h"
+-#include "base/test/metrics/histogram_tester.h"
+-#include "base/test/scoped_feature_list.h"
+-#include "base/test/task_environment.h"
+-#include "base/test/test_future.h"
+-#include "base/version.h"
+-#include "content/browser/first_party_sets/first_party_set_parser.h"
+-#include "content/browser/first_party_sets/local_set_declaration.h"
+-#include "content/public/browser/first_party_sets_handler.h"
+-#include "content/public/common/content_features.h"
+-#include "content/public/test/browser_task_environment.h"
+-#include "content/public/test/test_browser_context.h"
+-#include "net/base/schemeful_site.h"
+-#include "net/first_party_sets/first_party_set_entry.h"
+-#include "net/first_party_sets/first_party_set_metadata.h"
+-#include "net/first_party_sets/first_party_sets_cache_filter.h"
+-#include "net/first_party_sets/first_party_sets_context_config.h"
+-#include "net/first_party_sets/global_first_party_sets.h"
+-#include "testing/gmock/include/gmock/gmock-matchers.h"
+-#include "testing/gmock/include/gmock/gmock.h"
+-#include "testing/gtest/include/gtest/gtest.h"
+-#include "third_party/abseil-cpp/absl/types/optional.h"
+-#include "url/gurl.h"
+-
+-using ::testing::_;
+-using ::testing::Eq;
+-using ::testing::IsEmpty;
+-using ::testing::Not;
+-using ::testing::Optional;
+-using ::testing::Pair;
+-using ::testing::SizeIs;
+-using ::testing::UnorderedElementsAre;
+-
+-// Some of these tests overlap with FirstPartySetParser unittests, but
+-// overlapping test coverage isn't the worst thing.
+-namespace content {
+-
+-namespace {
+-
+-using ParseErrorType = FirstPartySetsHandler::ParseErrorType;
+-using ParseWarningType = FirstPartySetsHandler::ParseWarningType;
+-
+-constexpr char kAdditionsField[] = "additions";
+-constexpr char kPrimaryField[] = "primary";
+-constexpr char kCctldsField[] = "ccTLDs";
+-
+-constexpr char kFirstPartySetsClearSiteDataOutcomeHistogram[] =
+-    "FirstPartySets.Initialization.ClearSiteDataOutcome";
+-
+-constexpr char kDelayedQueriesCountHistogram[] =
+-    "Cookie.FirstPartySets.Browser.DelayedQueriesCount";
+-constexpr char kMostDelayedQueryDeltaHistogram[] =
+-    "Cookie.FirstPartySets.Browser.MostDelayedQueryDelta";
+-
+-}  // namespace
+-
+-TEST(FirstPartySetsHandlerImplInstance, ValidateEnterprisePolicy_ValidPolicy) {
+-  base::Value input = base::JSONReader::Read(R"(
+-             {
+-                "replacements": [
+-                  {
+-                    "primary": "https://primary1.test",
+-                    "associatedSites": ["https://associatedsite1.test"]
+-                  }
+-                ],
+-                "additions": [
+-                  {
+-                    "primary": "https://primary2.test",
+-                    "associatedSites": ["https://associatedsite2.test"]
+-                  }
+-                ]
+-              }
+-            )")
+-                          .value();
+-  // Validation doesn't fail with an error and there are no warnings to output.
+-  auto [success, warnings] =
+-      FirstPartySetsHandler::ValidateEnterprisePolicy(input.GetDict());
+-  EXPECT_TRUE(success.has_value());
+-  EXPECT_THAT(warnings, IsEmpty());
+-}
+-
+-TEST(FirstPartySetsHandlerImplInstance,
+-     ValidateEnterprisePolicy_ValidPolicyWithWarnings) {
+-  // Some input that matches our policies schema but returns non-fatal warnings.
+-  base::Value input = base::JSONReader::Read(R"(
+-              {
+-                "replacements": [],
+-                "additions": [
+-                  {
+-                    "primary": "https://primary1.test",
+-                    "associatedSites": ["https://associatedsite1.test"],
+-                    "ccTLDs": {
+-                      "https://non-canonical.test": ["https://primary1.test"]
+-                    }
+-                  }
+-                ]
+-              }
+-            )")
+-                          .value();
+-  // Validation succeeds without errors.
+-  auto [success, warnings] =
+-      FirstPartySetsHandler::ValidateEnterprisePolicy(input.GetDict());
+-  EXPECT_TRUE(success.has_value());
+-  // Outputs metadata that can be used to surface a descriptive warning.
+-  EXPECT_THAT(
+-      warnings,
+-      UnorderedElementsAre(FirstPartySetsHandler::ParseWarning(
+-          ParseWarningType::kCctldKeyNotCanonical,
+-          {kAdditionsField, 0, kCctldsField, "https://non-canonical.test"})));
+-}
+-
+-TEST(FirstPartySetsHandlerImplInstance,
+-     ValidateEnterprisePolicy_InvalidPolicy) {
+-  // Some input that matches our policies schema but breaks FPS invariants.
+-  // For more test coverage, see the ParseSetsFromEnterprisePolicy unit tests.
+-  base::Value input = base::JSONReader::Read(R"(
+-              {
+-                "replacements": [
+-                  {
+-                    "primary": "https://primary1.test",
+-                    "associatedSites": ["https://associatedsite1.test"]
+-                  }
+-                ],
+-                "additions": [
+-                  {
+-                    "primary": "https://primary1.test",
+-                    "associatedSites": ["https://associatedsite2.test"]
+-                  }
+-                ]
+-              }
+-            )")
+-                          .value();
+-  // Validation fails with an error and an appropriate ParseError is returned.
+-  EXPECT_THAT(
+-      FirstPartySetsHandler::ValidateEnterprisePolicy(input.GetDict()).first,
+-      base::test::ErrorIs(FirstPartySetsHandler::ParseError(
+-          ParseErrorType::kNonDisjointSets,
+-          {kAdditionsField, 0, kPrimaryField})));
+-}
+-
+-class FirstPartySetsHandlerImplTest : public ::testing::Test {
+- public:
+-  explicit FirstPartySetsHandlerImplTest(bool enabled)
+-      : handler_(FirstPartySetsHandlerImplInstance::CreateForTesting(
+-            /*enabled=*/enabled,
+-            /*embedder_will_provide_public_sets=*/enabled)) {
+-    CHECK(scoped_dir_.CreateUniqueTempDir());
+-    CHECK(PathExists(scoped_dir_.GetPath()));
+-  }
+-
+-  base::File WritePublicSetsFile(base::StringPiece content) {
+-    base::FilePath path =
+-        scoped_dir_.GetPath().Append(FILE_PATH_LITERAL("sets_file.json"));
+-    CHECK(base::WriteFile(path, content));
+-
+-    return base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+-  }
+-
+-  net::GlobalFirstPartySets GetSetsAndWait(
+-      FirstPartySetsHandlerImplInstance& handler) {
+-    base::test::TestFuture<net::GlobalFirstPartySets> future;
+-    absl::optional<net::GlobalFirstPartySets> result =
+-        handler.GetSets(future.GetCallback());
+-    return result.has_value() ? std::move(result).value() : future.Take();
+-  }
+-
+-  net::FirstPartySetsContextConfig GetContextConfigForPolicy(
+-      const base::Value::Dict* policy) {
+-    base::test::TestFuture<net::FirstPartySetsContextConfig> future;
+-    handler().GetContextConfigForPolicy(policy, future.GetCallback());
+-    return future.Take();
+-  }
+-
+-  void ClearSiteDataOnChangedSetsForContextAndWait(
+-      FirstPartySetsHandlerImplInstance& handler,
+-      BrowserContext* context,
+-      const std::string& browser_context_id,
+-      net::FirstPartySetsContextConfig context_config) {
+-    base::RunLoop run_loop;
+-    handler.ClearSiteDataOnChangedSetsForContext(
+-        base::BindLambdaForTesting([&]() { return context; }),
+-        browser_context_id, std::move(context_config),
+-        base::BindLambdaForTesting(
+-            [&](net::FirstPartySetsContextConfig,
+-                net::FirstPartySetsCacheFilter) { run_loop.Quit(); }));
+-    run_loop.Run();
+-  }
+-
+-  absl::optional<
+-      std::pair<net::GlobalFirstPartySets, net::FirstPartySetsContextConfig>>
+-  GetPersistedSetsAndWait(FirstPartySetsHandlerImplInstance& handler,
+-                          const std::string& browser_context_id) {
+-    base::test::TestFuture<absl::optional<
+-        std::pair<net::GlobalFirstPartySets, net::FirstPartySetsContextConfig>>>
+-        future;
+-    handler.GetPersistedSetsForTesting(browser_context_id,
+-                                       future.GetCallback());
+-    return future.Take();
+-  }
+-
+-  absl::optional<bool> HasEntryInBrowserContextsClearedAndWait(
+-      FirstPartySetsHandlerImplInstance& handler,
+-      const std::string& browser_context_id) {
+-    base::test::TestFuture<absl::optional<bool>> future;
+-    handler.HasBrowserContextClearedForTesting(browser_context_id,
+-                                               future.GetCallback());
+-    return future.Take();
+-  }
+-
+-  net::GlobalFirstPartySets GetSetsAndWait() {
+-    return GetSetsAndWait(handler());
+-  }
+-
+-  void ClearSiteDataOnChangedSetsForContextAndWait(
+-      BrowserContext* context,
+-      const std::string& browser_context_id,
+-      net::FirstPartySetsContextConfig context_config) {
+-    ClearSiteDataOnChangedSetsForContextAndWait(
+-        handler(), context, browser_context_id, std::move(context_config));
+-  }
+-
+-  absl::optional<
+-      std::pair<net::GlobalFirstPartySets, net::FirstPartySetsContextConfig>>
+-  GetPersistedSetsAndWait(const std::string& browser_context_id) {
+-    return GetPersistedSetsAndWait(handler(), browser_context_id);
+-  }
+-
+-  base::HistogramTester& histogram_tester() { return histogram_tester_; }
+-
+-  FirstPartySetsHandlerImplInstance& handler() { return handler_; }
+-
+-  BrowserContext* context() { return &context_; }
+-
+- protected:
+-  base::ScopedTempDir scoped_dir_;
+-
+- private:
+-  BrowserTaskEnvironment env_;
+-  TestBrowserContext context_;
+-  base::HistogramTester histogram_tester_;
+-  FirstPartySetsHandlerImplInstance handler_;
+-};
+-
+-class FirstPartySetsHandlerImplDisabledTest
+-    : public FirstPartySetsHandlerImplTest {
+- public:
+-  FirstPartySetsHandlerImplDisabledTest()
+-      : FirstPartySetsHandlerImplTest(/*enabled=*/false) {}
+-};
+-
+-TEST_F(FirstPartySetsHandlerImplDisabledTest, InitMetrics) {
+-  histogram_tester().ExpectTotalCount(kDelayedQueriesCountHistogram, 1);
+-  histogram_tester().ExpectTotalCount(kMostDelayedQueryDeltaHistogram, 1);
+-}
+-
+-TEST_F(FirstPartySetsHandlerImplDisabledTest, InitImmediately) {
+-  // Should already be able to answer queries, even before Init is called.
+-  EXPECT_THAT(handler().GetSets(base::NullCallback()), Optional(_));
+-
+-  EXPECT_EQ(GetContextConfigForPolicy(nullptr),
+-            net::FirstPartySetsContextConfig());
+-
+-  base::Value policy = base::JSONReader::Read(R"(
+-                {
+-                "replacements": [
+-                  {
+-                    "primary": "https://primary.test",
+-                    "associatedSites": ["https://associated.test"]
+-                  }
+-                ]
+-              }
+-            )")
+-                           .value();
+-  EXPECT_EQ(GetContextConfigForPolicy(&policy.GetDict()),
+-            net::FirstPartySetsContextConfig());
+-
+-  // The local set declaration should be ignored, since the handler is disabled.
+-  handler().Init(
+-      /*user_data_dir=*/{},
+-      LocalSetDeclaration(
+-          R"({"primary": "https://example.test",)"
+-          R"("associatedSites": ["https://associatedsite1.test"]})"));
+-
+-  // The public sets should be ignored, since the handler is disabled.
+-  handler().SetPublicFirstPartySets(
+-      base::Version("0.0.1"),
+-      WritePublicSetsFile(
+-          R"({"primary": "https://example.test", )"
+-          R"("associatedSites": ["https://associatedsite2.test"]})"));
+-
+-  EXPECT_THAT(GetSetsAndWait().FindEntries(
+-                  {
+-                      net::SchemefulSite(GURL("https://example.test")),
+-                      net::SchemefulSite(GURL("https://associatedsite1.test")),
+-                      net::SchemefulSite(GURL("https://associatedsite2.test")),
+-                  },
+-                  net::FirstPartySetsContextConfig()),
+-              IsEmpty());
+-}
+-
+-class FirstPartySetsHandlerImplEnabledTest
+-    : public FirstPartySetsHandlerImplTest {
+- public:
+-  FirstPartySetsHandlerImplEnabledTest()
+-      : FirstPartySetsHandlerImplTest(/*enabled=*/true) {}
+-};
+-
+-TEST_F(FirstPartySetsHandlerImplEnabledTest, EmptyDBPath) {
+-  net::SchemefulSite example(GURL("https://example.test"));
+-  net::SchemefulSite associated(GURL("https://associatedsite1.test"));
+-
+-  handler().SetPublicFirstPartySets(base::Version("0.0.1"),
+-                                    WritePublicSetsFile(""));
+-
+-  // Empty `user_data_dir` will fail to load persisted sets, but that will not
+-  // prevent `on_sets_ready` from being invoked.
+-  handler().Init(
+-      /*user_data_dir=*/{},
+-      LocalSetDeclaration(
+-          R"({"primary": "https://example.test",)"
+-          R"("associatedSites": ["https://associatedsite1.test"]})"));
+-
+-  EXPECT_THAT(
+-      GetSetsAndWait().FindEntries({example, associated},
+-                                   net::FirstPartySetsContextConfig()),
+-      UnorderedElementsAre(
+-          Pair(example, net::FirstPartySetEntry(
+-                            example, net::SiteType::kPrimary, absl::nullopt)),
+-          Pair(associated, net::FirstPartySetEntry(
+-                               example, net::SiteType::kAssociated, 0))));
+-}
+-
+-TEST_F(FirstPartySetsHandlerImplEnabledTest,
+-       ClearSiteDataOnChangedSetsForContext_FeatureNotEnabled) {
+-  base::test::ScopedFeatureList features;
+-  features.InitAndEnableFeatureWithParameters(
+-      features::kFirstPartySets,
+-      {{features::kFirstPartySetsClearSiteDataOnChangedSets.name, "false"}});
+-  base::HistogramTester histogram;
+-  net::SchemefulSite foo(GURL("https://foo.test"));
+-  net::SchemefulSite associated(GURL("https://associatedsite.test"));
+-
+-  const std::string browser_context_id = "profile";
+-  const std::string input =
+-      R"({"primary": "https://foo.test", )"
+-      R"("associatedSites": ["https://associatedsite.test"]})";
+-  ASSERT_TRUE(base::JSONReader::Read(input));
+-  handler().SetPublicFirstPartySets(base::Version("0.0.1"),
+-                                    WritePublicSetsFile(input));
+-
+-  handler().Init(scoped_dir_.GetPath(), LocalSetDeclaration());
+-  ASSERT_THAT(GetSetsAndWait().FindEntries({foo, associated},
+-                                           net::FirstPartySetsContextConfig()),
+-              UnorderedElementsAre(
+-                  Pair(foo, net::FirstPartySetEntry(
+-                                foo, net::SiteType::kPrimary, absl::nullopt)),
+-                  Pair(associated, net::FirstPartySetEntry(
+-                                       foo, net::SiteType::kAssociated, 0))));
+-
+-  histogram.ExpectTotalCount(kDelayedQueriesCountHistogram, 1);
+-  histogram.ExpectTotalCount(kMostDelayedQueryDeltaHistogram, 1);
+-
+-  ClearSiteDataOnChangedSetsForContextAndWait(
+-      context(), browser_context_id, net::FirstPartySetsContextConfig());
+-
+-  absl::optional<
+-      std::pair<net::GlobalFirstPartySets, net::FirstPartySetsContextConfig>>
+-      persisted = GetPersistedSetsAndWait(browser_context_id);
+-  EXPECT_TRUE(persisted.has_value());
+-  EXPECT_THAT(
+-      persisted->first.FindEntries({foo, associated}, persisted->second),
+-      IsEmpty());
+-  // Should not be recorded.
+-  histogram.ExpectTotalCount(kFirstPartySetsClearSiteDataOutcomeHistogram, 0);
+-}
+-
+-TEST_F(FirstPartySetsHandlerImplEnabledTest,
+-       ClearSiteDataOnChangedSetsForContext_ManualSet_Successful) {
+-  base::test::ScopedFeatureList features;
+-  features.InitAndEnableFeatureWithParameters(
+-      features::kFirstPartySets,
+-      {{features::kFirstPartySetsClearSiteDataOnChangedSets.name, "true"}});
+-
+-  net::SchemefulSite foo(GURL("https://foo.test"));
+-  net::SchemefulSite associated(GURL("https://associatedsite.test"));
+-  net::SchemefulSite associated2(GURL("https://associatedsite2.test"));
+-
+-  const std::string browser_context_id = "profile";
+-
+-  base::HistogramTester histogram;
+-  FirstPartySetsHandlerImplInstance handler =
+-      FirstPartySetsHandlerImplInstance::CreateForTesting(true, false);
+-  const std::string input =
+-      R"({"primary": "https://foo.test", )"
+-      R"("associatedSites": ["https://associatedsite.test"]})";
+-  ASSERT_TRUE(base::JSONReader::Read(input));
+-
+-  handler.Init(scoped_dir_.GetPath(), LocalSetDeclaration(input));
+-
+-  // Should not yet be recorded.
+-  histogram.ExpectTotalCount(kFirstPartySetsClearSiteDataOutcomeHistogram, 0);
+-  ClearSiteDataOnChangedSetsForContextAndWait(
+-      handler, context(), browser_context_id,
+-      net::FirstPartySetsContextConfig());
+-
+-  absl::optional<
+-      std::pair<net::GlobalFirstPartySets, net::FirstPartySetsContextConfig>>
+-      persisted = GetPersistedSetsAndWait(handler, browser_context_id);
+-  EXPECT_TRUE(persisted.has_value());
+-  EXPECT_THAT(
+-      persisted->first.FindEntries({foo, associated}, persisted->second),
+-      UnorderedElementsAre(
+-          Pair(foo, net::FirstPartySetEntry(foo, net::SiteType::kPrimary,
+-                                            absl::nullopt)),
+-          Pair(associated,
+-               net::FirstPartySetEntry(foo, net::SiteType::kAssociated,
+-                                       absl::nullopt))));
+-  histogram.ExpectUniqueSample(kFirstPartySetsClearSiteDataOutcomeHistogram,
+-                               /*sample=*/true, 1);
+-  histogram.ExpectTotalCount(kDelayedQueriesCountHistogram, 1);
+-  histogram.ExpectTotalCount(kMostDelayedQueryDeltaHistogram, 1);
+-}
+-
+-TEST_F(FirstPartySetsHandlerImplEnabledTest,
+-       ClearSiteDataOnChangedSetsForContext_PublicSetsWithDiff_Successful) {
+-  base::test::ScopedFeatureList features;
+-  features.InitAndEnableFeatureWithParameters(
+-      features::kFirstPartySets,
+-      {{features::kFirstPartySetsClearSiteDataOnChangedSets.name, "true"}});
+-
+-  net::SchemefulSite foo(GURL("https://foo.test"));
+-  net::SchemefulSite associated(GURL("https://associatedsite.test"));
+-  net::SchemefulSite associated2(GURL("https://associatedsite2.test"));
+-
+-  const std::string browser_context_id = "profile";
+-
+-  {
+-    base::HistogramTester histogram;
+-    FirstPartySetsHandlerImplInstance handler =
+-        FirstPartySetsHandlerImplInstance::CreateForTesting(true, true);
+-    const std::string input =
+-        R"({"primary": "https://foo.test", )"
+-        R"("associatedSites": ["https://associatedsite.test"]})";
+-    ASSERT_TRUE(base::JSONReader::Read(input));
+-    handler.SetPublicFirstPartySets(base::Version("0.0.1"),
+-                                    WritePublicSetsFile(input));
+-
+-    handler.Init(scoped_dir_.GetPath(), LocalSetDeclaration());
+-
+-    EXPECT_THAT(
+-        HasEntryInBrowserContextsClearedAndWait(handler, browser_context_id),
+-        Optional(false));
+-
+-    // Should not yet be recorded.
+-    histogram.ExpectTotalCount(kFirstPartySetsClearSiteDataOutcomeHistogram, 0);
+-    ClearSiteDataOnChangedSetsForContextAndWait(
+-        handler, context(), browser_context_id,
+-        net::FirstPartySetsContextConfig());
+-    absl::optional<
+-        std::pair<net::GlobalFirstPartySets, net::FirstPartySetsContextConfig>>
+-        persisted = GetPersistedSetsAndWait(handler, browser_context_id);
+-    EXPECT_TRUE(persisted.has_value());
+-    EXPECT_THAT(
+-        persisted->first.FindEntries({foo, associated}, persisted->second),
+-        UnorderedElementsAre(
+-            Pair(foo, net::FirstPartySetEntry(foo, net::SiteType::kPrimary,
+-                                              absl::nullopt)),
+-            Pair(associated,
+-                 net::FirstPartySetEntry(foo, net::SiteType::kAssociated,
+-                                         absl::nullopt))));
+-    EXPECT_THAT(
+-        HasEntryInBrowserContextsClearedAndWait(handler, browser_context_id),
+-        Optional(true));
+-
+-    histogram.ExpectUniqueSample(kFirstPartySetsClearSiteDataOutcomeHistogram,
+-                                 /*sample=*/true, 1);
+-
+-    // Make sure the database is closed properly before being opened again.
+-    handler.SynchronouslyResetDBHelperForTesting();
+-  }
+-
+-  // Verify FPS transition clearing is working for non-empty sites-to-clear
+-  // list.
+-  {
+-    base::HistogramTester histogram;
+-    FirstPartySetsHandlerImplInstance handler =
+-        FirstPartySetsHandlerImplInstance::CreateForTesting(true, true);
+-    const std::string input =
+-        R"({"primary": "https://foo.test", )"
+-        R"("associatedSites": ["https://associatedsite2.test"]})";
+-    ASSERT_TRUE(base::JSONReader::Read(input));
+-    // The new public sets need to be associated with a different version.
+-    handler.SetPublicFirstPartySets(base::Version("0.0.2"),
+-                                    WritePublicSetsFile(input));
+-
+-    handler.Init(scoped_dir_.GetPath(), LocalSetDeclaration());
+-
+-    // Should not yet be recorded.
+-    histogram.ExpectTotalCount(kFirstPartySetsClearSiteDataOutcomeHistogram, 0);
+-    ClearSiteDataOnChangedSetsForContextAndWait(
+-        handler, context(), browser_context_id,
+-        net::FirstPartySetsContextConfig());
+-    absl::optional<
+-        std::pair<net::GlobalFirstPartySets, net::FirstPartySetsContextConfig>>
+-        persisted = GetPersistedSetsAndWait(handler, browser_context_id);
+-    EXPECT_TRUE(persisted.has_value());
+-    EXPECT_THAT(
+-        persisted->first.FindEntries({foo, associated2}, persisted->second),
+-        UnorderedElementsAre(
+-            Pair(foo, net::FirstPartySetEntry(foo, net::SiteType::kPrimary,
+-                                              absl::nullopt)),
+-            Pair(associated2,
+-                 net::FirstPartySetEntry(foo, net::SiteType::kAssociated,
+-                                         absl::nullopt))));
+-    EXPECT_THAT(
+-        HasEntryInBrowserContextsClearedAndWait(handler, browser_context_id),
+-        Optional(true));
+-
+-    histogram.ExpectUniqueSample(kFirstPartySetsClearSiteDataOutcomeHistogram,
+-                                 /*sample=*/true, 1);
+-  }
+-}
+-
+-TEST_F(FirstPartySetsHandlerImplEnabledTest,
+-       ClearSiteDataOnChangedSetsForContext_EmptyDBPath) {
+-  base::test::ScopedFeatureList features;
+-  features.InitAndEnableFeatureWithParameters(
+-      features::kFirstPartySets,
+-      {{features::kFirstPartySetsClearSiteDataOnChangedSets.name, "true"}});
+-
+-  base::HistogramTester histogram;
+-  net::SchemefulSite foo(GURL("https://foo.test"));
+-  net::SchemefulSite associated(GURL("https://associatedsite.test"));
+-
+-  const std::string browser_context_id = "profile";
+-  const std::string input =
+-      R"({"primary": "https://foo.test", )"
+-      R"("associatedSites": ["https://associatedsite.test"]})";
+-  ASSERT_TRUE(base::JSONReader::Read(input));
+-  handler().SetPublicFirstPartySets(base::Version("0.0.1"),
+-                                    WritePublicSetsFile(input));
+-
+-  handler().Init(
+-      /*user_data_dir=*/{}, LocalSetDeclaration());
+-  ASSERT_THAT(GetSetsAndWait().FindEntries({foo, associated},
+-                                           net::FirstPartySetsContextConfig()),
+-              UnorderedElementsAre(
+-                  Pair(foo, net::FirstPartySetEntry(
+-                                foo, net::SiteType::kPrimary, absl::nullopt)),
+-                  Pair(associated, net::FirstPartySetEntry(
+-                                       foo, net::SiteType::kAssociated, 0))));
+-
+-  ClearSiteDataOnChangedSetsForContextAndWait(
+-      context(), browser_context_id, net::FirstPartySetsContextConfig());
+-
+-  EXPECT_EQ(GetPersistedSetsAndWait(browser_context_id), absl::nullopt);
+-  // Should not be recorded.
+-  histogram.ExpectTotalCount(kFirstPartySetsClearSiteDataOutcomeHistogram, 0);
+-  histogram.ExpectTotalCount(kDelayedQueriesCountHistogram, 1);
+-  histogram.ExpectTotalCount(kMostDelayedQueryDeltaHistogram, 1);
+-}
+-
+-TEST_F(FirstPartySetsHandlerImplEnabledTest,
+-       ClearSiteDataOnChangedSetsForContext_BeforeSetsReady) {
+-  base::test::ScopedFeatureList features;
+-  features.InitAndEnableFeatureWithParameters(
+-      features::kFirstPartySets,
+-      {{features::kFirstPartySetsClearSiteDataOnChangedSets.name, "true"}});
+-
+-  base::HistogramTester histogram;
+-
+-  handler().Init(scoped_dir_.GetPath(), LocalSetDeclaration());
+-
+-  const std::string browser_context_id = "profile";
+-  base::test::TestFuture<net::FirstPartySetsContextConfig,
+-                         net::FirstPartySetsCacheFilter>
+-      future;
+-  handler().ClearSiteDataOnChangedSetsForContext(
+-      base::BindLambdaForTesting([&]() { return context(); }),
+-      browser_context_id, net::FirstPartySetsContextConfig(),
+-      future.GetCallback());
+-
+-  handler().SetPublicFirstPartySets(
+-      base::Version("0.0.1"),
+-      WritePublicSetsFile(
+-          R"({"primary": "https://foo.test", )"
+-          R"("associatedSites": ["https://associatedsite.test"]})"));
+-
+-  EXPECT_TRUE(future.Wait());
+-
+-  net::SchemefulSite foo(GURL("https://foo.test"));
+-  net::SchemefulSite associated(GURL("https://associatedsite.test"));
+-
+-  absl::optional<
+-      std::pair<net::GlobalFirstPartySets, net::FirstPartySetsContextConfig>>
+-      persisted = GetPersistedSetsAndWait(browser_context_id);
+-  EXPECT_TRUE(persisted.has_value());
+-  EXPECT_THAT(
+-      persisted->first.FindEntries({foo, associated}, persisted->second),
+-      UnorderedElementsAre(
+-          Pair(foo, net::FirstPartySetEntry(foo, net::SiteType::kPrimary,
+-                                            absl::nullopt)),
+-          Pair(associated,
+-               net::FirstPartySetEntry(foo, net::SiteType::kAssociated,
+-                                       absl::nullopt))));
+-  histogram.ExpectUniqueSample(kFirstPartySetsClearSiteDataOutcomeHistogram,
+-                               /*sample=*/true, 1);
+-  histogram.ExpectTotalCount(kDelayedQueriesCountHistogram, 1);
+-  histogram.ExpectTotalCount(kMostDelayedQueryDeltaHistogram, 1);
+-}
+-
+-TEST_F(FirstPartySetsHandlerImplEnabledTest,
+-       GetSetsIfEnabledAndReady_AfterSetsReady) {
+-  net::SchemefulSite example(GURL("https://example.test"));
+-  net::SchemefulSite associated(GURL("https://associatedsite.test"));
+-
+-  const std::string input =
+-      R"({"primary": "https://example.test", )"
+-      R"("associatedSites": ["https://associatedsite.test"]})";
+-  ASSERT_TRUE(base::JSONReader::Read(input));
+-  handler().SetPublicFirstPartySets(base::Version("1.2.3"),
+-                                    WritePublicSetsFile(input));
+-
+-  handler().Init(scoped_dir_.GetPath(), LocalSetDeclaration());
+-
+-  // Wait until initialization is complete.
+-  GetSetsAndWait();
+-
+-  EXPECT_THAT(
+-      handler()
+-          .GetSets(base::NullCallback())
+-          .value()
+-          .FindEntries({example, associated},
+-                       net::FirstPartySetsContextConfig()),
+-      UnorderedElementsAre(
+-          Pair(example, net::FirstPartySetEntry(
+-                            example, net::SiteType::kPrimary, absl::nullopt)),
+-          Pair(associated, net::FirstPartySetEntry(
+-                               example, net::SiteType::kAssociated, 0))));
+-}
+-
+-TEST_F(FirstPartySetsHandlerImplEnabledTest,
+-       GetSetsIfEnabledAndReady_BeforeSetsReady) {
+-  net::SchemefulSite example(GURL("https://example.test"));
+-  net::SchemefulSite associated(GURL("https://associatedsite.test"));
+-
+-  // Call GetSets before the sets are ready, and before Init has been called.
+-  base::test::TestFuture<net::GlobalFirstPartySets> future;
+-  EXPECT_EQ(handler().GetSets(future.GetCallback()), absl::nullopt);
+-
+-  handler().Init(scoped_dir_.GetPath(), LocalSetDeclaration());
+-
+-  const std::string input =
+-      R"({"primary": "https://example.test", )"
+-      R"("associatedSites": ["https://associatedsite.test"]})";
+-  ASSERT_TRUE(base::JSONReader::Read(input));
+-  handler().SetPublicFirstPartySets(base::Version("1.2.3"),
+-                                    WritePublicSetsFile(input));
+-
+-  EXPECT_THAT(
+-      future.Get().FindEntries({example, associated},
+-                               net::FirstPartySetsContextConfig()),
+-      UnorderedElementsAre(
+-          Pair(example, net::FirstPartySetEntry(
+-                            example, net::SiteType::kPrimary, absl::nullopt)),
+-          Pair(associated, net::FirstPartySetEntry(
+-                               example, net::SiteType::kAssociated, 0))));
+-
+-  EXPECT_THAT(
+-      handler()
+-          .GetSets(base::NullCallback())
+-          .value()
+-          .FindEntries({example, associated},
+-                       net::FirstPartySetsContextConfig()),
+-      UnorderedElementsAre(
+-          Pair(example, net::FirstPartySetEntry(
+-                            example, net::SiteType::kPrimary, absl::nullopt)),
+-          Pair(associated, net::FirstPartySetEntry(
+-                               example, net::SiteType::kAssociated, 0))));
+-}
+-
+-TEST_F(FirstPartySetsHandlerImplEnabledTest,
+-       ComputeFirstPartySetMetadata_SynchronousResult) {
+-  handler().Init(scoped_dir_.GetPath(), LocalSetDeclaration());
+-
+-  handler().SetPublicFirstPartySets(
+-      base::Version("1.2.3"),
+-      WritePublicSetsFile(
+-          R"({"primary": "https://example.test", )"
+-          R"("associatedSites": ["https://associatedsite.test"]})"));
+-
+-  // Exploit another helper to wait until the public sets file has been read.
+-  GetSetsAndWait();
+-
+-  net::SchemefulSite example(GURL("https://example.test"));
+-  net::SchemefulSite associated(GURL("https://associatedsite.test"));
+-
+-  base::test::TestFuture<net::FirstPartySetMetadata> future;
+-  handler().ComputeFirstPartySetMetadata(example, &associated,
+-                                         net::FirstPartySetsContextConfig(),
+-                                         future.GetCallback());
+-  EXPECT_TRUE(future.IsReady());
+-  EXPECT_NE(future.Take(), net::FirstPartySetMetadata());
+-}
+-
+-TEST_F(FirstPartySetsHandlerImplEnabledTest,
+-       ComputeFirstPartySetMetadata_AsynchronousResult) {
+-  // Send query before the sets are ready.
+-  base::test::TestFuture<net::FirstPartySetMetadata> future;
+-  net::SchemefulSite example(GURL("https://example.test"));
+-  net::SchemefulSite associated(GURL("https://associatedsite.test"));
+-  handler().ComputeFirstPartySetMetadata(example, &associated,
+-                                         net::FirstPartySetsContextConfig(),
+-                                         future.GetCallback());
+-  EXPECT_FALSE(future.IsReady());
+-
+-  handler().Init(scoped_dir_.GetPath(), LocalSetDeclaration());
+-
+-  handler().SetPublicFirstPartySets(
+-      base::Version("1.2.3"),
+-      WritePublicSetsFile(
+-          R"({"primary": "https://example.test", )"
+-          R"("associatedSites": ["https://associatedsite.test"]})"));
+-
+-  EXPECT_NE(future.Get(), net::FirstPartySetMetadata());
+-}
+-
+-TEST_F(FirstPartySetsHandlerImplEnabledTest,
+-       ForEachEffectiveSetEntry_BeforeSetsReady) {
+-  net::SchemefulSite example(GURL("https://example.test"));
+-  net::SchemefulSite associated(GURL("https://associatedsite.test"));
+-
+-  // Verifies calling ForEachEffectiveSetEntry before the sets are ready returns
+-  // false.
+-  EXPECT_FALSE(handler().ForEachEffectiveSetEntry(
+-      net::FirstPartySetsContextConfig(),
+-      [&](const net::SchemefulSite& site,
+-          const net::FirstPartySetEntry& entry) {
+-        NOTREACHED_NORETURN();
+-        return true;
+-      }));
+-
+-  handler().Init(scoped_dir_.GetPath(), LocalSetDeclaration());
+-
+-  const std::string input =
+-      R"({"primary": "https://example.test", )"
+-      R"("associatedSites": ["https://associatedsite.test"]})";
+-  ASSERT_TRUE(base::JSONReader::Read(input));
+-  handler().SetPublicFirstPartySets(base::Version("1.2.3"),
+-                                    WritePublicSetsFile(input));
+-  // Wait for initialization is done.
+-  GetSetsAndWait();
+-
+-  std::vector<std::pair<net::SchemefulSite, net::FirstPartySetEntry>>
+-      set_entries;
+-  EXPECT_TRUE(handler().ForEachEffectiveSetEntry(
+-      net::FirstPartySetsContextConfig(),
+-      [&](const net::SchemefulSite& site,
+-          const net::FirstPartySetEntry& entry) {
+-        set_entries.emplace_back(site, entry);
+-        return true;
+-      }));
+-  EXPECT_THAT(
+-      set_entries,
+-      UnorderedElementsAre(
+-          Pair(example, net::FirstPartySetEntry(
+-                            example, net::SiteType::kPrimary, absl::nullopt)),
+-          Pair(associated, net::FirstPartySetEntry(
+-                               example, net::SiteType::kAssociated, 0))));
+-}
+-
+-TEST_F(FirstPartySetsHandlerImplEnabledTest,
+-       ForEachEffectiveSetEntry_WithNonEmptyConfig) {
+-  net::SchemefulSite example(GURL("https://example.test"));
+-  net::SchemefulSite associated1(GURL("https://associatedsite1.test"));
+-  net::SchemefulSite associated2(GURL("https://associatedsite2.test"));
+-
+-  handler().Init(scoped_dir_.GetPath(), LocalSetDeclaration());
+-
+-  const std::string input =
+-      R"({"primary": "https://example.test", )"
+-      R"("associatedSites": ["https://associatedsite1.test"]})";
+-  ASSERT_TRUE(base::JSONReader::Read(input));
+-  handler().SetPublicFirstPartySets(base::Version("1.2.3"),
+-                                    WritePublicSetsFile(input));
+-  // Wait for initialization is done.
+-  GetSetsAndWait();
+-
+-  std::vector<std::pair<net::SchemefulSite, net::FirstPartySetEntry>>
+-      set_entries;
+-  // Calling ForEachEffectiveSetEntry with context config which add a new
+-  // associated site https://associatedsite2.test to the above set.
+-  EXPECT_TRUE(handler().ForEachEffectiveSetEntry(
+-      net::FirstPartySetsContextConfig(
+-          {{associated2,
+-            net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+-                example, net::SiteType::kAssociated, absl::nullopt))}}),
+-      [&](const net::SchemefulSite& site,
+-          const net::FirstPartySetEntry& entry) {
+-        set_entries.emplace_back(site, entry);
+-        return true;
+-      }));
+-  EXPECT_THAT(
+-      set_entries,
+-      UnorderedElementsAre(
+-          Pair(example, net::FirstPartySetEntry(
+-                            example, net::SiteType::kPrimary, absl::nullopt)),
+-          Pair(associated1,
+-               net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)),
+-          Pair(associated2,
+-               net::FirstPartySetEntry(example, net::SiteType::kAssociated,
+-                                       absl::nullopt))));
+-}
+-
+-class FirstPartySetsHandlerGetContextConfigForPolicyTest
+-    : public FirstPartySetsHandlerImplEnabledTest {
+- public:
+-  FirstPartySetsHandlerGetContextConfigForPolicyTest() {
+-    handler().Init(scoped_dir_.GetPath(), LocalSetDeclaration());
+-  }
+-
+-  // Writes the public list of First-Party Sets which GetContextConfigForPolicy
+-  // awaits.
+-  //
+-  // Initializes the First-Party Sets with the following relationship:
+-  //
+-  // [
+-  //   {
+-  //     "primary": "https://primary1.test",
+-  //     "associatedSites": ["https://associatedsite1.test",
+-  //     "https://associatedsite2.test"]
+-  //   }
+-  // ]
+-  void InitPublicFirstPartySets() {
+-    net::SchemefulSite primary1(GURL("https://primary1.test"));
+-    net::SchemefulSite associated1(GURL("https://associatedsite1.test"));
+-    net::SchemefulSite associated2(GURL("https://associatedsite2.test"));
+-
+-    const std::string input =
+-        R"({"primary": "https://primary1.test", )"
+-        R"("associatedSites": ["https://associatedsite1.test", "https://associatedsite2.test"]})";
+-    ASSERT_TRUE(base::JSONReader::Read(input));
+-    handler().SetPublicFirstPartySets(base::Version("1.2.3"),
+-                                      WritePublicSetsFile(input));
+-
+-    ASSERT_THAT(
+-        GetSetsAndWait().FindEntries({primary1, associated1, associated2},
+-                                     net::FirstPartySetsContextConfig()),
+-        SizeIs(3));
+-  }
+-};
+-
+-TEST_F(FirstPartySetsHandlerGetContextConfigForPolicyTest,
+-       DefaultOverridesPolicy_DefaultContextConfigs) {
+-  base::Value policy = base::JSONReader::Read(R"({})").value();
+-  base::test::TestFuture<net::FirstPartySetsContextConfig> future;
+-  handler().GetContextConfigForPolicy(&policy.GetDict(), future.GetCallback());
+-
+-  InitPublicFirstPartySets();
+-  EXPECT_EQ(future.Take(), net::FirstPartySetsContextConfig());
+-}
+-
+-TEST_F(FirstPartySetsHandlerGetContextConfigForPolicyTest,
+-       MalformedOverridesPolicy_DefaultContextConfigs) {
+-  base::Value policy = base::JSONReader::Read(R"({
+-    "replacements": 123,
+-    "additions": true
+-  })")
+-                           .value();
+-  base::test::TestFuture<net::FirstPartySetsContextConfig> future;
+-  handler().GetContextConfigForPolicy(&policy.GetDict(), future.GetCallback());
+-
+-  InitPublicFirstPartySets();
+-  EXPECT_EQ(future.Take(), net::FirstPartySetsContextConfig());
+-}
+-
+-TEST_F(FirstPartySetsHandlerGetContextConfigForPolicyTest,
+-       NonDefaultOverridesPolicy_NonDefaultContextConfigs) {
+-  base::Value policy = base::JSONReader::Read(R"(
+-                {
+-                "replacements": [
+-                  {
+-                    "primary": "https://associatedsite1.test",
+-                    "associatedSites": ["https://primary3.test"]
+-                  }
+-                ],
+-                "additions": [
+-                  {
+-                    "primary": "https://primary2.test",
+-                    "associatedSites": ["https://associatedsite2.test"]
+-                  }
+-                ]
+-              }
+-            )")
+-                           .value();
+-  base::test::TestFuture<net::FirstPartySetsContextConfig> future;
+-  handler().GetContextConfigForPolicy(&policy.GetDict(), future.GetCallback());
+-
+-  InitPublicFirstPartySets();
+-  // We don't care what the customizations are, here; we only care that they're
+-  // not a no-op.
+-  EXPECT_FALSE(future.Take().empty());
+-  EXPECT_EQ(GetContextConfigForPolicy(nullptr),
+-            net::FirstPartySetsContextConfig());
+-}
+-
+-}  // namespace content
+diff --git a/content/browser/first_party_sets/first_party_sets_loader.cc b/content/browser/first_party_sets/first_party_sets_loader.cc
+index eecd574ad30bc6e19b33eabbf2609890f72af286..ff8299ab75711411a94b6ecf54a141b1cfd548a0 100644
+--- a/content/browser/first_party_sets/first_party_sets_loader.cc
++++ b/content/browser/first_party_sets/first_party_sets_loader.cc
+@@ -31,6 +31,19 @@ std::string ReadSetsFile(base::File sets_file) {
+   return base::ReadStreamToString(file.get(), &raw_sets) ? raw_sets : "";
+ }
+ 
++void DisposeFile(base::File sets_file) {
++  if (!sets_file.IsValid()) {
++    return;
++  }
++  base::ThreadPool::PostTask(
++      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
++      base::BindOnce(
++          [](base::File sets_file) {
++            // Run `sets_file`'s dtor in the threadpool.
++          },
++          std::move(sets_file)));
++}
++
+ }  // namespace
+ 
+ FirstPartySetsLoader::FirstPartySetsLoader(
+@@ -44,9 +57,6 @@ FirstPartySetsLoader::~FirstPartySetsLoader() {
+ void FirstPartySetsLoader::SetManuallySpecifiedSet(
+     const LocalSetDeclaration& local_set) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+-  if (manually_specified_set_.has_value()) {
+-    return;
+-  }
+   manually_specified_set_ = local_set;
+   UmaHistogramTimes(
+       "Cookie.FirstPartySets.InitializationDuration.ReadCommandLineSet2",
+@@ -79,20 +89,6 @@ void FirstPartySetsLoader::SetComponentSets(base::Version version,
+                      weak_factory_.GetWeakPtr(), std::move(version)));
+ }
+ 
+-// static
+-void FirstPartySetsLoader::DisposeFile(base::File file) {
+-  if (!file.IsValid()) {
+-    return;
+-  }
+-  base::ThreadPool::PostTask(
+-      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+-      base::BindOnce(
+-          [](base::File file) {
+-            // Run `file`'s dtor in the threadpool.
+-          },
+-          std::move(file)));
+-}
+-
+ void FirstPartySetsLoader::OnReadSetsFile(base::Version version,
+                                           const std::string& raw_sets) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+diff --git a/content/browser/first_party_sets/first_party_sets_loader.h b/content/browser/first_party_sets/first_party_sets_loader.h
+index 46b50bb18cec8562427af581f57e525ca48a5f95..0c51c9baa6fc0c43bba60dbb9e5d6df61a615374 100644
+--- a/content/browser/first_party_sets/first_party_sets_loader.h
++++ b/content/browser/first_party_sets/first_party_sets_loader.h
+@@ -35,7 +35,7 @@ class CONTENT_EXPORT FirstPartySetsLoader {
+   FirstPartySetsLoader& operator=(const FirstPartySetsLoader&) = delete;
+ 
+   // Stores the First-Party Set that was provided via the `kUseFirstPartySet`
+-  // flag/switch. Only the first call has any effect.
++  // flag/switch.
+   void SetManuallySpecifiedSet(const LocalSetDeclaration& local_set);
+ 
+   // Asynchronously parses and stores the sets from `sets_file`, and merges with
+@@ -46,9 +46,6 @@ class CONTENT_EXPORT FirstPartySetsLoader {
+   // invocations are ignored.
+   void SetComponentSets(base::Version version, base::File sets_file);
+ 
+-  // Closes the given file safely.
+-  static void DisposeFile(base::File file);
+-
+  private:
+   // Parses the contents of `raw_sets` as a collection of First-Party Set
+   // declarations, and stores the result.
+diff --git a/content/browser/first_party_sets/first_party_sets_loader_unittest.cc b/content/browser/first_party_sets/first_party_sets_loader_unittest.cc
+index dd86cf08266ba537f2443a0188d516419d35d01c..51b9c6b7b82a84bab0a7945caaadd47f697dcd5b 100644
+--- a/content/browser/first_party_sets/first_party_sets_loader_unittest.cc
++++ b/content/browser/first_party_sets/first_party_sets_loader_unittest.cc
+@@ -162,30 +162,4 @@ TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified) {
+                   net::SiteType::kAssociated, 0)));
+ }
+ 
+-TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified_Idempotent) {
+-  loader().SetManuallySpecifiedSet(LocalSetDeclaration(
+-      R"({"primary": "https://bar.test",)"
+-      R"("associatedSites": ["https://associatedsite1.test"]})"));
+-
+-  // All but the first SetManuallySpecifiedSet call should be ignored.
+-  loader().SetManuallySpecifiedSet(LocalSetDeclaration(
+-      R"({"primary": "https://bar.test",)"
+-      R"("associatedSites": ["https://associatedsite2.test"]})"));
+-
+-  SetComponentSets(loader(), base::Version(), "");
+-
+-  const net::SchemefulSite associated1(GURL("https://associatedsite1.test"));
+-
+-  EXPECT_THAT(WaitAndGetResult().FindEntries(
+-                  {
+-                      associated1,
+-                      net::SchemefulSite(GURL("https://associatedsite2.test")),
+-                  },
+-                  net::FirstPartySetsContextConfig()),
+-              UnorderedElementsAre(Pair(
+-                  associated1, net::FirstPartySetEntry(
+-                                   net::SchemefulSite(GURL("https://bar.test")),
+-                                   net::SiteType::kAssociated, 0))));
+-}
+-
+ }  // namespace content
+diff --git a/content/browser/media/android/media_resource_getter_impl.cc b/content/browser/media/android/media_resource_getter_impl.cc
+index d073eaef83888439114d5a6a110c74a3e4f4edc3..738c975dad138ad299cb74409d936d3e6cb1bdb3 100644
+--- a/content/browser/media/android/media_resource_getter_impl.cc
++++ b/content/browser/media/android/media_resource_getter_impl.cc
+@@ -61,7 +61,7 @@ GetRestrictedCookieManagerForContext(
+          site_for_cookies.IsFirstParty(top_frame_origin.GetURL()));
+   net::IsolationInfo isolation_info = net::IsolationInfo::Create(
+       net::IsolationInfo::RequestType::kOther, top_frame_origin,
+-      top_frame_origin, site_for_cookies);
++      top_frame_origin, site_for_cookies, absl::nullopt);
+ 
+   mojo::PendingRemote<network::mojom::RestrictedCookieManager> pipe;
+   static_cast<StoragePartitionImpl*>(storage_partition)
+diff --git a/content/browser/renderer_host/cookie_utils.cc b/content/browser/renderer_host/cookie_utils.cc
+index 5238480fd221e6fe2152bbe363bf0686a7dabd85..7cb17350869a2f93f22f1630a0fd253a59b4b424 100644
+--- a/content/browser/renderer_host/cookie_utils.cc
++++ b/content/browser/renderer_host/cookie_utils.cc
+@@ -178,6 +178,8 @@ void RecordSchemefulContextDowngradeUKM(
+ bool ShouldReportDevToolsIssueForStatus(
+     const net::CookieInclusionStatus& status) {
+   return status.ShouldWarn() ||
++         status.HasExclusionReason(
++             net::CookieInclusionStatus::EXCLUDE_INVALID_SAMEPARTY) ||
+          status.HasExclusionReason(
+              net::CookieInclusionStatus::EXCLUDE_DOMAIN_NON_ASCII) ||
+          status.HasExclusionReason(
+@@ -203,6 +205,10 @@ void EmitCookieWarningsAndMetricsOnce(
+   bool breaking_context_downgrade = false;
+   bool lax_allow_unsafe_cookies = false;
+ 
++  bool same_party = false;
++  bool same_party_exclusion_overruled_samesite = false;
++  bool same_party_inclusion_overruled_samesite = false;
++
+   bool samesite_cookie_inclusion_changed_by_cross_site_redirect = false;
+ 
+   bool partitioned_cookies_exist = false;
+@@ -247,6 +253,22 @@ void EmitCookieWarningsAndMetricsOnce(
+               net::CookieInclusionStatus::
+                   WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE);
+ 
++      same_party = same_party ||
++                   status.HasWarningReason(
++                       net::CookieInclusionStatus::WARN_TREATED_AS_SAMEPARTY);
++
++      same_party_exclusion_overruled_samesite =
++          same_party_exclusion_overruled_samesite ||
++          status.HasWarningReason(
++              net::CookieInclusionStatus::
++                  WARN_SAMEPARTY_EXCLUSION_OVERRULED_SAMESITE);
++
++      same_party_inclusion_overruled_samesite =
++          same_party_inclusion_overruled_samesite ||
++          status.HasWarningReason(
++              net::CookieInclusionStatus::
++                  WARN_SAMEPARTY_INCLUSION_OVERRULED_SAMESITE);
++
+       samesite_cookie_inclusion_changed_by_cross_site_redirect =
+           samesite_cookie_inclusion_changed_by_cross_site_redirect ||
+           status.HasWarningReason(
+@@ -346,6 +368,23 @@ void EmitCookieWarningsAndMetricsOnce(
+         rfh, blink::mojom::WebFeature::kLaxAllowingUnsafeCookies);
+   }
+ 
++  if (same_party) {
++    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
++        rfh, blink::mojom::WebFeature::kSamePartyCookieAttribute);
++  }
++
++  if (same_party_exclusion_overruled_samesite) {
++    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
++        rfh,
++        blink::mojom::WebFeature::kSamePartyCookieExclusionOverruledSameSite);
++  }
++
++  if (same_party_inclusion_overruled_samesite) {
++    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
++        rfh,
++        blink::mojom::WebFeature::kSamePartyCookieInclusionOverruledSameSite);
++  }
++
+   if (samesite_cookie_inclusion_changed_by_cross_site_redirect) {
+     GetContentClient()->browser()->LogWebFeatureForCurrentPage(
+         rfh, blink::mojom::WebFeature::
+diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
+index fb9671b69b3a2c4d9270f9f2d70e502ed11be5b6..4063b7f413dd85cae4c2d48b88f246c87499b682 100644
+--- a/content/browser/renderer_host/render_frame_host_impl.cc
++++ b/content/browser/renderer_host/render_frame_host_impl.cc
+@@ -4123,7 +4123,10 @@ net::IsolationInfo RenderFrameHostImpl::ComputeIsolationInfoInternal(
+ 
+   net::SiteForCookies candidate_site_for_cookies(top_frame_site);
+ 
+-  // Walk up the frame tree to check SiteForCookies.
++  std::set<net::SchemefulSite> party_context;
++
++  // Walk up the frame tree to check SiteForCookies and compute the
++  // |party_context|.
+   //
+   // If |request_type| is kOther, then IsolationInfo is being computed
+   // for subresource requests. Check/compute starting from the frame itself.
+@@ -4141,6 +4144,9 @@ net::IsolationInfo RenderFrameHostImpl::ComputeIsolationInfoInternal(
+         rfh == this ? frame_origin : rfh->last_committed_origin_;
+     net::SchemefulSite cur_site = net::SchemefulSite(cur_origin);
+ 
++    if (top_frame_site != cur_site) {
++      party_context.insert(cur_site);
++    }
+     candidate_site_for_cookies.CompareWithFrameTreeSiteAndRevise(cur_site);
+   }
+ 
+@@ -4158,7 +4164,7 @@ net::IsolationInfo RenderFrameHostImpl::ComputeIsolationInfoInternal(
+       ComputeNonce(is_credentialless, fenced_frame_nonce_for_navigation);
+   return net::IsolationInfo::Create(request_type, top_frame_origin,
+                                     frame_origin, candidate_site_for_cookies,
+-                                    nonce);
++                                    std::move(party_context), nonce);
+ }
+ 
+ absl::optional<base::UnguessableToken> RenderFrameHostImpl::ComputeNonce(
+diff --git a/content/browser/service_worker/service_worker_main_resource_loader_interceptor.cc b/content/browser/service_worker/service_worker_main_resource_loader_interceptor.cc
+index c526f29785e3523cb173457223a2fecc494cb5df..e6707d9c23466f943abbfa54a9e50c9b05e142c2 100644
+--- a/content/browser/service_worker/service_worker_main_resource_loader_interceptor.cc
++++ b/content/browser/service_worker/service_worker_main_resource_loader_interceptor.cc
+@@ -217,7 +217,7 @@ void ServiceWorkerMainResourceLoaderInterceptor::MaybeCreateLoader(
+   isolation_info_ = net::IsolationInfo::Create(
+       isolation_info_.request_type(),
+       isolation_info_.top_frame_origin().value(), new_origin,
+-      new_site_for_cookies, isolation_info_.nonce());
++      new_site_for_cookies, absl::nullopt, isolation_info_.nonce());
+ 
+   // Attempt to get the storage key from |RenderFrameHostImpl|. This correctly
+   // accounts for extension URLs. The absence of this logic was a potential
+diff --git a/content/browser/worker_host/shared_worker_host.cc b/content/browser/worker_host/shared_worker_host.cc
+index 1700732bf1a9becc6b4d9613250fccb26403e217..2919285638cf5f46f6d22587fc36e47a06f0fea3 100644
+--- a/content/browser/worker_host/shared_worker_host.cc
++++ b/content/browser/worker_host/shared_worker_host.cc
+@@ -426,7 +426,14 @@ SharedWorkerHost::CreateNetworkFactoryParamsForSubresources() {
+   }
+   network::mojom::URLLoaderFactoryParamsPtr factory_params =
+       URLLoaderFactoryParamsHelper::CreateForWorker(
+-          GetProcessHost(), origin, GetStorageKey().ToPartialNetIsolationInfo(),
++          GetProcessHost(), origin,
++          net::IsolationInfo::Create(
++              net::IsolationInfo::RequestType::kOther,
++              // TODO(https://crbug.com/1147281): We
++              // should pass the top_level_site from
++              // `GetStorageKey()` instead.
++              origin, origin, net::SiteForCookies::FromOrigin(origin),
++              /*party_context=*/absl::nullopt, GetStorageKey().nonce()),
+           std::move(coep_reporter),
+           /*url_loader_network_observer=*/mojo::NullRemote(),
+           /*devtools_observer=*/mojo::NullRemote(),
+diff --git a/content/browser/worker_host/shared_worker_service_impl.cc b/content/browser/worker_host/shared_worker_service_impl.cc
+index 4aa539bce808abef08959f547d02d3aed53959bb..0ebc898a7e94dac56bf1b580e755be6c92b150f5 100644
+--- a/content/browser/worker_host/shared_worker_service_impl.cc
++++ b/content/browser/worker_host/shared_worker_service_impl.cc
+@@ -370,7 +370,11 @@ SharedWorkerHost* SharedWorkerServiceImpl::CreateWorker(
+       worker_process_host->GetID(), host->token(), host->instance().url(),
+       &creator, &creator, host->instance().storage_key().ToNetSiteForCookies(),
+       host->instance().storage_key().origin(), host->instance().storage_key(),
+-      host->instance().storage_key().ToPartialNetIsolationInfo(),
++      net::IsolationInfo::Create(net::IsolationInfo::RequestType::kOther,
++                                 worker_origin, worker_origin,
++                                 net::SiteForCookies::FromOrigin(worker_origin),
++                                 /*party_context=*/absl::nullopt,
++                                 host->instance().storage_key().nonce()),
+       creator.BuildClientSecurityStateForWorkers(), credentials_mode,
+       std::move(outside_fetch_client_settings_object),
+       network::mojom::RequestDestination::kSharedWorker,
+diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
+index f09599bb9723418794db23ece510a15008dd0477..63157c9823e6ee00eeff28211c53a5d99b6759e1 100644
+--- a/content/public/browser/content_browser_client.h
++++ b/content/public/browser/content_browser_client.h
+@@ -1905,7 +1905,8 @@ class CONTENT_EXPORT ContentBrowserClient {
+   // |site_for_cookies| is empty, no domains are first-party).
+   // |top_frame_origin| held by |isolation_info| represents the domain for
+   // top-level frame, and can be used to look up preferences that are dependent
+-  // on that.
++  // on that. |party_context| hold by |isolation_info| is for the purposes of
++  // SameParty cookies inclusion calculation.
+   //
+   // |*receiver| is always valid upon entry.
+   //
+@@ -2715,10 +2716,6 @@ class CONTENT_EXPORT ContentBrowserClient {
+   virtual bool ShouldAllowBackForwardCacheForCacheControlNoStorePage(
+       content::BrowserContext* browser_context);
+ 
+-  // Set whether the browser is running in minimal mode (where most subsystems
+-  // are left uninitialized).
+-  virtual void SetIsMinimalMode(bool minimal) {}
+-
+ #if !BUILDFLAG(IS_ANDROID)
+   // Allows the embedder to correlate backend media services with profile-keyed
+   // effect settings.
+diff --git a/content/public/browser/first_party_sets_handler.h b/content/public/browser/first_party_sets_handler.h
+index 55dbe0afcfae7df771f17dc5ce56efd096a083a3..3e531dd05fc2268c93cb63f8f0bbaeb36e2371e2 100644
+--- a/content/public/browser/first_party_sets_handler.h
++++ b/content/public/browser/first_party_sets_handler.h
+@@ -197,6 +197,7 @@ class CONTENT_EXPORT FirstPartySetsHandler {
+   virtual void ComputeFirstPartySetMetadata(
+       const net::SchemefulSite& site,
+       const net::SchemefulSite* top_frame_site,
++      const std::set<net::SchemefulSite>& party_context,
+       const net::FirstPartySetsContextConfig& config,
+       base::OnceCallback<void(net::FirstPartySetMetadata)> callback) = 0;
+ 
+diff --git a/net/BUILD.gn b/net/BUILD.gn
+index e745a74d0446ea55c9e22cd7628ae0d6ac8a3982..eeff6d53e392f9ba90521ebcc94f7162cf53a7fa 100644
+--- a/net/BUILD.gn
++++ b/net/BUILD.gn
+@@ -584,6 +584,8 @@ component("net") {
+     "first_party_sets/first_party_sets_context_config.h",
+     "first_party_sets/global_first_party_sets.cc",
+     "first_party_sets/global_first_party_sets.h",
++    "first_party_sets/same_party_context.cc",
++    "first_party_sets/same_party_context.h",
+     "http/alternative_service.cc",
+     "http/alternative_service.h",
+     "http/bidirectional_stream.cc",
+diff --git a/net/base/features.cc b/net/base/features.cc
+index 3053df9162e31c7abeadb6e2f0b634ddda148355..159f91fc38aad31742add00ea5b1e16a0811911c 100644
+--- a/net/base/features.cc
++++ b/net/base/features.cc
+@@ -243,6 +243,10 @@ BASE_FEATURE(kCookieSameSiteConsidersRedirectChain,
+              "CookieSameSiteConsidersRedirectChain",
+              base::FEATURE_DISABLED_BY_DEFAULT);
+ 
++BASE_FEATURE(kSamePartyAttributeEnabled,
++             "SamePartyAttributeEnabled",
++             base::FEATURE_DISABLED_BY_DEFAULT);
++
+ BASE_FEATURE(kWaitForFirstPartySetsInit,
+              "WaitForFirstPartySetsInit",
+              base::FEATURE_DISABLED_BY_DEFAULT);
+diff --git a/net/base/features.h b/net/base/features.h
+index af1b135ef9bf4a0dae6292a4d952187f7c345f70..a951f3a27046fa3737e28eddfe0189b6b82a4aca 100644
+--- a/net/base/features.h
++++ b/net/base/features.h
+@@ -297,6 +297,12 @@ NET_EXPORT BASE_DECLARE_FEATURE(kUdpSocketPosixAlwaysUpdateBytesReceived);
+ // See spec changes in https://github.com/httpwg/http-extensions/pull/1348
+ NET_EXPORT BASE_DECLARE_FEATURE(kCookieSameSiteConsidersRedirectChain);
+ 
++// When this feature is enabled, the SameParty attribute is enabled. (Note that
++// when this feature is disabled, the SameParty attribute is still parsed and
++// saved for cookie-sets, but it has no associated semantics (when setting or
++// reading cookies).)
++NET_EXPORT BASE_DECLARE_FEATURE(kSamePartyAttributeEnabled);
++
+ // When this feature is enabled, the network service will wait until First-Party
+ // Sets are initialized before issuing requests that use the HTTP cache or
+ // cookies.
+diff --git a/net/base/isolation_info.cc b/net/base/isolation_info.cc
+index e337af60aa40bd31dd8d7549d963719278298c37..aac71c1c5d8c5892c98ee327198f0276b03e66e5 100644
+--- a/net/base/isolation_info.cc
++++ b/net/base/isolation_info.cc
+@@ -48,11 +48,13 @@ bool IsConsistent(IsolationInfo::RequestType request_type,
+                   const absl::optional<url::Origin>& top_frame_origin,
+                   const absl::optional<url::Origin>& frame_origin,
+                   const SiteForCookies& site_for_cookies,
++                  absl::optional<std::set<SchemefulSite>> party_context,
+                   const absl::optional<base::UnguessableToken>& nonce) {
+   // Check for the default-constructed case.
+   if (!top_frame_origin) {
+     return request_type == IsolationInfo::RequestType::kOther &&
+-           !frame_origin && !nonce && site_for_cookies.IsNull();
++           !frame_origin && !nonce && site_for_cookies.IsNull() &&
++           !party_context;
+   }
+ 
+   // As long as there is a |top_frame_origin|, |site_for_cookies| must be
+@@ -74,6 +76,9 @@ bool IsConsistent(IsolationInfo::RequestType request_type,
+       //
+       // TODO(https://crbug.com/1060631): Once CreatePartial() is removed,
+       // check if SiteForCookies is non-null if the scheme is HTTP or HTTPS.
++      //
++      // TODO(https://crbug.com/1151947): Once CreatePartial() is removed,
++      // check if party_context is non-null and empty.
+       break;
+     case IsolationInfo::RequestType::kSubFrame:
+       // For subframe navigations, the subframe's origin may not be consistent
+@@ -95,7 +100,8 @@ IsolationInfo::IsolationInfo()
+                     /*top_frame_origin=*/absl::nullopt,
+                     /*frame_origin=*/absl::nullopt,
+                     SiteForCookies(),
+-                    /*nonce=*/absl::nullopt) {}
++                    /*nonce=*/absl::nullopt,
++                    /*party_context=*/absl::nullopt) {}
+ 
+ IsolationInfo::IsolationInfo(const IsolationInfo&) = default;
+ IsolationInfo::IsolationInfo(IsolationInfo&&) = default;
+@@ -107,13 +113,15 @@ IsolationInfo IsolationInfo::CreateForInternalRequest(
+     const url::Origin& top_frame_origin) {
+   return IsolationInfo(RequestType::kOther, top_frame_origin, top_frame_origin,
+                        SiteForCookies::FromOrigin(top_frame_origin),
+-                       /*nonce=*/absl::nullopt);
++                       /*nonce=*/absl::nullopt,
++                       /*party_context=*/std::set<SchemefulSite>());
+ }
+ 
+ IsolationInfo IsolationInfo::CreateTransient() {
+   url::Origin opaque_origin;
+   return IsolationInfo(RequestType::kOther, opaque_origin, opaque_origin,
+-                       SiteForCookies(), /*nonce=*/absl::nullopt);
++                       SiteForCookies(), /*nonce=*/absl::nullopt,
++                       /*party_context=*/absl::nullopt);
+ }
+ 
+ absl::optional<IsolationInfo> IsolationInfo::Deserialize(
+@@ -130,11 +138,19 @@ absl::optional<IsolationInfo> IsolationInfo::Deserialize(
+   if (proto.has_frame_origin())
+     frame_origin = url::Origin::Create(GURL(proto.frame_origin()));
+ 
++  absl::optional<std::set<SchemefulSite>> party_context;
++  if (proto.has_party_context()) {
++    party_context = std::set<SchemefulSite>();
++    for (const auto& site : proto.party_context().site()) {
++      party_context->insert(SchemefulSite::Deserialize(site));
++    }
++  }
++
+   return IsolationInfo::CreateIfConsistent(
+       static_cast<RequestType>(proto.request_type()),
+       std::move(top_frame_origin), std::move(frame_origin),
+       SiteForCookies::FromUrl(GURL(proto.site_for_cookies())),
+-      /*nonce=*/absl::nullopt);
++      std::move(party_context), /*nonce=*/absl::nullopt);
+ }
+ 
+ IsolationInfo IsolationInfo::Create(
+@@ -142,9 +158,10 @@ IsolationInfo IsolationInfo::Create(
+     const url::Origin& top_frame_origin,
+     const url::Origin& frame_origin,
+     const SiteForCookies& site_for_cookies,
++    absl::optional<std::set<SchemefulSite>> party_context,
+     const absl::optional<base::UnguessableToken>& nonce) {
+   return IsolationInfo(request_type, top_frame_origin, frame_origin,
+-                       site_for_cookies, nonce);
++                       site_for_cookies, nonce, std::move(party_context));
+ }
+ 
+ IsolationInfo IsolationInfo::DoNotUseCreatePartialFromNak(
+@@ -173,7 +190,8 @@ IsolationInfo IsolationInfo::DoNotUseCreatePartialFromNak(
+ 
+   auto isolation_info = IsolationInfo::Create(
+       IsolationInfo::RequestType::kOther, top_frame_origin,
+-      frame_origin.value(), SiteForCookies(), nonce);
++      frame_origin.value(), SiteForCookies(),
++      /*party_context=*/absl::nullopt, nonce);
+   // TODO(crbug/1343856): DCHECK isolation info is fully populated.
+   return isolation_info;
+ }
+@@ -183,13 +201,14 @@ absl::optional<IsolationInfo> IsolationInfo::CreateIfConsistent(
+     const absl::optional<url::Origin>& top_frame_origin,
+     const absl::optional<url::Origin>& frame_origin,
+     const SiteForCookies& site_for_cookies,
++    absl::optional<std::set<SchemefulSite>> party_context,
+     const absl::optional<base::UnguessableToken>& nonce) {
+   if (!IsConsistent(request_type, top_frame_origin, frame_origin,
+-                    site_for_cookies, nonce)) {
++                    site_for_cookies, party_context, nonce)) {
+     return absl::nullopt;
+   }
+   return IsolationInfo(request_type, top_frame_origin, frame_origin,
+-                       site_for_cookies, nonce);
++                       site_for_cookies, nonce, std::move(party_context));
+ }
+ 
+ IsolationInfo IsolationInfo::CreateForRedirect(
+@@ -199,12 +218,14 @@ IsolationInfo IsolationInfo::CreateForRedirect(
+ 
+   if (request_type_ == RequestType::kSubFrame) {
+     return IsolationInfo(request_type_, top_frame_origin_, new_origin,
+-                         site_for_cookies_, nonce_);
++                         site_for_cookies_, nonce_, party_context_);
+   }
+ 
+   DCHECK_EQ(RequestType::kMainFrame, request_type_);
++  DCHECK(!party_context_ || party_context_->empty());
+   return IsolationInfo(request_type_, new_origin, new_origin,
+-                       SiteForCookies::FromOrigin(new_origin), nonce_);
++                       SiteForCookies::FromOrigin(new_origin), nonce_,
++                       party_context_);
+ }
+ 
+ const absl::optional<url::Origin>& IsolationInfo::frame_origin() const {
+@@ -223,7 +244,8 @@ bool IsolationInfo::IsEqualForTesting(const IsolationInfo& other) const {
+           network_isolation_key_ == other.network_isolation_key_ &&
+           network_anonymization_key_ == other.network_anonymization_key_ &&
+           nonce_ == other.nonce_ &&
+-          site_for_cookies_.IsEquivalent(other.site_for_cookies_));
++          site_for_cookies_.IsEquivalent(other.site_for_cookies_) &&
++          party_context_ == other.party_context_);
+ }
+ 
+ std::string IsolationInfo::Serialize() const {
+@@ -242,6 +264,13 @@ std::string IsolationInfo::Serialize() const {
+ 
+   info.set_site_for_cookies(site_for_cookies_.RepresentativeUrl().spec());
+ 
++  if (party_context_) {
++    auto* pc = info.mutable_party_context();
++    for (const auto& site : *party_context_) {
++      pc->add_site(site.Serialize());
++    }
++  }
++
+   return info.SerializeAsString();
+ }
+ 
+@@ -280,6 +309,18 @@ std::string IsolationInfo::DebugString() const {
+   s += "; network_isolation_key: ";
+   s += network_isolation_key_.ToDebugString();
+ 
++  s += "; party_context: ";
++  if (party_context_) {
++    s += "{";
++    for (auto& site : party_context_.value()) {
++      s += site.GetDebugString();
++      s += ", ";
++    }
++    s += "}";
++  } else {
++    s += "(none)";
++  }
++
+   s += "; nonce: ";
+   if (nonce_) {
+     s += nonce_.value().ToString();
+@@ -313,7 +354,8 @@ IsolationInfo::IsolationInfo(
+     const absl::optional<url::Origin>& top_frame_origin,
+     const absl::optional<url::Origin>& frame_origin,
+     const SiteForCookies& site_for_cookies,
+-    const absl::optional<base::UnguessableToken>& nonce)
++    const absl::optional<base::UnguessableToken>& nonce,
++    absl::optional<std::set<SchemefulSite>> party_context)
+     : request_type_(request_type),
+       top_frame_origin_(top_frame_origin),
+       frame_origin_(frame_origin),
+@@ -328,9 +370,13 @@ IsolationInfo::IsolationInfo(
+                                                         frame_origin,
+                                                         nonce)),
+       site_for_cookies_(site_for_cookies),
+-      nonce_(nonce) {
++      nonce_(nonce),
++      party_context_(party_context.has_value() &&
++                             party_context->size() > kPartyContextMaxSize
++                         ? absl::nullopt
++                         : party_context) {
+   DCHECK(IsConsistent(request_type_, top_frame_origin_, frame_origin_,
+-                      site_for_cookies_, nonce));
++                      site_for_cookies_, party_context_, nonce));
+ }
+ 
+ }  // namespace net
+diff --git a/net/base/isolation_info.h b/net/base/isolation_info.h
+index 8de58d607dcc6835027bc8f0295a2f5cc783e5f4..dc45e9354fe024609dc65e84b3c4e8b2cb53b500 100644
+--- a/net/base/isolation_info.h
++++ b/net/base/isolation_info.h
+@@ -67,8 +67,11 @@ class NET_EXPORT IsolationInfo {
+     kOther,
+   };
+ 
++  // Bound the party_context size with a reasonable number.
++  static constexpr size_t kPartyContextMaxSize = 20;
++
+   // Default constructor returns an IsolationInfo with empty origins, a null
+-  // SiteForCookies(), and a RequestType of kOther.
++  // SiteForCookies(), null |party_context|, and a RequestType of kOther.
+   IsolationInfo();
+   IsolationInfo(const IsolationInfo&);
+   IsolationInfo(IsolationInfo&&);
+@@ -80,7 +83,7 @@ class NET_EXPORT IsolationInfo {
+   // Simple constructor for internal requests. Sets |frame_origin| and
+   // |site_for_cookies| match |top_frame_origin|. Sets |request_type| to
+   // kOther. Will only send SameSite cookies to the site associated with
+-  // the passed in origin.
++  // the passed in origin. |party_context| is set to be an empty set.
+   static IsolationInfo CreateForInternalRequest(
+       const url::Origin& top_frame_origin);
+ 
+@@ -105,6 +108,7 @@ class NET_EXPORT IsolationInfo {
+   // * If |request_type| is kOther, |top_frame_origin| and
+   //   |frame_origin| must be first party with respect to |site_for_cookies|, or
+   //   |site_for_cookies| must be null.
++  // * If |party_context| is not empty, |top_frame_origin| must not be null.
+   // * If |nonce| is specified, then |top_frame_origin| must not be null.
+   //
+   // Note that the |site_for_cookies| consistency checks are skipped when
+@@ -114,6 +118,7 @@ class NET_EXPORT IsolationInfo {
+       const url::Origin& top_frame_origin,
+       const url::Origin& frame_origin,
+       const SiteForCookies& site_for_cookies,
++      absl::optional<std::set<SchemefulSite>> party_context = absl::nullopt,
+       const absl::optional<base::UnguessableToken>& nonce = absl::nullopt);
+ 
+   // TODO(crbug/1372769): Remove this and create a safer way to ensure NIKs
+@@ -132,6 +137,7 @@ class NET_EXPORT IsolationInfo {
+       const absl::optional<url::Origin>& top_frame_origin,
+       const absl::optional<url::Origin>& frame_origin,
+       const SiteForCookies& site_for_cookies,
++      absl::optional<std::set<SchemefulSite>> party_context = absl::nullopt,
+       const absl::optional<base::UnguessableToken>& nonce = absl::nullopt);
+ 
+   // Create a new IsolationInfo for a redirect to the supplied origin. |this| is
+@@ -174,6 +180,15 @@ class NET_EXPORT IsolationInfo {
+   // Do not use outside of testing. Returns the `frame_origin_`.
+   const absl::optional<url::Origin>& frame_origin_for_testing() const;
+ 
++  // Return |party_context| which exclude the top frame origin and the frame
++  // origin.
++  // TODO(mmenke): Make this function PartyContextForTesting() after switching
++  // RenderFrameHostImpl to use the parent IsolationInfo to create the child
++  // IsolationInfo instead of walking through all parent frames.
++  const absl::optional<std::set<SchemefulSite>>& party_context() const {
++    return party_context_;
++  }
++
+   bool IsEqualForTesting(const IsolationInfo& other) const;
+ 
+   NetworkAnonymizationKey CreateNetworkAnonymizationKeyForIsolationInfo(
+@@ -192,7 +207,8 @@ class NET_EXPORT IsolationInfo {
+                 const absl::optional<url::Origin>& top_frame_origin,
+                 const absl::optional<url::Origin>& frame_origin,
+                 const SiteForCookies& site_for_cookies,
+-                const absl::optional<base::UnguessableToken>& nonce);
++                const absl::optional<base::UnguessableToken>& nonce,
++                absl::optional<std::set<SchemefulSite>> party_context);
+ 
+   RequestType request_type_;
+ 
+@@ -211,7 +227,26 @@ class NET_EXPORT IsolationInfo {
+   // for non-opaque origins.
+   absl::optional<base::UnguessableToken> nonce_;
+ 
+-  // Mojo serialization code needs to access internal fields.
++  // This will hold the list of distinct sites in the form of SchemefulSite to
++  // be used for First-Party-Sets check.
++  //
++  // For |request_type_| being either RequestType::kMainFrame or
++  // RequestType::kSubFrame, |party_context| holds the set of the sites
++  // of the frames in between the current frame and the top frame (i.e. not
++  // considering the current frame or the top frame).
++  //
++  // For |request_type_| being RequestType::kOther, |party_context_| holds the
++  // above, and also the site of the current frame.
++  //
++  // Note that if an intermediate frame shares a site with the top frame, that
++  // frame's site is not reflected in the |party_context_|. Also note that if an
++  // intermediate frame shares a site with the current frame, that frame's site
++  // is still included in the set. The top frame's site is excluded because it
++  // is redundant with the |top_frame_origin_| field. The current frame is
++  // excluded to make it easier to update on subframe redirects.
++  absl::optional<std::set<SchemefulSite>> party_context_;
++
++  // Mojo serialization code needs to access internal party_context_ field.
+   friend struct mojo::StructTraits<network::mojom::IsolationInfoDataView,
+                                    IsolationInfo>;
+ };
+diff --git a/net/base/isolation_info.proto b/net/base/isolation_info.proto
+index 249f7bf414d44c0345650f72ffdfe99414073a2a..9a769ab079c0f2d632fcc46527d5d1b9b7838fad 100644
+--- a/net/base/isolation_info.proto
++++ b/net/base/isolation_info.proto
+@@ -14,6 +14,6 @@ message IsolationInfo {
+   optional string frame_origin = 3;
+   optional string site_for_cookies = 4;
+ 
+-  reserved 5;
+-  reserved "party_context";
++  message PartyContext { repeated string site = 1; }
++  optional PartyContext party_context = 5;
+ }
+\ No newline at end of file
+diff --git a/net/base/network_delegate.cc b/net/base/network_delegate.cc
+index 653f2acf2d53fdc45944271972c6a42cf69329f4..7b08eecb6d4a00797fc2b11ecb7b7d9fee2dbd1c 100644
+--- a/net/base/network_delegate.cc
++++ b/net/base/network_delegate.cc
+@@ -165,6 +165,16 @@ bool NetworkDelegate::CanUseReportingClient(const url::Origin& origin,
+   return OnCanUseReportingClient(origin, endpoint);
+ }
+ 
++absl::optional<FirstPartySetsCacheFilter::MatchInfo>
++NetworkDelegate::GetFirstPartySetsCacheFilterMatchInfoMaybeAsync(
++    const SchemefulSite& request_site,
++    base::OnceCallback<void(FirstPartySetsCacheFilter::MatchInfo)> callback)
++    const {
++  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
++  return OnGetFirstPartySetsCacheFilterMatchInfoMaybeAsync(request_site,
++                                                           std::move(callback));
++}
++
+ // static
+ void NetworkDelegate::ExcludeAllCookies(
+     net::CookieInclusionStatus::ExclusionReason reason,
+diff --git a/net/base/network_delegate.h b/net/base/network_delegate.h
+index c9c6cf72a5303f7d2848e5e09aee2c38cc3c2db4..ba2df571f61556dc1239d557fa7fafcaa6ea110d 100644
+--- a/net/base/network_delegate.h
++++ b/net/base/network_delegate.h
+@@ -48,6 +48,7 @@ class CookieInclusionStatus;
+ class HttpRequestHeaders;
+ class HttpResponseHeaders;
+ class IPEndPoint;
++class SchemefulSite;
+ class URLRequest;
+ 
+ class NET_EXPORT NetworkDelegate {
+@@ -120,6 +121,20 @@ class NET_EXPORT NetworkDelegate {
+   bool CanUseReportingClient(const url::Origin& origin,
+                              const GURL& endpoint) const;
+ 
++  // Gets the First-Party Sets cache filter info, which is used to mark the
++  // cache and determine if the previously stored cache of `request_site` can be
++  // accessed.
++  //
++  // The result may be returned synchronously, or `callback` may be invoked
++  // asynchronously with the result. The callback will be invoked iff the return
++  // value is nullopt; i.e. a result will be provided via return value or
++  // callback, but not both, and not neither.
++  absl::optional<FirstPartySetsCacheFilter::MatchInfo>
++  GetFirstPartySetsCacheFilterMatchInfoMaybeAsync(
++      const SchemefulSite& request_site,
++      base::OnceCallback<void(FirstPartySetsCacheFilter::MatchInfo)> callback)
++      const;
++
+  protected:
+   // Adds the given ExclusionReason to all cookies in
+   // `mayble_included_cookies`, and moves the contents of
+@@ -288,6 +303,12 @@ class NET_EXPORT NetworkDelegate {
+ 
+   virtual bool OnCanUseReportingClient(const url::Origin& origin,
+                                        const GURL& endpoint) const = 0;
++
++  virtual absl::optional<FirstPartySetsCacheFilter::MatchInfo>
++  OnGetFirstPartySetsCacheFilterMatchInfoMaybeAsync(
++      const SchemefulSite& request_site,
++      base::OnceCallback<void(FirstPartySetsCacheFilter::MatchInfo)> callback)
++      const = 0;
+ };
+ 
+ }  // namespace net
+diff --git a/net/base/network_delegate_impl.cc b/net/base/network_delegate_impl.cc
+index c14f48ddf635784492c030a8a0089eacbc83b35c..a8ed45ed263ea1a7592aa48f4cd0081155ead17c 100644
+--- a/net/base/network_delegate_impl.cc
++++ b/net/base/network_delegate_impl.cc
+@@ -97,4 +97,12 @@ bool NetworkDelegateImpl::OnCanUseReportingClient(const url::Origin& origin,
+   return true;
+ }
+ 
++absl::optional<FirstPartySetsCacheFilter::MatchInfo>
++NetworkDelegateImpl::OnGetFirstPartySetsCacheFilterMatchInfoMaybeAsync(
++    const SchemefulSite& request_site,
++    base::OnceCallback<void(FirstPartySetsCacheFilter::MatchInfo)> callback)
++    const {
++  return {FirstPartySetsCacheFilter::MatchInfo()};
++}
++
+ }  // namespace net
+diff --git a/net/base/network_delegate_impl.h b/net/base/network_delegate_impl.h
+index a43507f98641a626712ccfa1549512abca908502..9bc72d4ef0ab2cb7d52ddd3cf484c2933829da58 100644
+--- a/net/base/network_delegate_impl.h
++++ b/net/base/network_delegate_impl.h
+@@ -28,6 +28,7 @@ class Origin;
+ 
+ namespace net {
+ 
++class SchemefulSite;
+ class CookieOptions;
+ class CookieInclusionStatus;
+ class HttpRequestHeaders;
+@@ -99,6 +100,12 @@ class NET_EXPORT NetworkDelegateImpl : public NetworkDelegate {
+ 
+   bool OnCanUseReportingClient(const url::Origin& origin,
+                                const GURL& endpoint) const override;
++
++  absl::optional<FirstPartySetsCacheFilter::MatchInfo>
++  OnGetFirstPartySetsCacheFilterMatchInfoMaybeAsync(
++      const SchemefulSite& request_site,
++      base::OnceCallback<void(FirstPartySetsCacheFilter::MatchInfo)> callback)
++      const override;
+ };
+ 
+ }  // namespace net
+diff --git a/net/cookies/canonical_cookie.cc b/net/cookies/canonical_cookie.cc
+index b81e1656003f7676c2f0151612cec9648ebd9cbc..2e34df420cc6aaf59d3e638af7cd0cbcfa99afc3 100644
+--- a/net/cookies/canonical_cookie.cc
++++ b/net/cookies/canonical_cookie.cc
+@@ -340,9 +340,11 @@ bool HasValidHostPrefixAttributes(const GURL& url,
+ }  // namespace
+ 
+ CookieAccessParams::CookieAccessParams(CookieAccessSemantics access_semantics,
+-                                       bool delegate_treats_url_as_trustworthy)
++                                       bool delegate_treats_url_as_trustworthy,
++                                       CookieSamePartyStatus same_party_status)
+     : access_semantics(access_semantics),
+-      delegate_treats_url_as_trustworthy(delegate_treats_url_as_trustworthy) {}
++      delegate_treats_url_as_trustworthy(delegate_treats_url_as_trustworthy),
++      same_party_status(same_party_status) {}
+ 
+ CanonicalCookie::CanonicalCookie() = default;
+ 
+@@ -615,6 +617,12 @@ std::unique_ptr<CanonicalCookie> CanonicalCookie::Create(
+     status->AddExclusionReason(CookieInclusionStatus::EXCLUDE_INVALID_PREFIX);
+   }
+ 
++  bool is_same_party_valid = IsCookieSamePartyValid(parsed_cookie);
++  if (!is_same_party_valid) {
++    status->AddExclusionReason(
++        CookieInclusionStatus::EXCLUDE_INVALID_SAMEPARTY);
++  }
++
+   bool partition_has_nonce = CookiePartitionKey::HasNonce(cookie_partition_key);
+   bool is_partitioned_valid =
+       IsCookiePartitionedValid(url, parsed_cookie, partition_has_nonce);
+@@ -880,6 +888,10 @@ std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateSanitizedCookie(
+         net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX);
+   }
+ 
++  if (!IsCookieSamePartyValid(same_party, secure, same_site)) {
++    status->AddExclusionReason(
++        net::CookieInclusionStatus::EXCLUDE_INVALID_SAMEPARTY);
++  }
+   if (!IsCookiePartitionedValid(url, secure,
+                                 /*is_partitioned=*/partition_key.has_value(),
+                                 /*partition_has_nonce=*/
+@@ -1200,11 +1212,56 @@ CookieAccessResult CanonicalCookie::IncludeForRequestURL(
+         CookieInclusionStatus::EXCLUDE_SAMESITE_NONE_INSECURE);
+   }
+ 
+-  // Only apply SameSite-related warnings if SameParty is not in effect.
+-  ApplySameSiteCookieWarningToStatus(SameSite(), effective_same_site,
+-                                     IsSecure(),
+-                                     options.same_site_cookie_context(),
+-                                     &status, false /* is_cookie_being_set */);
++  switch (params.same_party_status) {
++    case CookieSamePartyStatus::kEnforceSamePartyExclude:
++      DCHECK(IsSameParty());
++      status.AddExclusionReason(
++          CookieInclusionStatus::EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT);
++      [[fallthrough]];
++    case CookieSamePartyStatus::kEnforceSamePartyInclude: {
++      status.AddWarningReason(CookieInclusionStatus::WARN_TREATED_AS_SAMEPARTY);
++      // Remove any SameSite exclusion reasons, since SameParty overrides
++      // SameSite.
++      DCHECK(!status.HasExclusionReason(
++          CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT));
++      DCHECK_NE(effective_same_site, CookieEffectiveSameSite::STRICT_MODE);
++      bool included_by_samesite =
++          !status.HasExclusionReason(
++              CookieInclusionStatus::EXCLUDE_SAMESITE_LAX) &&
++          !status.HasExclusionReason(
++              CookieInclusionStatus::
++                  EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX);
++      if (!included_by_samesite) {
++        status.RemoveExclusionReasons({
++            CookieInclusionStatus::EXCLUDE_SAMESITE_LAX,
++            CookieInclusionStatus::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
++        });
++      }
++
++      // Update metrics.
++      if (status.HasOnlyExclusionReason(
++              CookieInclusionStatus::EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT) &&
++          included_by_samesite) {
++        status.AddWarningReason(
++            CookieInclusionStatus::WARN_SAMEPARTY_EXCLUSION_OVERRULED_SAMESITE);
++      }
++      if (status.IsInclude()) {
++        if (!included_by_samesite) {
++          status.AddWarningReason(
++              CookieInclusionStatus::
++                  WARN_SAMEPARTY_INCLUSION_OVERRULED_SAMESITE);
++        }
++      }
++      break;
++    }
++    case CookieSamePartyStatus::kNoSamePartyEnforcement:
++      // Only apply SameSite-related warnings if SameParty is not in effect.
++      ApplySameSiteCookieWarningToStatus(
++          SameSite(), effective_same_site, IsSecure(),
++          options.same_site_cookie_context(), &status,
++          false /* is_cookie_being_set */);
++      break;
++  }
+ 
+   if (status.IsInclude()) {
+     UMA_HISTOGRAM_ENUMERATION("Cookie.IncludedRequestEffectiveSameSite",
+@@ -1374,11 +1431,59 @@ CookieAccessResult CanonicalCookie::IsSetPermittedInContext(
+       break;
+   }
+ 
+-  // Only apply SameSite-related warnings if SameParty is not in effect.
+-  ApplySameSiteCookieWarningToStatus(
+-      SameSite(), access_result.effective_same_site, IsSecure(),
+-      options.same_site_cookie_context(), &access_result.status,
+-      true /* is_cookie_being_set */);
++  switch (params.same_party_status) {
++    case CookieSamePartyStatus::kEnforceSamePartyExclude:
++      DCHECK(IsSameParty());
++      access_result.status.AddExclusionReason(
++          CookieInclusionStatus::EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT);
++      [[fallthrough]];
++    case CookieSamePartyStatus::kEnforceSamePartyInclude: {
++      DCHECK(IsSameParty());
++      access_result.status.AddWarningReason(
++          CookieInclusionStatus::WARN_TREATED_AS_SAMEPARTY);
++      // Remove any SameSite exclusion reasons, since SameParty overrides
++      // SameSite.
++      DCHECK(!access_result.status.HasExclusionReason(
++          CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT));
++      DCHECK_NE(access_result.effective_same_site,
++                CookieEffectiveSameSite::STRICT_MODE);
++      bool included_by_samesite =
++          !access_result.status.HasExclusionReason(
++              CookieInclusionStatus::EXCLUDE_SAMESITE_LAX) &&
++          !access_result.status.HasExclusionReason(
++              CookieInclusionStatus::
++                  EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX);
++      if (!included_by_samesite) {
++        access_result.status.RemoveExclusionReasons({
++            CookieInclusionStatus::EXCLUDE_SAMESITE_LAX,
++            CookieInclusionStatus::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
++        });
++      }
++
++      // Update metrics.
++      if (access_result.status.HasOnlyExclusionReason(
++              CookieInclusionStatus::EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT) &&
++          included_by_samesite) {
++        access_result.status.AddWarningReason(
++            CookieInclusionStatus::WARN_SAMEPARTY_EXCLUSION_OVERRULED_SAMESITE);
++      }
++      if (access_result.status.IsInclude()) {
++        if (!included_by_samesite) {
++          access_result.status.AddWarningReason(
++              CookieInclusionStatus::
++                  WARN_SAMEPARTY_INCLUSION_OVERRULED_SAMESITE);
++        }
++      }
++      break;
++    }
++    case CookieSamePartyStatus::kNoSamePartyEnforcement:
++      // Only apply SameSite-related warnings if SameParty is not in effect.
++      ApplySameSiteCookieWarningToStatus(
++          SameSite(), access_result.effective_same_site, IsSecure(),
++          options.same_site_cookie_context(), &access_result.status,
++          true /* is_cookie_being_set */);
++      break;
++  }
+ 
+   if (access_result.status.IsInclude()) {
+     UMA_HISTOGRAM_ENUMERATION("Cookie.IncludedResponseEffectiveSameSite",
+@@ -1485,6 +1590,9 @@ bool CanonicalCookie::IsCanonicalForFromStorage() const {
+   if (name_ == "" && HasHiddenPrefixName(value_))
+     return false;
+ 
++  if (!IsCookieSamePartyValid(same_party_, secure_, same_site_))
++    return false;
++
+   if (IsPartitioned()) {
+     if (CookiePartitionKey::HasNonce(partition_key_))
+       return true;
+@@ -1731,6 +1839,23 @@ bool CanonicalCookie::IsRecentlyCreated(base::TimeDelta age_threshold) const {
+   return (base::Time::Now() - creation_date_) <= age_threshold;
+ }
+ 
++// static
++bool CanonicalCookie::IsCookieSamePartyValid(
++    const ParsedCookie& parsed_cookie) {
++  return IsCookieSamePartyValid(parsed_cookie.IsSameParty(),
++                                parsed_cookie.IsSecure(),
++                                parsed_cookie.SameSite());
++}
++
++// static
++bool CanonicalCookie::IsCookieSamePartyValid(bool is_same_party,
++                                             bool is_secure,
++                                             CookieSameSite same_site) {
++  if (!is_same_party)
++    return true;
++  return is_secure && (same_site != CookieSameSite::STRICT_MODE);
++}
++
+ // static
+ bool CanonicalCookie::IsCookiePartitionedValid(
+     const GURL& url,
+diff --git a/net/cookies/canonical_cookie.h b/net/cookies/canonical_cookie.h
+index 56b919c8e965182e2e5621ee123e8afea3565e36..9dbc2a30c85c3e70e7a995e1bc8336b49e2ed7a8 100644
+--- a/net/cookies/canonical_cookie.h
++++ b/net/cookies/canonical_cookie.h
+@@ -43,7 +43,8 @@ using CookieAccessResultList = std::vector<CookieWithAccessResult>;
+ struct NET_EXPORT CookieAccessParams {
+   CookieAccessParams() = delete;
+   CookieAccessParams(CookieAccessSemantics access_semantics,
+-                     bool delegate_treats_url_as_trustworthy);
++                     bool delegate_treats_url_as_trustworthy,
++                     CookieSamePartyStatus same_party_status);
+ 
+   // |access_semantics| is the access mode of the cookie access check.
+   CookieAccessSemantics access_semantics = CookieAccessSemantics::UNKNOWN;
+@@ -51,6 +52,10 @@ struct NET_EXPORT CookieAccessParams {
+   // CookieAccessDelegate has authorized access to secure cookies from URLs
+   // which might not otherwise be able to do so.
+   bool delegate_treats_url_as_trustworthy = false;
++  // |same_party_status| indicates whether, and how, SameParty restrictions
++  // should be enforced.
++  CookieSamePartyStatus same_party_status =
++      CookieSamePartyStatus::kNoSamePartyEnforcement;
+ };
+ 
+ class NET_EXPORT CanonicalCookie {
+@@ -565,6 +570,14 @@ class NET_EXPORT CanonicalCookie {
+   // Returns whether the cookie was created at most |age_threshold| ago.
+   bool IsRecentlyCreated(base::TimeDelta age_threshold) const;
+ 
++  // Returns true iff the cookie does not violate any rules associated with
++  // creating a cookie with the SameParty attribute. In particular, if a cookie
++  // has SameParty, then it must be Secure and must not be SameSite=Strict.
++  static bool IsCookieSamePartyValid(const ParsedCookie& parsed_cookie);
++  static bool IsCookieSamePartyValid(bool is_same_party,
++                                     bool is_secure,
++                                     CookieSameSite same_site);
++
+   // Returns true iff the cookie is a partitioned cookie with a nonce or that
+   // does not violate the semantics of the Partitioned attribute:
+   // - Must have the Secure attribute OR the cookie partition contains a nonce.
+diff --git a/net/cookies/cookie_access_delegate.cc b/net/cookies/cookie_access_delegate.cc
+index 256dd856cce9fb482926f7a0c8bb80676a37e0e7..9811f944fff0a20a1a7b431e214f3578ed30785b 100644
+--- a/net/cookies/cookie_access_delegate.cc
++++ b/net/cookies/cookie_access_delegate.cc
+@@ -4,7 +4,13 @@
+ 
+ #include "net/cookies/cookie_access_delegate.h"
+ 
+-class GURL;
++#include <set>
++
++#include "base/functional/callback.h"
++#include "net/base/schemeful_site.h"
++#include "net/cookies/cookie_partition_key.h"
++#include "net/first_party_sets/first_party_set_entry.h"
++#include "third_party/abseil-cpp/absl/types/optional.h"
+ 
+ namespace net {
+ 
+diff --git a/net/cookies/cookie_access_delegate.h b/net/cookies/cookie_access_delegate.h
+index 0eec2486fae7b793ee522954cd3c05a1047b2776..ee8e3d0fab27ef7f087194ad8fcfb6f17b042592 100644
+--- a/net/cookies/cookie_access_delegate.h
++++ b/net/cookies/cookie_access_delegate.h
+@@ -5,6 +5,8 @@
+ #ifndef NET_COOKIES_COOKIE_ACCESS_DELEGATE_H_
+ #define NET_COOKIES_COOKIE_ACCESS_DELEGATE_H_
+ 
++#include <set>
++
+ #include "base/containers/flat_map.h"
+ #include "base/containers/flat_set.h"
+ #include "base/functional/callback_forward.h"
+@@ -15,7 +17,7 @@
+ #include "net/cookies/cookie_partition_key.h"
+ #include "net/first_party_sets/first_party_set_entry.h"
+ #include "net/first_party_sets/first_party_set_metadata.h"
+-#include "net/first_party_sets/first_party_sets_cache_filter.h"
++#include "net/first_party_sets/same_party_context.h"
+ #include "third_party/abseil-cpp/absl/types/optional.h"
+ #include "url/gurl.h"
+ 
+@@ -50,23 +52,21 @@ class NET_EXPORT CookieAccessDelegate {
+       const GURL& url,
+       const SiteForCookies& site_for_cookies) const = 0;
+ 
+-  // Calls `callback` with First-Party Sets metadata about `site` and
+-  // `top_frame_site`, and cache filter info for `site`. Cache filter info is
+-  // used to determine if the existing HTTP cache entries for `site` are allowed
+-  // to be accessed.
++  // Calls `callback` with metadata indicating whether `site` is same-party with
++  // `party_context` and `top_frame_site`; and `site`'s owner, if applicable..
++  // If `top_frame_site` is nullptr, then `site` will be checked only against
++  // `party_context`.
+   //
+   // This may return a result synchronously, or asynchronously invoke `callback`
+   // with the result. The callback will be invoked iff the return value is
+   // nullopt; i.e. a result will be provided via return value or callback, but
+   // not both, and not neither.
+-  [[nodiscard]] virtual absl::optional<
+-      std::pair<FirstPartySetMetadata, FirstPartySetsCacheFilter::MatchInfo>>
++  [[nodiscard]] virtual absl::optional<FirstPartySetMetadata>
+   ComputeFirstPartySetMetadataMaybeAsync(
+       const net::SchemefulSite& site,
+       const net::SchemefulSite* top_frame_site,
+-      base::OnceCallback<void(FirstPartySetMetadata,
+-                              FirstPartySetsCacheFilter::MatchInfo)> callback)
+-      const = 0;
++      const std::set<net::SchemefulSite>& party_context,
++      base::OnceCallback<void(FirstPartySetMetadata)> callback) const = 0;
+ 
+   // Returns the entries of a set of sites if the sites are in non-trivial sets.
+   // If a given site is not in a non-trivial set, the output does not contain a
+diff --git a/net/cookies/cookie_constants.h b/net/cookies/cookie_constants.h
+index 33dd35c7cc3f4ce03fe359f33afc3f9a3b700476..9d8a2fa9e5255d4dc18fd1e7d5b8bc5e24728a95 100644
+--- a/net/cookies/cookie_constants.h
++++ b/net/cookies/cookie_constants.h
+@@ -118,6 +118,16 @@ enum class CookieAccessSemantics {
+   LEGACY,
+ };
+ 
++enum class CookieSamePartyStatus {
++  // Used when there should be no SameParty enforcement (either because the
++  // cookie is not marked SameParty, or the enforcement is irrelevant).
++  kNoSamePartyEnforcement = 0,
++  // Used when SameParty enforcement says to exclude the cookie.
++  kEnforceSamePartyExclude = 1,
++  // Used when SameParty enforcement says to include the cookie.
++  kEnforceSamePartyInclude = 2,
++};
++
+ // What scheme was used in the setting of a cookie.
+ // Do not renumber.
+ enum class CookieSourceScheme {
+diff --git a/net/cookies/cookie_inclusion_status.cc b/net/cookies/cookie_inclusion_status.cc
+index 5be49154795380ee37258a9f2d24256d406af03f..4fba69d9ea8c9e94f8f8c75536584089589cd633 100644
+--- a/net/cookies/cookie_inclusion_status.cc
++++ b/net/cookies/cookie_inclusion_status.cc
+@@ -244,6 +244,9 @@ std::string CookieInclusionStatus::GetDebugString() const {
+       {EXCLUDE_DISALLOWED_CHARACTER, "EXCLUDE_DISALLOWED_CHARACTER"},
+       {EXCLUDE_THIRD_PARTY_PHASEOUT, "EXCLUDE_THIRD_PARTY_PHASEOUT"},
+       {EXCLUDE_NO_COOKIE_CONTENT, "EXCLUDE_NO_COOKIE_CONTENT"},
++      {EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT,
++       "EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT"},
++      {EXCLUDE_INVALID_SAMEPARTY, "EXCLUDE_INVALID_SAMEPARTY"},
+   };
+   static_assert(
+       std::size(exclusion_reasons) == ExclusionReason::NUM_EXCLUSION_REASONS,
+@@ -293,6 +296,11 @@ std::string CookieInclusionStatus::GetDebugString() const {
+        "WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME"},
+       {WARN_SHADOWING_DOMAIN, "WARN_SHADOWING_DOMAIN"},
+       {WARN_THIRD_PARTY_PHASEOUT, "WARN_THIRD_PARTY_PHASEOUT"},
++      {WARN_TREATED_AS_SAMEPARTY, "WARN_TREATED_AS_SAMEPARTY"},
++      {WARN_SAMEPARTY_EXCLUSION_OVERRULED_SAMESITE,
++       "WARN_SAMEPARTY_EXCLUSION_OVERRULED_SAMESITE"},
++      {WARN_SAMEPARTY_INCLUSION_OVERRULED_SAMESITE,
++       "WARN_SAMEPARTY_INCLUSION_OVERRULED_SAMESITE"},
+   };
+   static_assert(
+       std::size(warning_reasons) == WarningReason::NUM_WARNING_REASONS,
+diff --git a/net/cookies/cookie_inclusion_status.h b/net/cookies/cookie_inclusion_status.h
+index 1eefc3db602f5bb419ad3dbcb167dcd9742e1114..87ad6f7049560ac7f17a23047d983e4b17ec5bfa 100644
+--- a/net/cookies/cookie_inclusion_status.h
++++ b/net/cookies/cookie_inclusion_status.h
+@@ -109,6 +109,12 @@ class NET_EXPORT CookieInclusionStatus {
+     EXCLUDE_THIRD_PARTY_PHASEOUT = 25,
+     // Cookie contains no content or only whitespace.
+     EXCLUDE_NO_COOKIE_CONTENT = 26,
++    // The cookie specified SameParty, but was used in a cross-party context.
++    EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT = 27,
++    // Cookie was set with an invalid SameParty attribute in combination with
++    // other attributes. (SameParty is invalid if Secure is not present, or if
++    // SameSite=Strict is present.)
++    EXCLUDE_INVALID_SAMEPARTY = 28,
+ 
+     // This should be kept last.
+     NUM_EXCLUSION_REASONS
+@@ -228,6 +234,24 @@ class NET_EXPORT CookieInclusionStatus {
+     // This cookie will be blocked for third-party cookie phaseout.
+     WARN_THIRD_PARTY_PHASEOUT = 16,
+ 
++    // The cookie was treated as SameParty. This is different from looking at
++    // whether the cookie has the SameParty attribute, since we may choose to
++    // ignore that attribute for one reason or another. E.g., we ignore the
++    // SameParty attribute if the site is not a member of a nontrivial
++    // First-Party Set.
++    WARN_TREATED_AS_SAMEPARTY = 17,
++
++    // The cookie was excluded solely for SameParty reasons (i.e. it was in
++    // cross-party context), but would have been included by SameSite. (This can
++    // only occur in cross-party, cross-site contexts, for cookies that are
++    // 'SameParty; SameSite=None'.)
++    WARN_SAMEPARTY_EXCLUSION_OVERRULED_SAMESITE = 18,
++
++    // The cookie was included due to SameParty, even though it would have been
++    // excluded by SameSite. (This can only occur in same-party, cross-site
++    // contexts, for cookies that are 'SameParty; SameSite=Lax'.)
++    WARN_SAMEPARTY_INCLUSION_OVERRULED_SAMESITE = 19,
++
+     // This should be kept last.
+     NUM_WARNING_REASONS
+   };
+diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc
+index 0c3ce2e289b5944d38315790cca829e851c31e19..5c4352bb9fc1fd85373ee413b59a12967bd02710 100644
+--- a/net/cookies/cookie_monster.cc
++++ b/net/cookies/cookie_monster.cc
+@@ -377,7 +377,9 @@ CookieMonster::CookieMonster(scoped_refptr<PersistentCookieStore> store,
+ CookieMonster::CookieMonster(scoped_refptr<PersistentCookieStore> store,
+                              base::TimeDelta last_access_threshold,
+                              NetLog* net_log)
+-    : change_dispatcher_(this),
++    : same_party_attribute_enabled_(base::FeatureList::IsEnabled(
++          net::features::kSamePartyAttributeEnabled)),
++      change_dispatcher_(this, same_party_attribute_enabled_),
+       net_log_(NetLogWithSource::Make(net_log, NetLogSourceType::COOKIE_STORE)),
+       store_(std::move(store)),
+       last_access_threshold_(last_access_threshold),
+@@ -739,9 +741,13 @@ bool CookieMonster::MatchCookieDeletionInfo(
+             delete_info.url.value());
+   }
+ 
++  // Deletion uses all inclusive options, so it's ok to get the
++  // `CookieSamePartyStatus` wrong here.
+   return delete_info.Matches(
+-      cookie, CookieAccessParams{GetAccessSemanticsForCookie(cookie),
+-                                 delegate_treats_url_as_trustworthy});
++      cookie,
++      CookieAccessParams{GetAccessSemanticsForCookie(cookie),
++                         delegate_treats_url_as_trustworthy,
++                         CookieSamePartyStatus::kNoSamePartyEnforcement});
+ }
+ 
+ void CookieMonster::DeleteCanonicalCookie(const CanonicalCookie& cookie,
+@@ -1205,8 +1211,11 @@ void CookieMonster::FilterCookiesWithOptions(
+     // cookie |options|.
+     CookieAccessResult access_result = cookie_ptr->IncludeForRequestURL(
+         url, options,
+-        CookieAccessParams{GetAccessSemanticsForCookie(*cookie_ptr),
+-                           delegate_treats_url_as_trustworthy});
++        CookieAccessParams{
++            GetAccessSemanticsForCookie(*cookie_ptr),
++            delegate_treats_url_as_trustworthy,
++            cookie_util::GetSamePartyStatus(*cookie_ptr, options,
++                                            same_party_attribute_enabled_)});
+     cookies_and_access_results.emplace_back(cookie_ptr, access_result);
+ 
+     // Record the names of all origin cookies that would be included if both
+@@ -1549,7 +1558,9 @@ void CookieMonster::SetCanonicalCookie(
+   CookieAccessResult access_result = cc->IsSetPermittedInContext(
+       source_url, options,
+       CookieAccessParams(GetAccessSemanticsForCookie(*cc),
+-                         delegate_treats_url_as_trustworthy),
++                         delegate_treats_url_as_trustworthy,
++                         cookie_util::GetSamePartyStatus(
++                             *cc, options, same_party_attribute_enabled_)),
+       cookieable_schemes_, cookie_access_result);
+ 
+   const std::string key(GetKey(cc->Domain()));
+diff --git a/net/cookies/cookie_monster.h b/net/cookies/cookie_monster.h
+index 3ca2c691cfd00acf34f7648dfc6c9f6692be470a..6086953c99f55bd8a1034f0fa33ea77e4af9ec0d 100644
+--- a/net/cookies/cookie_monster.h
++++ b/net/cookies/cookie_monster.h
+@@ -734,6 +734,8 @@ class NET_EXPORT CookieMonster : public CookieStore {
+   // nonce.
+   size_t num_nonced_partitioned_cookie_bytes_ = 0u;
+ 
++  bool same_party_attribute_enabled_ = false;
++
+   CookieMonsterChangeDispatcher change_dispatcher_;
+ 
+   // Indicates whether the cookie store has been initialized.
+diff --git a/net/cookies/cookie_monster_change_dispatcher.cc b/net/cookies/cookie_monster_change_dispatcher.cc
+index 61bd41e5a273e9d8d467deb587794600257feae6..34448cfee892b93558016c4fba22123c9522f6c4 100644
+--- a/net/cookies/cookie_monster_change_dispatcher.cc
++++ b/net/cookies/cookie_monster_change_dispatcher.cc
+@@ -37,6 +37,7 @@ CookieMonsterChangeDispatcher::Subscription::Subscription(
+     std::string name_key,
+     GURL url,
+     CookiePartitionKeyCollection cookie_partition_key_collection,
++    bool same_party_attribute_enabled,
+     net::CookieChangeCallback callback)
+     : change_dispatcher_(std::move(change_dispatcher)),
+       domain_key_(std::move(domain_key)),
+@@ -45,6 +46,7 @@ CookieMonsterChangeDispatcher::Subscription::Subscription(
+       cookie_partition_key_collection_(
+           std::move(cookie_partition_key_collection)),
+       callback_(std::move(callback)),
++      same_party_attribute_enabled_(same_party_attribute_enabled),
+       task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {
+   DCHECK(url_.is_valid() || url_.is_empty());
+   DCHECK_EQ(url_.is_empty(), domain_key_ == kGlobalDomainKey);
+@@ -73,11 +75,14 @@ void CookieMonsterChangeDispatcher::Subscription::DispatchChange(
+         cookie_access_delegate &&
+         cookie_access_delegate->ShouldTreatUrlAsTrustworthy(url_);
+     CookieOptions options = CookieOptions::MakeAllInclusive();
++    CookieSamePartyStatus same_party_status = cookie_util::GetSamePartyStatus(
++        cookie, options, same_party_attribute_enabled_);
+     if (!cookie
+              .IncludeForRequestURL(
+                  url_, options,
+                  CookieAccessParams{change.access_result.access_semantics,
+-                                    delegate_treats_url_as_trustworthy})
++                                    delegate_treats_url_as_trustworthy,
++                                    same_party_status})
+              .status.IsInclude()) {
+       return;
+     }
+@@ -111,8 +116,10 @@ void CookieMonsterChangeDispatcher::Subscription::DoDispatchChange(
+ }
+ 
+ CookieMonsterChangeDispatcher::CookieMonsterChangeDispatcher(
+-    const CookieMonster* cookie_monster)
+-    : cookie_monster_(cookie_monster) {}
++    const CookieMonster* cookie_monster,
++    bool same_party_attribute_enabled)
++    : cookie_monster_(cookie_monster),
++      same_party_attribute_enabled_(same_party_attribute_enabled) {}
+ 
+ CookieMonsterChangeDispatcher::~CookieMonsterChangeDispatcher() {
+   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+@@ -154,7 +161,7 @@ CookieMonsterChangeDispatcher::AddCallbackForCookie(
+   std::unique_ptr<Subscription> subscription = std::make_unique<Subscription>(
+       weak_ptr_factory_.GetWeakPtr(), DomainKey(url), NameKey(name), url,
+       CookiePartitionKeyCollection::FromOptional(cookie_partition_key),
+-      std::move(callback));
++      same_party_attribute_enabled_, std::move(callback));
+ 
+   LinkSubscription(subscription.get());
+   return subscription;
+@@ -171,7 +178,7 @@ CookieMonsterChangeDispatcher::AddCallbackForUrl(
+       weak_ptr_factory_.GetWeakPtr(), DomainKey(url),
+       std::string(kGlobalNameKey), url,
+       CookiePartitionKeyCollection::FromOptional(cookie_partition_key),
+-      std::move(callback));
++      same_party_attribute_enabled_, std::move(callback));
+ 
+   LinkSubscription(subscription.get());
+   return subscription;
+@@ -185,7 +192,8 @@ CookieMonsterChangeDispatcher::AddCallbackForAllChanges(
+   std::unique_ptr<Subscription> subscription = std::make_unique<Subscription>(
+       weak_ptr_factory_.GetWeakPtr(), std::string(kGlobalDomainKey),
+       std::string(kGlobalNameKey), GURL(""),
+-      CookiePartitionKeyCollection::ContainsAll(), std::move(callback));
++      CookiePartitionKeyCollection::ContainsAll(),
++      same_party_attribute_enabled_, std::move(callback));
+ 
+   LinkSubscription(subscription.get());
+   return subscription;
+diff --git a/net/cookies/cookie_monster_change_dispatcher.h b/net/cookies/cookie_monster_change_dispatcher.h
+index d6f449140b0244263b0c248e9163eb9940407541..31ca1e979ce1133dd6a78a7d8d09a6aba8f0ee44 100644
+--- a/net/cookies/cookie_monster_change_dispatcher.h
++++ b/net/cookies/cookie_monster_change_dispatcher.h
+@@ -33,7 +33,8 @@ class CookieMonsterChangeDispatcher : public CookieChangeDispatcher {
+       base::RepeatingCallbackList<void(const CookieChangeInfo&)>;
+ 
+   // Expects |cookie_monster| to outlive this.
+-  explicit CookieMonsterChangeDispatcher(const CookieMonster* cookie_monster);
++  CookieMonsterChangeDispatcher(const CookieMonster* cookie_monster,
++                                bool same_party_attribute_enabled);
+ 
+   CookieMonsterChangeDispatcher(const CookieMonsterChangeDispatcher&) = delete;
+   CookieMonsterChangeDispatcher& operator=(
+@@ -78,6 +79,7 @@ class CookieMonsterChangeDispatcher : public CookieChangeDispatcher {
+                  std::string name_key,
+                  GURL url,
+                  CookiePartitionKeyCollection cookie_partition_key_collection,
++                 bool same_party_attribute_enabled,
+                  net::CookieChangeCallback callback);
+ 
+     Subscription(const Subscription&) = delete;
+@@ -105,6 +107,7 @@ class CookieMonsterChangeDispatcher : public CookieChangeDispatcher {
+     const GURL url_;                // empty() means no URL-based filtering.
+     const CookiePartitionKeyCollection cookie_partition_key_collection_;
+     const net::CookieChangeCallback callback_;
++    bool same_party_attribute_enabled_;
+ 
+     void DoDispatchChange(const CookieChangeInfo& change) const;
+ 
+@@ -154,6 +157,8 @@ class CookieMonsterChangeDispatcher : public CookieChangeDispatcher {
+ 
+   CookieDomainMap cookie_domain_map_;
+ 
++  const bool same_party_attribute_enabled_;
++
+   THREAD_CHECKER(thread_checker_);
+ 
+   // Vends weak pointers to subscriptions.
+diff --git a/net/cookies/cookie_options.cc b/net/cookies/cookie_options.cc
+index d3e96410b39057e3a8d20078d69cbd728f7c8bde..42eb151094e5b26671ea576bf402f8a877e862a3 100644
+--- a/net/cookies/cookie_options.cc
++++ b/net/cookies/cookie_options.cc
+@@ -8,7 +8,9 @@
+ 
+ #include <tuple>
+ 
++#include "base/metrics/histogram_functions.h"
+ #include "net/cookies/cookie_util.h"
++#include "net/first_party_sets/same_party_context.h"
+ 
+ namespace net {
+ 
+@@ -99,6 +101,8 @@ CookieOptions CookieOptions::MakeAllInclusive() {
+   options.set_include_httponly();
+   options.set_same_site_cookie_context(SameSiteCookieContext::MakeInclusive());
+   options.set_do_not_update_access_time();
++  options.set_same_party_context(SamePartyContext::MakeInclusive());
++  options.set_is_in_nontrivial_first_party_set(true);
+   return options;
+ }
+ 
+diff --git a/net/cookies/cookie_options.h b/net/cookies/cookie_options.h
+index 183c1f61c1fbe94c8c85dd4f9a27695a04aad816..fc7cddad43a74945d5af6c2100893720f3895734 100644
+--- a/net/cookies/cookie_options.h
++++ b/net/cookies/cookie_options.h
+@@ -10,8 +10,11 @@
+ #include <ostream>
+ #include <string>
+ 
+-#include "base/check_op.h"
+ #include "net/base/net_export.h"
++#include "net/cookies/cookie_constants.h"
++#include "net/cookies/cookie_inclusion_status.h"
++#include "net/first_party_sets/same_party_context.h"
++#include "url/gurl.h"
+ 
+ namespace net {
+ 
+@@ -266,6 +269,26 @@ class NET_EXPORT CookieOptions {
+   void unset_return_excluded_cookies() { return_excluded_cookies_ = false; }
+   bool return_excluded_cookies() const { return return_excluded_cookies_; }
+ 
++  void set_same_party_context(const SamePartyContext& context) {
++    same_party_context_ = context;
++  }
++  const SamePartyContext& same_party_context() const {
++    return same_party_context_;
++  }
++
++  // Getter/setter of |full_party_context_size_| for logging purposes.
++  void set_full_party_context_size(uint32_t len) {
++    full_party_context_size_ = len;
++  }
++  uint32_t full_party_context_size() const { return full_party_context_size_; }
++
++  void set_is_in_nontrivial_first_party_set(bool is_member) {
++    is_in_nontrivial_first_party_set_ = is_member;
++  }
++  bool is_in_nontrivial_first_party_set() const {
++    return is_in_nontrivial_first_party_set_;
++  }
++
+   // Convenience method for where you need a CookieOptions that will
+   // work for getting/setting all types of cookies, including HttpOnly and
+   // SameSite cookies. Also specifies not to update the access time, because
+@@ -281,6 +304,19 @@ class NET_EXPORT CookieOptions {
+   SameSiteCookieContext same_site_cookie_context_;
+   bool update_access_time_ = true;
+   bool return_excluded_cookies_ = false;
++
++  SamePartyContext same_party_context_;
++
++  // The size of the isolation_info.party_context plus the top-frame site.
++  // Stored for logging purposes.
++  uint32_t full_party_context_size_ = 0;
++  // Whether the site requesting cookie access (as opposed to e.g. the
++  // `site_for_cookies`) is a member (or owner) of a nontrivial First-Party
++  // Set.
++  // This is included here temporarily, for the purpose of ignoring SameParty
++  // for sites that are not participating in the Origin Trial.
++  // TODO(https://crbug.com/1163990): remove this field.
++  bool is_in_nontrivial_first_party_set_ = false;
+ };
+ 
+ NET_EXPORT bool operator==(
+diff --git a/net/cookies/cookie_util.cc b/net/cookies/cookie_util.cc
+index b479ab1deb4c54d14ccf87c1d923f6ab7fad6876..e2670576d72bfcc2267d8cc9b1865210d8162a4f 100644
+--- a/net/cookies/cookie_util.cc
++++ b/net/cookies/cookie_util.cc
+@@ -32,7 +32,7 @@
+ #include "net/cookies/cookie_monster.h"
+ #include "net/cookies/cookie_options.h"
+ #include "net/first_party_sets/first_party_set_metadata.h"
+-#include "net/first_party_sets/first_party_sets_cache_filter.h"
++#include "net/first_party_sets/same_party_context.h"
+ #include "net/http/http_util.h"
+ #include "url/gurl.h"
+ #include "url/url_constants.h"
+@@ -894,24 +894,23 @@ bool IsSchemefulSameSiteEnabled() {
+   return base::FeatureList::IsEnabled(features::kSchemefulSameSite);
+ }
+ 
+-absl::optional<
+-    std::pair<FirstPartySetMetadata, FirstPartySetsCacheFilter::MatchInfo>>
+-ComputeFirstPartySetMetadataMaybeAsync(
++absl::optional<FirstPartySetMetadata> ComputeFirstPartySetMetadataMaybeAsync(
+     const SchemefulSite& request_site,
+     const IsolationInfo& isolation_info,
+     const CookieAccessDelegate* cookie_access_delegate,
+-    base::OnceCallback<void(FirstPartySetMetadata,
+-                            FirstPartySetsCacheFilter::MatchInfo)> callback) {
+-  if (cookie_access_delegate) {
++    bool force_ignore_top_frame_party,
++    base::OnceCallback<void(FirstPartySetMetadata)> callback) {
++  if (isolation_info.party_context().has_value() && cookie_access_delegate) {
+     return cookie_access_delegate->ComputeFirstPartySetMetadataMaybeAsync(
+         request_site,
+-        base::OptionalToPtr(
+-            isolation_info.network_isolation_key().GetTopFrameSite()),
+-        std::move(callback));
++        force_ignore_top_frame_party
++            ? nullptr
++            : base::OptionalToPtr(
++                  isolation_info.network_isolation_key().GetTopFrameSite()),
++        isolation_info.party_context().value(), std::move(callback));
+   }
+ 
+-  return std::make_pair(FirstPartySetMetadata(),
+-                        FirstPartySetsCacheFilter::MatchInfo());
++  return FirstPartySetMetadata();
+ }
+ 
+ CookieOptions::SameSiteCookieContext::ContextMetadata::HttpMethod
+@@ -940,6 +939,23 @@ HttpMethodStringToEnum(const std::string& in) {
+   return HttpMethod::kUnknown;
+ }
+ 
++CookieSamePartyStatus GetSamePartyStatus(
++    const CanonicalCookie& cookie,
++    const CookieOptions& options,
++    const bool same_party_attribute_enabled) {
++  if (!same_party_attribute_enabled || !cookie.IsSameParty() ||
++      !options.is_in_nontrivial_first_party_set()) {
++    return CookieSamePartyStatus::kNoSamePartyEnforcement;
++  }
++
++  switch (options.same_party_context().context_type()) {
++    case SamePartyContext::Type::kCrossParty:
++      return CookieSamePartyStatus::kEnforceSamePartyExclude;
++    case SamePartyContext::Type::kSameParty:
++      return CookieSamePartyStatus::kEnforceSamePartyInclude;
++  };
++}
++
+ bool IsCookieAccessResultInclude(CookieAccessResult cookie_access_result) {
+   return cookie_access_result.status.IsInclude();
+ }
+diff --git a/net/cookies/cookie_util.h b/net/cookies/cookie_util.h
+index e2e9ea3e8ae5af94b3c14df897cf0282513d67ab..a268d94feecf5e3e1c0559b75b66cefa1580ace0 100644
+--- a/net/cookies/cookie_util.h
++++ b/net/cookies/cookie_util.h
+@@ -17,7 +17,6 @@
+ #include "net/cookies/cookie_options.h"
+ #include "net/cookies/site_for_cookies.h"
+ #include "net/first_party_sets/first_party_set_metadata.h"
+-#include "net/first_party_sets/first_party_sets_cache_filter.h"
+ #include "third_party/abseil-cpp/absl/types/optional.h"
+ #include "url/origin.h"
+ 
+@@ -286,27 +285,37 @@ NET_EXPORT bool IsSchemeBoundCookiesEnabled();
+ // Returns whether the respective feature is enabled.
+ NET_EXPORT bool IsSchemefulSameSiteEnabled();
+ 
+-// Computes the First-Party Sets metadata and cache match information.
+-// `isolation_info` must be fully populated.
++// Computes the First-Party Sets metadata, determining which of the cookies for
++// `request_site` can be accessed. `isolation_info` must be fully populated.  If
++// `force_ignore_top_frame_party` is true, the top frame from `isolation_info`
++// will be assumed to be same-party with `request_site`, regardless of what it
++// is.
+ //
+ // The result may be returned synchronously, or `callback` may be invoked
+ // asynchronously with the result. The callback will be invoked iff the return
+ // value is nullopt; i.e. a result will be provided via return value or
+ // callback, but not both, and not neither.
+-[[nodiscard]] NET_EXPORT absl::optional<
+-    std::pair<FirstPartySetMetadata, FirstPartySetsCacheFilter::MatchInfo>>
++[[nodiscard]] NET_EXPORT absl::optional<FirstPartySetMetadata>
+ ComputeFirstPartySetMetadataMaybeAsync(
+     const SchemefulSite& request_site,
+     const IsolationInfo& isolation_info,
+     const CookieAccessDelegate* cookie_access_delegate,
+-    base::OnceCallback<void(FirstPartySetMetadata,
+-                            FirstPartySetsCacheFilter::MatchInfo)> callback);
++    bool force_ignore_top_frame_party,
++    base::OnceCallback<void(FirstPartySetMetadata)> callback);
+ 
+ // Converts a string representing the http request method to its enum
+ // representation.
+ NET_EXPORT CookieOptions::SameSiteCookieContext::ContextMetadata::HttpMethod
+ HttpMethodStringToEnum(const std::string& in);
+ 
++// Get the SameParty inclusion status. If the cookie is not SameParty, returns
++// kNoSamePartyEnforcement; if the cookie is SameParty but does not have a
++// valid context, returns kEnforceSamePartyExclude.
++NET_EXPORT CookieSamePartyStatus
++GetSamePartyStatus(const CanonicalCookie& cookie,
++                   const CookieOptions& options,
++                   bool same_party_attribute_enabled);
++
+ // Takes a CookieAccessResult and returns a bool, returning true if the
+ // CookieInclusionStatus in CookieAccessResult was set to "include", else
+ // returning false.
+diff --git a/net/first_party_sets/first_party_set_metadata.cc b/net/first_party_sets/first_party_set_metadata.cc
+index 75f41ad79ac067f806d4bea8b687a4781778e37a..26521fa5f9b4a93f892212cbd45e77ce41fc0f29 100644
+--- a/net/first_party_sets/first_party_set_metadata.cc
++++ b/net/first_party_sets/first_party_set_metadata.cc
+@@ -13,9 +13,11 @@ namespace net {
+ 
+ FirstPartySetMetadata::FirstPartySetMetadata() = default;
+ FirstPartySetMetadata::FirstPartySetMetadata(
++    const SamePartyContext& context,
+     const FirstPartySetEntry* frame_entry,
+     const FirstPartySetEntry* top_frame_entry)
+-    : frame_entry_(base::OptionalFromPtr(frame_entry)),
++    : context_(context),
++      frame_entry_(base::OptionalFromPtr(frame_entry)),
+       top_frame_entry_(base::OptionalFromPtr(top_frame_entry)) {}
+ 
+ FirstPartySetMetadata::FirstPartySetMetadata(FirstPartySetMetadata&&) = default;
+@@ -26,8 +28,8 @@ FirstPartySetMetadata::~FirstPartySetMetadata() = default;
+ 
+ bool FirstPartySetMetadata::operator==(
+     const FirstPartySetMetadata& other) const {
+-  return std::tie(frame_entry_, top_frame_entry_) ==
+-         std::tie(other.frame_entry_, other.top_frame_entry_);
++  return std::tie(context_, frame_entry_, top_frame_entry_) ==
++         std::tie(other.context_, other.frame_entry_, other.top_frame_entry_);
+ }
+ 
+ bool FirstPartySetMetadata::operator!=(
+@@ -37,7 +39,8 @@ bool FirstPartySetMetadata::operator!=(
+ 
+ std::ostream& operator<<(std::ostream& os,
+                          const FirstPartySetMetadata& metadata) {
+-  os << "{" << base::OptionalToPtr(metadata.frame_entry()) << ", "
++  os << "{" << metadata.context() << ", "
++     << base::OptionalToPtr(metadata.frame_entry()) << ", "
+      << base::OptionalToPtr(metadata.top_frame_entry()) << "}";
+   return os;
+ }
+diff --git a/net/first_party_sets/first_party_set_metadata.h b/net/first_party_sets/first_party_set_metadata.h
+index b23e52575bc2ae0634075890782eb46716116217..77cf13c75599522be3efda658b8685b21532c41c 100644
+--- a/net/first_party_sets/first_party_set_metadata.h
++++ b/net/first_party_sets/first_party_set_metadata.h
+@@ -7,6 +7,7 @@
+ 
+ #include "net/base/net_export.h"
+ #include "net/first_party_sets/first_party_set_entry.h"
++#include "net/first_party_sets/same_party_context.h"
+ #include "third_party/abseil-cpp/absl/types/optional.h"
+ 
+ namespace net {
+@@ -20,7 +21,8 @@ class NET_EXPORT FirstPartySetMetadata {
+   // `frame_entry` and `top_frame_entry` must live for the duration of the ctor;
+   // nullptr indicates that there's no First-Party Set that's associated with
+   // the current frame or the top frame, respectively, in the given context.
+-  FirstPartySetMetadata(const FirstPartySetEntry* frame_entry,
++  FirstPartySetMetadata(const SamePartyContext& context,
++                        const FirstPartySetEntry* frame_entry,
+                         const FirstPartySetEntry* top_frame_entry);
+ 
+   FirstPartySetMetadata(FirstPartySetMetadata&&);
+@@ -31,6 +33,10 @@ class NET_EXPORT FirstPartySetMetadata {
+   bool operator==(const FirstPartySetMetadata& other) const;
+   bool operator!=(const FirstPartySetMetadata& other) const;
+ 
++  const SamePartyContext& context() const { return context_; }
++
++  // Returns a optional<T>& instead of a T* so that operator== can be defined
++  // more easily.
+   const absl::optional<FirstPartySetEntry>& frame_entry() const {
+     return frame_entry_;
+   }
+@@ -43,6 +49,7 @@ class NET_EXPORT FirstPartySetMetadata {
+   bool AreSitesInSameFirstPartySet() const;
+ 
+  private:
++  SamePartyContext context_ = SamePartyContext();
+   absl::optional<FirstPartySetEntry> frame_entry_ = absl::nullopt;
+   absl::optional<FirstPartySetEntry> top_frame_entry_ = absl::nullopt;
+ };
+diff --git a/net/first_party_sets/global_first_party_sets.cc b/net/first_party_sets/global_first_party_sets.cc
+index 29d34cd442ae19006d399b09b766c19dd5b12c76..d90a01fde8f9692825e5b7bb0c90c32b48d60e65 100644
+--- a/net/first_party_sets/global_first_party_sets.cc
++++ b/net/first_party_sets/global_first_party_sets.cc
+@@ -28,6 +28,13 @@ namespace {
+ using FlattenedSets = base::flat_map<SchemefulSite, FirstPartySetEntry>;
+ using SingleSet = base::flat_map<SchemefulSite, FirstPartySetEntry>;
+ 
++// Converts WS to HTTP, and WSS to HTTPS.
++SchemefulSite NormalizeScheme(const SchemefulSite& site) {
++  SchemefulSite normalized_site = site;
++  normalized_site.ConvertWebSocketToHttp();
++  return normalized_site;
++}
++
+ // Converts a list of First-Party Sets from a SingleSet to a FlattenedSet
+ // representation.
+ FlattenedSets SetListToFlattenedSets(const std::vector<SingleSet>& set_list) {
+@@ -59,6 +66,11 @@ const SchemefulSite& ProjectKey(
+   return p.first;
+ }
+ 
++SamePartyContext::Type ContextTypeFromBool(bool is_same_party) {
++  return is_same_party ? SamePartyContext::Type::kSameParty
++                       : SamePartyContext::Type::kCrossParty;
++}
++
+ }  // namespace
+ 
+ GlobalFirstPartySets::GlobalFirstPartySets() = default;
+@@ -126,9 +138,11 @@ absl::optional<FirstPartySetEntry> GlobalFirstPartySets::FindEntry(
+ absl::optional<FirstPartySetEntry> GlobalFirstPartySets::FindEntry(
+     const SchemefulSite& site,
+     const FirstPartySetsContextConfig* config) const {
+-  // Check if `site` can be found in the customizations first.
++  const SchemefulSite normalized_site = NormalizeScheme(site);
++
++  // Check if `normalized_site` can be found in the customizations first.
+   if (config) {
+-    if (const auto override = config->FindOverride(site);
++    if (const auto override = config->FindOverride(normalized_site);
+         override.has_value()) {
+       return override->IsDeletion() ? absl::nullopt
+                                     : absl::make_optional(override->GetEntry());
+@@ -136,7 +150,7 @@ absl::optional<FirstPartySetEntry> GlobalFirstPartySets::FindEntry(
+   }
+ 
+   // Now see if it's in the manual config (with or without a manual alias).
+-  if (const auto manual_override = manual_config_.FindOverride(site);
++  if (const auto manual_override = manual_config_.FindOverride(normalized_site);
+       manual_override.has_value()) {
+     return manual_override->IsDeletion()
+                ? absl::nullopt
+@@ -144,9 +158,9 @@ absl::optional<FirstPartySetEntry> GlobalFirstPartySets::FindEntry(
+   }
+ 
+   // Finally, look up in `entries_`, applying an alias if applicable.
+-  const auto canonical_it = aliases_.find(site);
++  const auto canonical_it = aliases_.find(normalized_site);
+   const SchemefulSite& canonical_site =
+-      canonical_it == aliases_.end() ? site : canonical_it->second;
++      canonical_it == aliases_.end() ? normalized_site : canonical_it->second;
+   if (const auto entry_it = entries_.find(canonical_site);
+       entry_it != entries_.end()) {
+     return entry_it->second;
+@@ -172,16 +186,48 @@ GlobalFirstPartySets::FindEntries(
+ FirstPartySetMetadata GlobalFirstPartySets::ComputeMetadata(
+     const SchemefulSite& site,
+     const SchemefulSite* top_frame_site,
++    const std::set<SchemefulSite>& party_context,
+     const FirstPartySetsContextConfig& fps_context_config) const {
++  SamePartyContext::Type context_type =
++      ContextTypeFromBool(IsContextSamePartyWithSite(
++          site, top_frame_site, party_context, fps_context_config));
++
++  SamePartyContext context(context_type);
++
+   absl::optional<FirstPartySetEntry> top_frame_entry =
+       top_frame_site ? FindEntry(*top_frame_site, fps_context_config)
+                      : absl::nullopt;
+ 
+   return FirstPartySetMetadata(
+-      base::OptionalToPtr(FindEntry(site, fps_context_config)),
++      context, base::OptionalToPtr(FindEntry(site, fps_context_config)),
+       base::OptionalToPtr(top_frame_entry));
+ }
+ 
++bool GlobalFirstPartySets::IsContextSamePartyWithSite(
++    const SchemefulSite& site,
++    const SchemefulSite* top_frame_site,
++    const std::set<SchemefulSite>& party_context,
++    const FirstPartySetsContextConfig& fps_context_config) const {
++  const absl::optional<FirstPartySetEntry> site_entry =
++      FindEntry(site, fps_context_config);
++  if (!site_entry.has_value())
++    return false;
++
++  const auto is_in_same_set_as_frame_site =
++      [this, &site_entry,
++       &fps_context_config](const SchemefulSite& context_site) -> bool {
++    const absl::optional<FirstPartySetEntry> context_entry =
++        FindEntry(context_site, fps_context_config);
++    return context_entry.has_value() &&
++           context_entry->primary() == site_entry->primary();
++  };
++
++  if (top_frame_site && !is_in_same_set_as_frame_site(*top_frame_site))
++    return false;
++
++  return base::ranges::all_of(party_context, is_in_same_set_as_frame_site);
++}
++
+ void GlobalFirstPartySets::ApplyManuallySpecifiedSet(
+     const base::flat_map<SchemefulSite, FirstPartySetEntry>& manual_entries) {
+   CHECK(manual_config_.empty());
+diff --git a/net/first_party_sets/global_first_party_sets.h b/net/first_party_sets/global_first_party_sets.h
+index f4f4e99189e0f3b8accfa884e136a127d0128bab..cc2b42c89deb47fda879a5bef2c4a7f9989c139d 100644
+--- a/net/first_party_sets/global_first_party_sets.h
++++ b/net/first_party_sets/global_first_party_sets.h
+@@ -83,6 +83,7 @@ class NET_EXPORT GlobalFirstPartySets {
+   FirstPartySetMetadata ComputeMetadata(
+       const SchemefulSite& site,
+       const SchemefulSite* top_frame_site,
++      const std::set<SchemefulSite>& party_context,
+       const FirstPartySetsContextConfig& fps_context_config) const;
+ 
+   // Modifies this instance such that it will respect the given
+@@ -166,6 +167,18 @@ class NET_EXPORT GlobalFirstPartySets {
+       const std::vector<base::flat_map<SchemefulSite, FirstPartySetEntry>>&
+           addition_sets) const;
+ 
++  // Returns whether `site` is same-party with `party_context`, and
++  // `top_frame_site` (if it is not nullptr). That is, is `site`'s primary the
++  // same as the primaries of every member of `party_context` and of
++  // `top_frame_site`? Note: if `site` is not a member of a First-Party Set,
++  // then this returns false. If `top_frame_site` is nullptr, then it is
++  // ignored.
++  bool IsContextSamePartyWithSite(
++      const SchemefulSite& site,
++      const SchemefulSite* top_frame_site,
++      const std::set<SchemefulSite>& party_context,
++      const FirstPartySetsContextConfig& fps_context_config) const;
++
+   // Same as the public version of ForEachEffectiveSetEntry, but is allowed to
+   // omit the `config` argument (i.e. pass nullptr instead of a reference).
+   bool ForEachEffectiveSetEntry(
+diff --git a/net/first_party_sets/same_party_context.cc b/net/first_party_sets/same_party_context.cc
+new file mode 100644
+index 0000000000000000000000000000000000000000..afa9ddf645cc8776d7c9303da9d93725b945a5d7
+--- /dev/null
++++ b/net/first_party_sets/same_party_context.cc
+@@ -0,0 +1,28 @@
++// Copyright 2021 The Chromium Authors
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "net/first_party_sets/same_party_context.h"
++
++#include <ostream>
++
++namespace net {
++
++SamePartyContext::SamePartyContext(Type context_type)
++    : context_type_(context_type) {}
++
++bool SamePartyContext::operator==(const SamePartyContext& other) const {
++  return context_type_ == other.context_type_;
++}
++
++std::ostream& operator<<(std::ostream& os, const SamePartyContext& spc) {
++  os << "{" << static_cast<int>(spc.context_type()) << "}";
++  return os;
++}
++
++// static
++SamePartyContext SamePartyContext::MakeInclusive() {
++  return SamePartyContext(Type::kSameParty);
++}
++
++}  // namespace net
+diff --git a/net/first_party_sets/same_party_context.h b/net/first_party_sets/same_party_context.h
+new file mode 100644
+index 0000000000000000000000000000000000000000..448f000d8ab368f071d73da887e0e857c33e8973
+--- /dev/null
++++ b/net/first_party_sets/same_party_context.h
+@@ -0,0 +1,51 @@
++// Copyright 2021 The Chromium Authors
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef NET_FIRST_PARTY_SETS_SAME_PARTY_CONTEXT_H_
++#define NET_FIRST_PARTY_SETS_SAME_PARTY_CONTEXT_H_
++
++#include <ostream>
++
++#include "net/base/net_export.h"
++
++namespace net {
++
++// This struct bundles together a few different notions of same-party-ness.
++// `context_type()` gives the notion of same-party-ness that Chromium should use
++// in all cases except metrics; other accessors are just for metrics purposes,
++// to explore the impact of different definitions of "same-party".
++class NET_EXPORT SamePartyContext {
++ public:
++  // Computed for every cookie access attempt but is only relevant for SameParty
++  // cookies.
++  enum class Type {
++    // The opposite to kSameParty. Should be the default value.
++    kCrossParty = 0,
++    // If the request URL is in the same First-Party Sets as the top-frame site
++    // and each member of the isolation_info.party_context.
++    kSameParty = 1,
++  };
++
++  SamePartyContext() = default;
++  explicit SamePartyContext(Type context_type);
++
++  bool operator==(const SamePartyContext& other) const;
++
++  // How trusted is the current browser environment when it comes to accessing
++  // SameParty cookies. Default is not trusted, e.g. kCrossParty.
++  Type context_type() const { return context_type_; }
++
++  // Creates a SamePartyContext that is as permissive as possible.
++  static SamePartyContext MakeInclusive();
++
++ private:
++  Type context_type_ = Type::kCrossParty;
++};
++
++NET_EXPORT std::ostream& operator<<(std::ostream& os,
++                                    const SamePartyContext& spc);
++
++}  // namespace net
++
++#endif  // NET_FIRST_PARTY_SETS_SAME_PARTY_CONTEXT_H_
+diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc
+index b5e18cf187f1ca0618eaac4b1453fa4ffdb3bb9d..d4a5e60a60f372fccbf54ead93666f9856b972de 100644
+--- a/net/url_request/url_request.cc
++++ b/net/url_request/url_request.cc
+@@ -1216,7 +1216,7 @@ IsolationInfo URLRequest::CreateIsolationInfoFromNetworkAnonymizationKey(
+   auto isolation_info = IsolationInfo::Create(
+       IsolationInfo::RequestType::kOther, top_frame_origin,
+       frame_origin.value(), SiteForCookies(),
+-      network_anonymization_key.GetNonce());
++      /*party_context=*/absl::nullopt, network_anonymization_key.GetNonce());
+   // TODO(crbug/1343856): DCHECK isolation info is fully populated.
+   return isolation_info;
+ }
+diff --git a/net/url_request/url_request.h b/net/url_request/url_request.h
+index acd9a595a6c83bf58153354da91cb1d23e0f7235..9668aed7e3dde807e040ab71b442d81ca7e1c9b7 100644
+--- a/net/url_request/url_request.h
++++ b/net/url_request/url_request.h
+@@ -315,6 +315,16 @@ class NET_EXPORT URLRequest : public base::SupportsUserData {
+     force_ignore_site_for_cookies_ = attach;
+   }
+ 
++  // Indicates whether the top frame party will be considered same-party to the
++  // request URL (regardless of what it is), for the purpose of SameParty
++  // cookies.
++  bool force_ignore_top_frame_party_for_cookies() const {
++    return force_ignore_top_frame_party_for_cookies_;
++  }
++  void set_force_ignore_top_frame_party_for_cookies(bool force) {
++    force_ignore_top_frame_party_for_cookies_ = force;
++  }
++
+   // Indicates if the request should be treated as a main frame navigation for
+   // SameSite cookie computations.  This flag overrides the IsolationInfo
+   // request type associated with fetches from a service worker context.
+@@ -961,6 +971,7 @@ class NET_EXPORT URLRequest : public base::SupportsUserData {
+   absl::optional<CookiePartitionKey> cookie_partition_key_ = absl::nullopt;
+ 
+   bool force_ignore_site_for_cookies_ = false;
++  bool force_ignore_top_frame_party_for_cookies_ = false;
+   bool force_main_frame_for_same_site_cookies_ = false;
+   CookieSettingOverrides cookie_setting_overrides_;
+ 
+diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
+index 689818d34e56eeaa91605bfeeeb552ec86bc4b14..c8730d3c1ab22d28330152d7b58b1d99a53b9d00 100644
+--- a/net/url_request/url_request_http_job.cc
++++ b/net/url_request/url_request_http_job.cc
+@@ -62,7 +62,7 @@
+ #include "net/filter/zstd_source_stream.h"
+ #include "net/first_party_sets/first_party_set_entry.h"
+ #include "net/first_party_sets/first_party_set_metadata.h"
+-#include "net/first_party_sets/first_party_sets_cache_filter.h"
++#include "net/first_party_sets/same_party_context.h"
+ #include "net/http/http_content_disposition.h"
+ #include "net/http/http_log_util.h"
+ #include "net/http/http_network_session.h"
+@@ -165,11 +165,22 @@ void LogTrustAnchor(const net::HashValueVector& spki_hashes) {
+ }
+ 
+ net::CookieOptions CreateCookieOptions(
+-    net::CookieOptions::SameSiteCookieContext same_site_context) {
++    net::CookieOptions::SameSiteCookieContext same_site_context,
++    const net::SamePartyContext& same_party_context,
++    const net::IsolationInfo& isolation_info,
++    bool is_in_nontrivial_first_party_set) {
+   net::CookieOptions options;
+   options.set_return_excluded_cookies();
+   options.set_include_httponly();
+   options.set_same_site_cookie_context(same_site_context);
++  options.set_same_party_context(same_party_context);
++  if (isolation_info.party_context().has_value()) {
++    // Count the top-frame site since it's not in the party_context.
++    options.set_full_party_context_size(isolation_info.party_context()->size() +
++                                        1);
++  }
++  options.set_is_in_nontrivial_first_party_set(
++      is_in_nontrivial_first_party_set);
+   return options;
+ }
+ 
+@@ -318,33 +329,53 @@ void URLRequestHttpJob::Start() {
+           request_initiator_site().value() ==
+               net::SchemefulSite(request()->url()));
+ 
++  bool should_add_cookie_header = ShouldAddCookieHeader();
+   UMA_HISTOGRAM_BOOLEAN("Net.HttpJob.CanIncludeCookies",
+-                        ShouldAddCookieHeader());
+-
+-  CookieStore* cookie_store = request()->context()->cookie_store();
+-  const CookieAccessDelegate* delegate =
+-      cookie_store ? cookie_store->cookie_access_delegate() : nullptr;
++                        should_add_cookie_header);
+ 
+   request_->net_log().BeginEvent(NetLogEventType::FIRST_PARTY_SETS_METADATA);
+ 
+-  absl::optional<
+-      std::pair<FirstPartySetMetadata, FirstPartySetsCacheFilter::MatchInfo>>
+-      maybe_metadata = cookie_util::ComputeFirstPartySetMetadataMaybeAsync(
++  if (!should_add_cookie_header) {
++    OnGotFirstPartySetMetadata(FirstPartySetMetadata());
++    return;
++  }
++
++  absl::optional<FirstPartySetMetadata> metadata =
++      cookie_util::ComputeFirstPartySetMetadataMaybeAsync(
+           SchemefulSite(request()->url()), request()->isolation_info(),
+-          delegate,
++          request()->context()->cookie_store()->cookie_access_delegate(),
++          request()->force_ignore_top_frame_party_for_cookies(),
+           base::BindOnce(&URLRequestHttpJob::OnGotFirstPartySetMetadata,
+                          weak_factory_.GetWeakPtr()));
+ 
+-  if (maybe_metadata.has_value()) {
+-    auto [metadata, match_info] = std::move(maybe_metadata).value();
+-    OnGotFirstPartySetMetadata(std::move(metadata), std::move(match_info));
+-  }
++  if (metadata.has_value())
++    OnGotFirstPartySetMetadata(std::move(metadata.value()));
+ }
+ 
+ void URLRequestHttpJob::OnGotFirstPartySetMetadata(
+-    FirstPartySetMetadata first_party_set_metadata,
+-    FirstPartySetsCacheFilter::MatchInfo match_info) {
++    FirstPartySetMetadata first_party_set_metadata) {
+   first_party_set_metadata_ = std::move(first_party_set_metadata);
++
++  if (!request()->network_delegate()) {
++    OnGotFirstPartySetCacheFilterMatchInfo(
++        net::FirstPartySetsCacheFilter::MatchInfo());
++    return;
++  }
++  absl::optional<FirstPartySetsCacheFilter::MatchInfo> match_info =
++      request()
++          ->network_delegate()
++          ->GetFirstPartySetsCacheFilterMatchInfoMaybeAsync(
++              SchemefulSite(request()->url()),
++              base::BindOnce(
++                  &URLRequestHttpJob::OnGotFirstPartySetCacheFilterMatchInfo,
++                  weak_factory_.GetWeakPtr()));
++
++  if (match_info.has_value())
++    OnGotFirstPartySetCacheFilterMatchInfo(std::move(match_info.value()));
++}
++
++void URLRequestHttpJob::OnGotFirstPartySetCacheFilterMatchInfo(
++    FirstPartySetsCacheFilter::MatchInfo match_info) {
+   request_info_.fps_cache_filter = match_info.clear_at_run_id;
+   request_info_.browser_run_id = match_info.browser_run_id;
+ 
+@@ -676,7 +707,11 @@ void URLRequestHttpJob::AddCookieHeaderAndStart() {
+           request_->site_for_cookies(), request_->initiator(),
+           is_main_frame_navigation, force_ignore_site_for_cookies);
+ 
+-  CookieOptions options = CreateCookieOptions(same_site_context);
++  bool is_in_nontrivial_first_party_set =
++      first_party_set_metadata_.frame_entry().has_value();
++  CookieOptions options = CreateCookieOptions(
++      same_site_context, first_party_set_metadata_.context(),
++      request_->isolation_info(), is_in_nontrivial_first_party_set);
+ 
+   cookie_store->GetCookieListWithOptionsAsync(
+       request_->url(), options,
+@@ -927,7 +962,11 @@ void URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete(int result) {
+           request_->initiator(), is_main_frame_navigation,
+           force_ignore_site_for_cookies);
+ 
+-  CookieOptions options = CreateCookieOptions(same_site_context);
++  bool is_in_nontrivial_first_party_set =
++      first_party_set_metadata_.frame_entry().has_value();
++  CookieOptions options = CreateCookieOptions(
++      same_site_context, first_party_set_metadata_.context(),
++      request_->isolation_info(), is_in_nontrivial_first_party_set);
+ 
+   // Set all cookies, without waiting for them to be set. Any subsequent
+   // read will see the combined result of all cookie operation.
+diff --git a/net/url_request/url_request_http_job.h b/net/url_request/url_request_http_job.h
+index a629e89cef3675c4ffe1ad56e17d4d595a51e3bd..78c8fbb6bfa39535c4fc2ae49dafa5a4ad001cd5 100644
+--- a/net/url_request/url_request_http_job.h
++++ b/net/url_request/url_request_http_job.h
+@@ -216,7 +216,11 @@ class NET_EXPORT_PRIVATE URLRequestHttpJob : public URLRequestJob {
+ 
+   // Called after getting the FirstPartySetMetadata during Start for this job.
+   void OnGotFirstPartySetMetadata(
+-      FirstPartySetMetadata first_party_set_metadata,
++      FirstPartySetMetadata first_party_set_metadata);
++
++  // Called after getting the FirstPartySetsCacheFilter match info during Start
++  // for this job.
++  void OnGotFirstPartySetCacheFilterMatchInfo(
+       FirstPartySetsCacheFilter::MatchInfo match_info);
+ 
+   // Returns true iff this request leg should include the Cookie header. Note
+diff --git a/services/network/cookie_access_delegate_impl.cc b/services/network/cookie_access_delegate_impl.cc
+index 5e0d27953bf8d8770ad72559183ea5f56258cfa0..9d8c6c16d97fb1f4e7393656d40907b457d89818 100644
+--- a/services/network/cookie_access_delegate_impl.cc
++++ b/services/network/cookie_access_delegate_impl.cc
+@@ -13,7 +13,6 @@
+ #include "net/cookies/cookie_constants.h"
+ #include "net/cookies/cookie_util.h"
+ #include "net/first_party_sets/first_party_set_metadata.h"
+-#include "net/first_party_sets/first_party_sets_cache_filter.h"
+ #include "services/network/public/cpp/is_potentially_trustworthy.h"
+ #include "third_party/abseil-cpp/absl/types/optional.h"
+ 
+@@ -61,20 +60,16 @@ bool CookieAccessDelegateImpl::ShouldIgnoreSameSiteRestrictions(
+   return false;
+ }
+ 
+-absl::optional<std::pair<net::FirstPartySetMetadata,
+-                         net::FirstPartySetsCacheFilter::MatchInfo>>
++absl::optional<net::FirstPartySetMetadata>
+ CookieAccessDelegateImpl::ComputeFirstPartySetMetadataMaybeAsync(
+     const net::SchemefulSite& site,
+     const net::SchemefulSite* top_frame_site,
+-    base::OnceCallback<void(net::FirstPartySetMetadata,
+-                            net::FirstPartySetsCacheFilter::MatchInfo)>
+-        callback) const {
+-  if (!first_party_sets_access_delegate_) {
+-    return std::make_pair(net::FirstPartySetMetadata(),
+-                          net::FirstPartySetsCacheFilter::MatchInfo());
+-  }
++    const std::set<net::SchemefulSite>& party_context,
++    base::OnceCallback<void(net::FirstPartySetMetadata)> callback) const {
++  if (!first_party_sets_access_delegate_)
++    return {net::FirstPartySetMetadata()};
+   return first_party_sets_access_delegate_->ComputeMetadata(
+-      site, top_frame_site, std::move(callback));
++      site, top_frame_site, party_context, std::move(callback));
+ }
+ 
+ absl::optional<FirstPartySetsAccessDelegate::EntriesResult>
+diff --git a/services/network/cookie_access_delegate_impl.h b/services/network/cookie_access_delegate_impl.h
+index 72a96c033d0c3eb3555cff718c66d7775cf4800c..50c3a309db86e5f044e2bfc429ca82fc6586d084 100644
+--- a/services/network/cookie_access_delegate_impl.h
++++ b/services/network/cookie_access_delegate_impl.h
+@@ -5,7 +5,10 @@
+ #ifndef SERVICES_NETWORK_COOKIE_ACCESS_DELEGATE_IMPL_H_
+ #define SERVICES_NETWORK_COOKIE_ACCESS_DELEGATE_IMPL_H_
+ 
++#include <set>
++
+ #include "base/component_export.h"
++#include "base/containers/flat_map.h"
+ #include "base/containers/flat_set.h"
+ #include "base/functional/callback_forward.h"
+ #include "base/memory/raw_ptr.h"
+@@ -13,7 +16,6 @@
+ #include "net/cookies/cookie_access_delegate.h"
+ #include "net/cookies/cookie_constants.h"
+ #include "net/first_party_sets/first_party_set_metadata.h"
+-#include "net/first_party_sets/first_party_sets_cache_filter.h"
+ #include "services/network/cookie_settings.h"
+ #include "services/network/first_party_sets/first_party_sets_access_delegate.h"
+ #include "services/network/public/mojom/cookie_manager.mojom.h"
+@@ -53,15 +55,13 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) CookieAccessDelegateImpl
+   bool ShouldIgnoreSameSiteRestrictions(
+       const GURL& url,
+       const net::SiteForCookies& site_for_cookies) const override;
+-  [[nodiscard]] absl::optional<
+-      std::pair<net::FirstPartySetMetadata,
+-                net::FirstPartySetsCacheFilter::MatchInfo>>
++  [[nodiscard]] absl::optional<net::FirstPartySetMetadata>
+   ComputeFirstPartySetMetadataMaybeAsync(
+       const net::SchemefulSite& site,
+       const net::SchemefulSite* top_frame_site,
+-      base::OnceCallback<void(net::FirstPartySetMetadata,
+-                              net::FirstPartySetsCacheFilter::MatchInfo)>
+-          callback) const override;
++      const std::set<net::SchemefulSite>& party_context,
++      base::OnceCallback<void(net::FirstPartySetMetadata)> callback)
++      const override;
+   [[nodiscard]] absl::optional<FirstPartySetsAccessDelegate::EntriesResult>
+   FindFirstPartySetEntries(
+       const base::flat_set<net::SchemefulSite>& sites,
+diff --git a/services/network/first_party_sets/first_party_sets_access_delegate.cc b/services/network/first_party_sets/first_party_sets_access_delegate.cc
+index ed35e2dc6f5b0e2867cfdc5fb118e0dd45324a13..c07f0dc2d8a146cf187b204db4f2a9bceca6ceac 100644
+--- a/services/network/first_party_sets/first_party_sets_access_delegate.cc
++++ b/services/network/first_party_sets/first_party_sets_access_delegate.cc
+@@ -65,24 +65,20 @@ void FirstPartySetsAccessDelegate::SetEnabled(bool enabled) {
+   enabled_ = enabled;
+ }
+ 
+-absl::optional<std::pair<net::FirstPartySetMetadata,
+-                         net::FirstPartySetsCacheFilter::MatchInfo>>
++absl::optional<net::FirstPartySetMetadata>
+ FirstPartySetsAccessDelegate::ComputeMetadata(
+     const net::SchemefulSite& site,
+     const net::SchemefulSite* top_frame_site,
+-    base::OnceCallback<void(net::FirstPartySetMetadata,
+-                            net::FirstPartySetsCacheFilter::MatchInfo)>
+-        callback) {
++    const std::set<net::SchemefulSite>& party_context,
++    base::OnceCallback<void(net::FirstPartySetMetadata)> callback) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ 
+   if (!enabled_) {
+-    return std::make_pair(net::FirstPartySetMetadata(),
+-                          net::FirstPartySetsCacheFilter::MatchInfo());
++    return {net::FirstPartySetMetadata()};
+   }
+   if (!ready_event_.has_value()) {
+     if (!wait_for_init_) {
+-      return std::make_pair(net::FirstPartySetMetadata(),
+-                            net::FirstPartySetsCacheFilter::MatchInfo());
++      return {net::FirstPartySetMetadata()};
+     }
+     // base::Unretained() is safe because `this` owns `pending_queries_` and
+     // `pending_queries_` will not run the enqueued callbacks after `this` is
+@@ -90,29 +86,12 @@ FirstPartySetsAccessDelegate::ComputeMetadata(
+     EnqueuePendingQuery(base::BindOnce(
+         &FirstPartySetsAccessDelegate::ComputeMetadataAndInvoke,
+         base::Unretained(this), site, base::OptionalFromPtr(top_frame_site),
+-        std::move(callback)));
++        party_context, std::move(callback)));
+     return absl::nullopt;
+   }
+ 
+-  net::FirstPartySetsCacheFilter::MatchInfo match_info(
+-      cache_filter()->GetMatchInfo(site));
+-
+-  absl::optional<net::FirstPartySetMetadata> metadata =
+-      manager_->ComputeMetadata(
+-          site, top_frame_site, *context_config(),
+-          base::BindOnce(
+-              [](base::OnceCallback<void(
+-                     net::FirstPartySetMetadata,
+-                     net::FirstPartySetsCacheFilter::MatchInfo)> callback,
+-                 net::FirstPartySetsCacheFilter::MatchInfo info,
+-                 net::FirstPartySetMetadata metadata) {
+-                std::move(callback).Run(std::move(metadata), std::move(info));
+-              },
+-              std::move(callback), match_info));
+-
+-  return metadata.has_value() ? absl::make_optional(std::make_pair(
+-                                    std::move(metadata).value(), match_info))
+-                              : absl::nullopt;
++  return manager_->ComputeMetadata(site, top_frame_site, party_context,
++                                   *context_config(), std::move(callback));
+ }
+ 
+ absl::optional<FirstPartySetsAccessDelegate::EntriesResult>
+@@ -141,13 +120,37 @@ FirstPartySetsAccessDelegate::FindEntries(
+   return manager_->FindEntries(sites, *context_config(), std::move(callback));
+ }
+ 
++absl::optional<net::FirstPartySetsCacheFilter::MatchInfo>
++FirstPartySetsAccessDelegate::GetCacheFilterMatchInfo(
++    const net::SchemefulSite& site,
++    base::OnceCallback<void(net::FirstPartySetsCacheFilter::MatchInfo)>
++        callback) {
++  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
++
++  if (!enabled_)
++    return {net::FirstPartySetsCacheFilter::MatchInfo()};
++
++  if (!ready_event_.has_value()) {
++    if (!wait_for_init_) {
++      return {net::FirstPartySetsCacheFilter::MatchInfo()};
++    }
++    // base::Unretained() is safe because `this` owns `pending_queries_` and
++    // `pending_queries_` will not run the enqueued callbacks after `this` is
++    // destroyed.
++    EnqueuePendingQuery(base::BindOnce(
++        &FirstPartySetsAccessDelegate::GetCacheFilterMatchInfoAndInvoke,
++        base::Unretained(this), site, std::move(callback)));
++    return absl::nullopt;
++  }
++
++  return cache_filter()->GetMatchInfo(site);
++}
++
+ void FirstPartySetsAccessDelegate::ComputeMetadataAndInvoke(
+     const net::SchemefulSite& site,
+     const absl::optional<net::SchemefulSite> top_frame_site,
+-    base::OnceCallback<void(net::FirstPartySetMetadata,
+-                            net::FirstPartySetsCacheFilter::MatchInfo)>
+-        callback) const {
+-  using CallbackType = decltype(callback);
++    const std::set<net::SchemefulSite>& party_context,
++    base::OnceCallback<void(net::FirstPartySetMetadata)> callback) const {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+   CHECK(context_config());
+   // NB: since `ComputeMetadata` returns early if the delegate is disabled,
+@@ -155,26 +158,17 @@ void FirstPartySetsAccessDelegate::ComputeMetadataAndInvoke(
+   // enabled when the query was received. However, the delegate may have been
+   // disabled between then and now, so we have no guarantees re: `enabled_` now.
+ 
+-  std::pair<CallbackType, CallbackType> callbacks =
+-      base::SplitOnceCallback(std::move(callback));
+-
+-  net::FirstPartySetsCacheFilter::MatchInfo match_info(
+-      cache_filter()->GetMatchInfo(site));
++  std::pair<base::OnceCallback<void(net::FirstPartySetMetadata)>,
++            base::OnceCallback<void(net::FirstPartySetMetadata)>>
++      callbacks = base::SplitOnceCallback(std::move(callback));
+ 
+   absl::optional<net::FirstPartySetMetadata> sync_result =
+-      manager_->ComputeMetadata(
+-          site, base::OptionalToPtr(top_frame_site), *context_config(),
+-          base::BindOnce(
+-              [](CallbackType callback,
+-                 net::FirstPartySetsCacheFilter::MatchInfo match_info,
+-                 net::FirstPartySetMetadata metadata) {
+-                std::move(callback).Run(std::move(metadata), match_info);
+-              },
+-              std::move(callbacks.first), match_info));
+-
+-  if (sync_result.has_value()) {
+-    std::move(callbacks.second).Run(std::move(sync_result.value()), match_info);
+-  }
++      manager_->ComputeMetadata(site, base::OptionalToPtr(top_frame_site),
++                                party_context, *context_config(),
++                                std::move(callbacks.first));
++
++  if (sync_result.has_value())
++    std::move(callbacks.second).Run(std::move(sync_result.value()));
+ }
+ 
+ void FirstPartySetsAccessDelegate::FindEntriesAndInvoke(
+@@ -201,6 +195,20 @@ void FirstPartySetsAccessDelegate::FindEntriesAndInvoke(
+     std::move(callbacks.second).Run(sync_result.value());
+ }
+ 
++void FirstPartySetsAccessDelegate::GetCacheFilterMatchInfoAndInvoke(
++    const net::SchemefulSite& site,
++    base::OnceCallback<void(net::FirstPartySetsCacheFilter::MatchInfo)>
++        callback) const {
++  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
++  CHECK(cache_filter());
++  // NB: since `GetCacheFilterMatchInfo` returns early if the delegate is
++  // disabled, we're guaranteed that for any queued query, the delegate must
++  // have been enabled when the query was received. However, the delegate may
++  // have been disabled between then and now, so we have no guarantees re:
++  // `enabled_` now.
++  std::move(callback).Run(cache_filter()->GetMatchInfo(site));
++}
++
+ void FirstPartySetsAccessDelegate::InvokePendingQueries() {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+   CHECK(ready_event_.has_value());
+diff --git a/services/network/first_party_sets/first_party_sets_access_delegate.h b/services/network/first_party_sets/first_party_sets_access_delegate.h
+index d8a717465c10ed97863aec940987c1dd5814ed83..67df37ef7dfc344bff9bb569fdc58c29967aeb22 100644
+--- a/services/network/first_party_sets/first_party_sets_access_delegate.h
++++ b/services/network/first_party_sets/first_party_sets_access_delegate.h
+@@ -53,22 +53,17 @@ class FirstPartySetsAccessDelegate
+   void NotifyReady(mojom::FirstPartySetsReadyEventPtr ready_event) override;
+   void SetEnabled(bool enabled) override;
+ 
+-  // Computes the First-Party Set metadata and cache filter match info related
+-  // to the given context.
++  // Computes the First-Party Set metadata related to the given context.
+   //
+   // This may return a result synchronously, or asynchronously invoke `callback`
+   // with the result. The callback will be invoked iff the return value is
+   // nullopt; i.e. a result will be provided via return value or callback, but
+   // not both, and not neither.
+-  [[nodiscard]] absl::optional<
+-      std::pair<net::FirstPartySetMetadata,
+-                net::FirstPartySetsCacheFilter::MatchInfo>>
+-  ComputeMetadata(
++  [[nodiscard]] absl::optional<net::FirstPartySetMetadata> ComputeMetadata(
+       const net::SchemefulSite& site,
+       const net::SchemefulSite* top_frame_site,
+-      base::OnceCallback<void(net::FirstPartySetMetadata,
+-                              net::FirstPartySetsCacheFilter::MatchInfo)>
+-          callback);
++      const std::set<net::SchemefulSite>& party_context,
++      base::OnceCallback<void(net::FirstPartySetMetadata)> callback);
+ 
+   // Calls FirstPartySetsManager::FindEntries either asynchronously or
+   // synchronously, once initialization is complete.
+@@ -81,15 +76,24 @@ class FirstPartySetsAccessDelegate
+       const base::flat_set<net::SchemefulSite>& sites,
+       base::OnceCallback<void(EntriesResult)> callback);
+ 
++  // This may return a result synchronously, or asynchronously invoke `callback`
++  // with the result. The callback will be invoked iff the return value is
++  // nullopt; i.e. a result will be provided via return value or callback, but
++  // not both, and not neither.
++  [[nodiscard]] absl::optional<net::FirstPartySetsCacheFilter::MatchInfo>
++  GetCacheFilterMatchInfo(
++      const net::SchemefulSite& site,
++      base::OnceCallback<void(net::FirstPartySetsCacheFilter::MatchInfo)>
++          callback);
++
+  private:
+   // Same as `ComputeMetadata`, but plumbs the result into the callback. Must
+   // only be called once the instance is fully initialized.
+   void ComputeMetadataAndInvoke(
+       const net::SchemefulSite& site,
+       const absl::optional<net::SchemefulSite> top_frame_site,
+-      base::OnceCallback<void(net::FirstPartySetMetadata,
+-                              net::FirstPartySetsCacheFilter::MatchInfo)>
+-          callback) const;
++      const std::set<net::SchemefulSite>& party_context,
++      base::OnceCallback<void(net::FirstPartySetMetadata)> callback) const;
+ 
+   // Same as `FindEntries`, but plumbs the result into the callback. Must only
+   // be called once the instance is fully initialized.
+@@ -97,6 +101,13 @@ class FirstPartySetsAccessDelegate
+       const base::flat_set<net::SchemefulSite>& sites,
+       base::OnceCallback<void(EntriesResult)> callback) const;
+ 
++  // Same as `GetCacheFilterMatchInfo`, but plumbs the result into the
++  // callback. Must only be called once the instance is fully initialized.
++  void GetCacheFilterMatchInfoAndInvoke(
++      const net::SchemefulSite& site,
++      base::OnceCallback<void(net::FirstPartySetsCacheFilter::MatchInfo)>
++          callback) const;
++
+   // Runs all pending queries. Must not be called until the instance is fully
+   // initialized.
+   void InvokePendingQueries();
+diff --git a/services/network/first_party_sets/first_party_sets_manager.cc b/services/network/first_party_sets/first_party_sets_manager.cc
+index 2ffd2a8be03efcf3900e7bbb4056c722e0dd9130..6cbd2bd9e09b6b7cb4fdb4d853959a1009cbf4a8 100644
+--- a/services/network/first_party_sets/first_party_sets_manager.cc
++++ b/services/network/first_party_sets/first_party_sets_manager.cc
+@@ -15,6 +15,7 @@
+ #include "base/feature_list.h"
+ #include "base/metrics/histogram_functions.h"
+ #include "base/metrics/histogram_macros.h"
++#include "base/ranges/algorithm.h"
+ #include "base/sequence_checker.h"
+ #include "base/time/time.h"
+ #include "base/timer/elapsed_timer.h"
+@@ -24,6 +25,7 @@
+ #include "net/first_party_sets/first_party_set_entry.h"
+ #include "net/first_party_sets/first_party_set_metadata.h"
+ #include "net/first_party_sets/global_first_party_sets.h"
++#include "net/first_party_sets/same_party_context.h"
+ #include "third_party/abseil-cpp/absl/types/optional.h"
+ 
+ namespace network {
+@@ -48,6 +50,7 @@ absl::optional<net::FirstPartySetMetadata>
+ FirstPartySetsManager::ComputeMetadata(
+     const net::SchemefulSite& site,
+     const net::SchemefulSite* top_frame_site,
++    const std::set<net::SchemefulSite>& party_context,
+     const net::FirstPartySetsContextConfig& fps_context_config,
+     base::OnceCallback<void(net::FirstPartySetMetadata)> callback) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+@@ -59,16 +62,19 @@ FirstPartySetsManager::ComputeMetadata(
+     EnqueuePendingQuery(base::BindOnce(
+         &FirstPartySetsManager::ComputeMetadataAndInvoke,
+         weak_factory_.GetWeakPtr(), site, base::OptionalFromPtr(top_frame_site),
+-        fps_context_config.Clone(), std::move(callback), base::ElapsedTimer()));
++        party_context, fps_context_config.Clone(), std::move(callback),
++        base::ElapsedTimer()));
+     return absl::nullopt;
+   }
+ 
+-  return ComputeMetadataInternal(site, top_frame_site, fps_context_config);
++  return ComputeMetadataInternal(site, top_frame_site, party_context,
++                                 fps_context_config);
+ }
+ 
+ void FirstPartySetsManager::ComputeMetadataAndInvoke(
+     const net::SchemefulSite& site,
+     const absl::optional<net::SchemefulSite> top_frame_site,
++    const std::set<net::SchemefulSite>& party_context,
+     const net::FirstPartySetsContextConfig& fps_context_config,
+     base::OnceCallback<void(net::FirstPartySetMetadata)> callback,
+     base::ElapsedTimer timer) const {
+@@ -78,18 +84,21 @@ void FirstPartySetsManager::ComputeMetadataAndInvoke(
+   UMA_HISTOGRAM_TIMES("Cookie.FirstPartySets.EnqueueingDelay.ComputeMetadata2",
+                       timer.Elapsed());
+ 
+-  std::move(callback).Run(ComputeMetadataInternal(
+-      site, base::OptionalToPtr(top_frame_site), fps_context_config));
++  std::move(callback).Run(
++      ComputeMetadataInternal(site, base::OptionalToPtr(top_frame_site),
++                              party_context, fps_context_config));
+ }
+ 
+ net::FirstPartySetMetadata FirstPartySetsManager::ComputeMetadataInternal(
+     const net::SchemefulSite& site,
+     const net::SchemefulSite* top_frame_site,
++    const std::set<net::SchemefulSite>& party_context,
+     const net::FirstPartySetsContextConfig& fps_context_config) const {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+   CHECK(sets_.has_value());
+ 
+-  return sets_->ComputeMetadata(site, top_frame_site, fps_context_config);
++  return sets_->ComputeMetadata(site, top_frame_site, party_context,
++                                fps_context_config);
+ }
+ 
+ absl::optional<net::FirstPartySetEntry> FirstPartySetsManager::FindEntry(
+diff --git a/services/network/first_party_sets/first_party_sets_manager.h b/services/network/first_party_sets/first_party_sets_manager.h
+index 9754dd5f6b0087c6630285b1f592e43cda4ba8d6..b04a98f42fc96a5f2e85cd1bc8ea841fb170e4f2 100644
+--- a/services/network/first_party_sets/first_party_sets_manager.h
++++ b/services/network/first_party_sets/first_party_sets_manager.h
+@@ -15,6 +15,7 @@
+ #include "base/functional/callback.h"
+ #include "base/sequence_checker.h"
+ #include "base/thread_annotations.h"
++#include "base/time/time.h"
+ #include "base/timer/elapsed_timer.h"
+ #include "net/base/schemeful_site.h"
+ #include "net/first_party_sets/first_party_set_entry.h"
+@@ -52,6 +53,7 @@ class FirstPartySetsManager {
+   [[nodiscard]] absl::optional<net::FirstPartySetMetadata> ComputeMetadata(
+       const net::SchemefulSite& site,
+       const net::SchemefulSite* top_frame_site,
++      const std::set<net::SchemefulSite>& party_context,
+       const net::FirstPartySetsContextConfig& fps_context_config,
+       base::OnceCallback<void(net::FirstPartySetMetadata)> callback);
+ 
+@@ -84,6 +86,7 @@ class FirstPartySetsManager {
+   void ComputeMetadataAndInvoke(
+       const net::SchemefulSite& site,
+       const absl::optional<net::SchemefulSite> top_frame_site,
++      const std::set<net::SchemefulSite>& party_context,
+       const net::FirstPartySetsContextConfig& fps_context_config,
+       base::OnceCallback<void(net::FirstPartySetMetadata)> callback,
+       base::ElapsedTimer timer) const;
+@@ -93,6 +96,7 @@ class FirstPartySetsManager {
+   net::FirstPartySetMetadata ComputeMetadataInternal(
+       const net::SchemefulSite& site,
+       const net::SchemefulSite* top_frame_site,
++      const std::set<net::SchemefulSite>& party_context,
+       const net::FirstPartySetsContextConfig& fps_context_config) const;
+ 
+   // Returns `site`'s entry, or `nullopt` if `site` has no entry.
+diff --git a/services/network/network_service_network_delegate.cc b/services/network/network_service_network_delegate.cc
+index 82829ecd4005812b9973fbf4bd1a1fcee7ed5d5e..f2bdb481687b5e37fcaa5f6b12c9d754fb4f653f 100644
+--- a/services/network/network_service_network_delegate.cc
++++ b/services/network/network_service_network_delegate.cc
+@@ -18,6 +18,7 @@
+ #include "net/base/load_flags.h"
+ #include "net/base/net_errors.h"
+ #include "net/cookies/cookie_setting_override.h"
++#include "net/first_party_sets/same_party_context.h"
+ #include "net/url_request/clear_site_data.h"
+ #include "net/url_request/referrer_policy.h"
+ #include "net/url_request/url_request.h"
+@@ -330,6 +331,16 @@ bool NetworkServiceNetworkDelegate::OnCanUseReportingClient(
+                                  origin, net::CookieSettingOverrides());
+ }
+ 
++absl::optional<net::FirstPartySetsCacheFilter::MatchInfo>
++NetworkServiceNetworkDelegate::
++    OnGetFirstPartySetsCacheFilterMatchInfoMaybeAsync(
++        const net::SchemefulSite& request_site,
++        base::OnceCallback<void(net::FirstPartySetsCacheFilter::MatchInfo)>
++            callback) const {
++  return network_context_->first_party_sets_access_delegate()
++      .GetCacheFilterMatchInfo(request_site, std::move(callback));
++}
++
+ int NetworkServiceNetworkDelegate::HandleClearSiteDataHeader(
+     net::URLRequest* request,
+     net::CompletionOnceCallback callback,
+diff --git a/services/network/network_service_network_delegate.h b/services/network/network_service_network_delegate.h
+index cfcd91f7e47e4d6869c97ba1eb9dd58e5b69cfff..ea8be1d29ad4ab384df0cf451d155551a6455842 100644
+--- a/services/network/network_service_network_delegate.h
++++ b/services/network/network_service_network_delegate.h
+@@ -19,6 +19,7 @@
+ #include "third_party/abseil-cpp/absl/types/optional.h"
+ 
+ namespace net {
++class SchemefulSite;
+ class CookieInclusionStatus;
+ }  // namespace net
+ 
+@@ -92,6 +93,11 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkServiceNetworkDelegate
+                                const GURL& endpoint) const override;
+   bool OnCanUseReportingClient(const url::Origin& origin,
+                                const GURL& endpoint) const override;
++  absl::optional<net::FirstPartySetsCacheFilter::MatchInfo>
++  OnGetFirstPartySetsCacheFilterMatchInfoMaybeAsync(
++      const net::SchemefulSite& request_site,
++      base::OnceCallback<void(net::FirstPartySetsCacheFilter::MatchInfo)>
++          callback) const override;
+ 
+   int HandleClearSiteDataHeader(
+       net::URLRequest* request,
+diff --git a/services/network/public/cpp/cookie_manager_mojom_traits.cc b/services/network/public/cpp/cookie_manager_mojom_traits.cc
+index 5269c91580bc8d9cf93c3323f7b96ac748cd5972..2505b01a13c62f49ce73a539d5d452ea6ae340bd 100644
+--- a/services/network/public/cpp/cookie_manager_mojom_traits.cc
++++ b/services/network/public/cpp/cookie_manager_mojom_traits.cc
+@@ -586,6 +586,17 @@ bool StructTraits<network::mojom::CookieOptionsDataView, net::CookieOptions>::
+   else
+     cookie_options->unset_return_excluded_cookies();
+ 
++  net::SamePartyContext same_party_context;
++  if (!mojo_options.ReadSamePartyContext(&same_party_context))
++    return false;
++  cookie_options->set_same_party_context(same_party_context);
++
++  cookie_options->set_full_party_context_size(
++      mojo_options.full_party_context_size());
++
++  cookie_options->set_is_in_nontrivial_first_party_set(
++      mojo_options.is_in_nontrivial_first_party_set());
++
+   return true;
+ }
+ 
+diff --git a/services/network/public/cpp/cookie_manager_mojom_traits.h b/services/network/public/cpp/cookie_manager_mojom_traits.h
+index 5531222e359de032cb45b28e3451b5622a731069..d562b702b0f5a87017268e80d5941585609ccc3b 100644
+--- a/services/network/public/cpp/cookie_manager_mojom_traits.h
++++ b/services/network/public/cpp/cookie_manager_mojom_traits.h
+@@ -16,6 +16,8 @@
+ #include "net/cookies/cookie_inclusion_status.h"
+ #include "net/cookies/cookie_options.h"
+ #include "net/cookies/cookie_partition_key_collection.h"
++#include "net/first_party_sets/first_party_set_entry.h"
++#include "net/first_party_sets/same_party_context.h"
+ #include "services/network/public/cpp/cookie_manager_shared_mojom_traits.h"
+ #include "services/network/public/mojom/cookie_manager.mojom-forward.h"
+ #include "services/network/public/mojom/cookie_manager.mojom.h"
+@@ -194,6 +196,18 @@ struct StructTraits<network::mojom::CookieOptionsDataView, net::CookieOptions> {
+     return o.return_excluded_cookies();
+   }
+ 
++  static net::SamePartyContext same_party_context(const net::CookieOptions& o) {
++    return o.same_party_context();
++  }
++
++  static uint32_t full_party_context_size(const net::CookieOptions& o) {
++    return o.full_party_context_size();
++  }
++
++  static bool is_in_nontrivial_first_party_set(const net::CookieOptions& o) {
++    return o.is_in_nontrivial_first_party_set();
++  }
++
+   static bool Read(network::mojom::CookieOptionsDataView mojo_options,
+                    net::CookieOptions* cookie_options);
+ };
+diff --git a/services/network/public/cpp/first_party_sets_mojom_traits.cc b/services/network/public/cpp/first_party_sets_mojom_traits.cc
+index 0c20fd9b054b6bdf989adde7d9c03f4f784215d9..4d1e584278722eb449012ef4f2f8f2162223711f 100644
+--- a/services/network/public/cpp/first_party_sets_mojom_traits.cc
++++ b/services/network/public/cpp/first_party_sets_mojom_traits.cc
+@@ -18,6 +18,7 @@
+ #include "net/first_party_sets/first_party_sets_cache_filter.h"
+ #include "net/first_party_sets/first_party_sets_context_config.h"
+ #include "net/first_party_sets/global_first_party_sets.h"
++#include "net/first_party_sets/same_party_context.h"
+ #include "services/network/public/cpp/schemeful_site_mojom_traits.h"
+ #include "services/network/public/mojom/first_party_sets.mojom-shared.h"
+ 
+@@ -83,10 +84,55 @@ bool StructTraits<network::mojom::FirstPartySetEntryDataView,
+   return true;
+ }
+ 
++bool EnumTraits<network::mojom::SamePartyCookieContextType,
++                net::SamePartyContext::Type>::
++    FromMojom(network::mojom::SamePartyCookieContextType context_type,
++              net::SamePartyContext::Type* out) {
++  switch (context_type) {
++    case network::mojom::SamePartyCookieContextType::kCrossParty:
++      *out = net::SamePartyContext::Type::kCrossParty;
++      return true;
++    case network::mojom::SamePartyCookieContextType::kSameParty:
++      *out = net::SamePartyContext::Type::kSameParty;
++      return true;
++  }
++  return false;
++}
++
++network::mojom::SamePartyCookieContextType
++EnumTraits<network::mojom::SamePartyCookieContextType,
++           net::SamePartyContext::Type>::ToMojom(net::SamePartyContext::Type
++                                                     context_type) {
++  switch (context_type) {
++    case net::SamePartyContext::Type::kCrossParty:
++      return network::mojom::SamePartyCookieContextType::kCrossParty;
++    case net::SamePartyContext::Type::kSameParty:
++      return network::mojom::SamePartyCookieContextType::kSameParty;
++  }
++  NOTREACHED();
++  return network::mojom::SamePartyCookieContextType::kCrossParty;
++}
++
++bool StructTraits<network::mojom::SamePartyContextDataView,
++                  net::SamePartyContext>::
++    Read(network::mojom::SamePartyContextDataView context,
++         net::SamePartyContext* out) {
++  net::SamePartyContext::Type context_type;
++  if (!context.ReadContextType(&context_type))
++    return false;
++
++  *out = net::SamePartyContext(context_type);
++  return true;
++}
++
+ bool StructTraits<network::mojom::FirstPartySetMetadataDataView,
+                   net::FirstPartySetMetadata>::
+     Read(network::mojom::FirstPartySetMetadataDataView metadata,
+          net::FirstPartySetMetadata* out_metadata) {
++  net::SamePartyContext context;
++  if (!metadata.ReadContext(&context))
++    return false;
++
+   absl::optional<net::FirstPartySetEntry> frame_entry;
+   if (!metadata.ReadFrameEntry(&frame_entry))
+     return false;
+@@ -95,8 +141,9 @@ bool StructTraits<network::mojom::FirstPartySetMetadataDataView,
+   if (!metadata.ReadTopFrameEntry(&top_frame_entry))
+     return false;
+ 
+-  *out_metadata = net::FirstPartySetMetadata(
+-      base::OptionalToPtr(frame_entry), base::OptionalToPtr(top_frame_entry));
++  *out_metadata =
++      net::FirstPartySetMetadata(context, base::OptionalToPtr(frame_entry),
++                                 base::OptionalToPtr(top_frame_entry));
+ 
+   return true;
+ }
+diff --git a/services/network/public/cpp/first_party_sets_mojom_traits.h b/services/network/public/cpp/first_party_sets_mojom_traits.h
+index b255f56ead022bb237296e7560c73f692a7d7ee3..644aab23aef7fb37c573add9e4180b743c755a12 100644
+--- a/services/network/public/cpp/first_party_sets_mojom_traits.h
++++ b/services/network/public/cpp/first_party_sets_mojom_traits.h
+@@ -16,6 +16,7 @@
+ #include "net/first_party_sets/first_party_sets_cache_filter.h"
+ #include "net/first_party_sets/first_party_sets_context_config.h"
+ #include "net/first_party_sets/global_first_party_sets.h"
++#include "net/first_party_sets/same_party_context.h"
+ #include "services/network/public/mojom/first_party_sets.mojom-shared.h"
+ 
+ namespace mojo {
+@@ -61,10 +62,38 @@ struct COMPONENT_EXPORT(FIRST_PARTY_SETS_MOJOM_TRAITS)
+                    net::FirstPartySetEntry* out);
+ };
+ 
++template <>
++struct COMPONENT_EXPORT(FIRST_PARTY_SETS_MOJOM_TRAITS)
++    EnumTraits<network::mojom::SamePartyCookieContextType,
++               net::SamePartyContext::Type> {
++  static network::mojom::SamePartyCookieContextType ToMojom(
++      net::SamePartyContext::Type context_type);
++
++  static bool FromMojom(network::mojom::SamePartyCookieContextType context_type,
++                        net::SamePartyContext::Type* out);
++};
++
++template <>
++struct COMPONENT_EXPORT(FIRST_PARTY_SETS_MOJOM_TRAITS)
++    StructTraits<network::mojom::SamePartyContextDataView,
++                 net::SamePartyContext> {
++  static net::SamePartyContext::Type context_type(
++      const net::SamePartyContext& s) {
++    return s.context_type();
++  }
++
++  static bool Read(network::mojom::SamePartyContextDataView bundle,
++                   net::SamePartyContext* out);
++};
++
+ template <>
+ struct COMPONENT_EXPORT(FIRST_PARTY_SETS_MOJOM_TRAITS)
+     StructTraits<network::mojom::FirstPartySetMetadataDataView,
+                  net::FirstPartySetMetadata> {
++  static net::SamePartyContext context(const net::FirstPartySetMetadata& m) {
++    return m.context();
++  }
++
+   static absl::optional<net::FirstPartySetEntry> frame_entry(
+       const net::FirstPartySetMetadata& m) {
+     return m.frame_entry();
+diff --git a/services/network/public/cpp/isolation_info_mojom_traits.cc b/services/network/public/cpp/isolation_info_mojom_traits.cc
+index cc91befdc7da5e88eac6cea0f05eef0707bb4b04..a5a3d0af67c958766bacc8a02cacf490d3c5b5e4 100644
+--- a/services/network/public/cpp/isolation_info_mojom_traits.cc
++++ b/services/network/public/cpp/isolation_info_mojom_traits.cc
+@@ -53,6 +53,7 @@ bool StructTraits<network::mojom::IsolationInfoDataView, net::IsolationInfo>::
+   absl::optional<base::UnguessableToken> nonce;
+   net::SiteForCookies site_for_cookies;
+   net::IsolationInfo::RequestType request_type;
++  absl::optional<std::vector<net::SchemefulSite>> mojo_party_context;
+ 
+   if (!data.ReadTopFrameOrigin(&top_frame_origin)) {
+     network::debug::SetDeserializationCrashKeyString("isolation_top_origin");
+@@ -63,14 +64,23 @@ bool StructTraits<network::mojom::IsolationInfoDataView, net::IsolationInfo>::
+     return false;
+   }
+   if (!data.ReadNonce(&nonce) || !data.ReadSiteForCookies(&site_for_cookies) ||
+-      !data.ReadRequestType(&request_type)) {
++      !data.ReadRequestType(&request_type) ||
++      !data.ReadPartyContext(&mojo_party_context)) {
+     return false;
+   }
+ 
++  absl::optional<std::set<net::SchemefulSite>> party_context;
++  if (mojo_party_context.has_value()) {
++    party_context = std::set<net::SchemefulSite>(mojo_party_context->begin(),
++                                                 mojo_party_context->end());
++    if (party_context->size() != mojo_party_context->size())
++      return false;
++  }
++
+   absl::optional<net::IsolationInfo> isolation_info =
+       net::IsolationInfo::CreateIfConsistent(request_type, top_frame_origin,
+                                              frame_origin, site_for_cookies,
+-                                             nonce);
++                                             std::move(party_context), nonce);
+   if (!isolation_info) {
+     network::debug::SetDeserializationCrashKeyString("isolation_inconsistent");
+     return false;
+diff --git a/services/network/public/cpp/isolation_info_mojom_traits.h b/services/network/public/cpp/isolation_info_mojom_traits.h
+index 40c5582de6832132b9e32de65cdffb9d58b99b36..1d41304d38061f7fb1b219a2289af5e41cbd1353 100644
+--- a/services/network/public/cpp/isolation_info_mojom_traits.h
++++ b/services/network/public/cpp/isolation_info_mojom_traits.h
+@@ -57,6 +57,11 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE)
+     return input.site_for_cookies();
+   }
+ 
++  static const absl::optional<std::set<net::SchemefulSite>>& party_context(
++      const net::IsolationInfo& input) {
++    return input.party_context_;
++  }
++
+   static bool Read(network::mojom::IsolationInfoDataView data,
+                    net::IsolationInfo* out);
+ };
+diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn
+index 530b9da45489fc8572b44e32eac6564908e983d0..adb7d544e728a4ea49653eede3c1a68cf907bebc 100644
+--- a/services/network/public/mojom/BUILD.gn
++++ b/services/network/public/mojom/BUILD.gn
+@@ -1030,6 +1030,14 @@ mojom("mojom_first_party_sets") {
+               mojom = "network.mojom.FirstPartySetEntry"
+               cpp = "::net::FirstPartySetEntry"
+             },
++            {
++              mojom = "network.mojom.SamePartyCookieContextType"
++              cpp = "::net::SamePartyContext::Type"
++            },
++            {
++              mojom = "network.mojom.SamePartyContext"
++              cpp = "::net::SamePartyContext"
++            },
+             {
+               mojom = "network.mojom.FirstPartySetMetadata"
+               cpp = "::net::FirstPartySetMetadata"
+diff --git a/services/network/public/mojom/cookie_manager.mojom b/services/network/public/mojom/cookie_manager.mojom
+index 7aa887883985c86bb6ceda023225a35d1bc07f38..ee009f2425455d4617d21af0be3e47a7b695665d 100644
+--- a/services/network/public/mojom/cookie_manager.mojom
++++ b/services/network/public/mojom/cookie_manager.mojom
+@@ -165,6 +165,12 @@ struct CookieOptions {
+   CookieSameSiteContext same_site_cookie_context;
+   bool update_access_time = true;
+   bool return_excluded_cookies = false;
++  SamePartyContext same_party_context;
++  // The size of the isolation_info.party_context plus the top-frame site for
++  // logging purposes.
++  uint32 full_party_context_size = 0;
++  // Whether the site is a member of a nontrivial First-Party Set.
++  bool is_in_nontrivial_first_party_set = false;
+ };
+ 
+ // See net/cookies/canonical_cookie.{h,cc} for documentation.
+diff --git a/services/network/public/mojom/first_party_sets.mojom b/services/network/public/mojom/first_party_sets.mojom
+index 0ea745cd2450c2d92002c3a0113295b0d4bbd40e..150fed986ded1c82ca3b965c0218193dc938d7a1 100644
+--- a/services/network/public/mojom/first_party_sets.mojom
++++ b/services/network/public/mojom/first_party_sets.mojom
+@@ -27,9 +27,25 @@ struct FirstPartySetEntry {
+   SiteIndex? site_index;
+ };
+ 
++// Computed for every cookie access attempt but is only relevant for SameParty
++// cookies.
++enum SamePartyCookieContextType {
++  // The opposite to kSameParty. Should be the default value.
++  kCrossParty,
++  // If the request URL is in the same First-Party Sets as the top-frame site
++  // and each member of the isolation_info.party_context.
++  kSameParty,
++};
++
++// Keep defaults in here in sync with net/cookies/same_party_context.cc.
++struct SamePartyContext {
++  SamePartyCookieContextType context_type = kCrossParty;
++};
++
+ // This struct must match the class fields defined in
+ // //net/first_party_sets/first_party_set_metadata.h.
+ struct FirstPartySetMetadata {
++  SamePartyContext context;
+   // absl::nullopt indicates that the frame's site is not associated with any
+   // First-Party Set.
+   FirstPartySetEntry? frame_entry;
+diff --git a/services/network/public/mojom/isolation_info.mojom b/services/network/public/mojom/isolation_info.mojom
+index 60b726cce8f05a8d0a89a310d00b467be519badc..407ea92cb29712fd89f7c40c6bb210900da8987d 100644
+--- a/services/network/public/mojom/isolation_info.mojom
++++ b/services/network/public/mojom/isolation_info.mojom
+@@ -26,4 +26,5 @@ struct IsolationInfo {
+   url.mojom.Origin? frame_origin;
+   mojo_base.mojom.UnguessableToken? nonce;
+   SiteForCookies site_for_cookies;
++  array<SchemefulSite>? party_context;
+ };
+diff --git a/services/network/restricted_cookie_manager.cc b/services/network/restricted_cookie_manager.cc
+index 8b0e7695e78f15632bea2a49813f096802517bc0..00bda3ab02fc8586973640a3ecf644dfc64f5dd7 100644
+--- a/services/network/restricted_cookie_manager.cc
++++ b/services/network/restricted_cookie_manager.cc
+@@ -38,7 +38,6 @@
+ #include "net/cookies/cookie_util.h"
+ #include "net/cookies/site_for_cookies.h"
+ #include "net/first_party_sets/first_party_set_metadata.h"
+-#include "net/first_party_sets/first_party_sets_cache_filter.h"
+ #include "services/network/cookie_settings.h"
+ #include "services/network/public/cpp/features.h"
+ #include "services/network/public/mojom/cookie_manager.mojom.h"
+@@ -105,11 +104,20 @@ constexpr base::TimeDelta kCookiesAccessedTimeout = base::Milliseconds(100);
+ constexpr size_t kMaxCookieCacheCount = 32u;
+ constexpr size_t kIncreasedMaxCookieCacheCount = 100u;
+ 
++// TODO(cfredric): the `force_ignore_top_frame_party` param being false prevents
++// `document.cookie` access for same-party scripts embedded in an extension
++// frame. It would be better if we allowed that similarly to how we allow
++// SameParty cookies for requests in same-party contexts embedded in top-level
++// extension frames.
++const bool kForceIgnoreTopFrameParty = false;
++
+ net::CookieOptions MakeOptionsForSet(
+     mojom::RestrictedCookieManagerRole role,
+     const GURL& url,
+     const net::SiteForCookies& site_for_cookies,
+-    const CookieSettings& cookie_settings) {
++    const net::IsolationInfo& isolation_info,
++    const CookieSettings& cookie_settings,
++    const net::FirstPartySetMetadata& first_party_set_metadata) {
+   net::CookieOptions options;
+   bool force_ignore_site_for_cookies =
+       cookie_settings.ShouldIgnoreSameSiteRestrictions(url, site_for_cookies);
+@@ -125,6 +133,14 @@ net::CookieOptions MakeOptionsForSet(
+         net::cookie_util::ComputeSameSiteContextForSubresource(
+             url, site_for_cookies, force_ignore_site_for_cookies));
+   }
++  options.set_same_party_context(first_party_set_metadata.context());
++  if (isolation_info.party_context().has_value()) {
++    // Count the top-frame site since it's not in the party_context.
++    options.set_full_party_context_size(isolation_info.party_context()->size() +
++                                        1);
++  }
++  options.set_is_in_nontrivial_first_party_set(
++      first_party_set_metadata.frame_entry().has_value());
+ 
+   return options;
+ }
+@@ -133,7 +149,9 @@ net::CookieOptions MakeOptionsForGet(
+     mojom::RestrictedCookieManagerRole role,
+     const GURL& url,
+     const net::SiteForCookies& site_for_cookies,
+-    const CookieSettings& cookie_settings) {
++    const net::IsolationInfo& isolation_info,
++    const CookieSettings& cookie_settings,
++    const net::FirstPartySetMetadata& first_party_set_metadata) {
+   // TODO(https://crbug.com/925311): Wire initiator here.
+   net::CookieOptions options;
+   bool force_ignore_site_for_cookies =
+@@ -151,6 +169,14 @@ net::CookieOptions MakeOptionsForGet(
+         net::cookie_util::ComputeSameSiteContextForSubresource(
+             url, site_for_cookies, force_ignore_site_for_cookies));
+   }
++  options.set_same_party_context(first_party_set_metadata.context());
++  if (isolation_info.party_context().has_value()) {
++    // Count the top-frame site since it's not in the party_context.
++    options.set_full_party_context_size(isolation_info.party_context()->size() +
++                                        1);
++  }
++  options.set_is_in_nontrivial_first_party_set(
++      first_party_set_metadata.frame_entry().has_value());
+ 
+   return options;
+ }
+@@ -169,21 +195,13 @@ void RestrictedCookieManager::ComputeFirstPartySetMetadata(
+   std::pair<base::OnceCallback<void(net::FirstPartySetMetadata)>,
+             base::OnceCallback<void(net::FirstPartySetMetadata)>>
+       callbacks = base::SplitOnceCallback(std::move(callback));
+-  absl::optional<std::pair<net::FirstPartySetMetadata,
+-                           net::FirstPartySetsCacheFilter::MatchInfo>>
+-      metadata_and_match_info =
+-          net::cookie_util::ComputeFirstPartySetMetadataMaybeAsync(
+-              /*request_site=*/net::SchemefulSite(origin), isolation_info,
+-              cookie_store->cookie_access_delegate(),
+-              base::BindOnce([](net::FirstPartySetMetadata metadata,
+-                                net::FirstPartySetsCacheFilter::MatchInfo
+-                                    match_info) {
+-                return metadata;
+-              }).Then(std::move(callbacks.first)));
+-  if (metadata_and_match_info.has_value()) {
+-    std::move(callbacks.second)
+-        .Run(std::move(metadata_and_match_info.value().first));
+-  }
++  absl::optional<net::FirstPartySetMetadata> metadata =
++      net::cookie_util::ComputeFirstPartySetMetadataMaybeAsync(
++          /*request_site=*/net::SchemefulSite(origin), isolation_info,
++          cookie_store->cookie_access_delegate(), kForceIgnoreTopFrameParty,
++          std::move(callbacks.first));
++  if (metadata.has_value())
++    std::move(callbacks.second).Run(std::move(metadata.value()));
+ }
+ 
+ bool CookieWithAccessResultComparer::operator()(
+@@ -315,7 +333,8 @@ class RestrictedCookieManager::Listener : public base::LinkNode<Listener> {
+            bool has_storage_access,
+            const absl::optional<net::CookiePartitionKey>& cookie_partition_key,
+            net::CookieOptions options,
+-           mojo::PendingRemote<mojom::CookieChangeListener> mojo_listener)
++           mojo::PendingRemote<mojom::CookieChangeListener> mojo_listener,
++           bool same_party_attribute_enabled)
+       : cookie_store_(cookie_store),
+         restricted_cookie_manager_(restricted_cookie_manager),
+         url_(url),
+@@ -323,7 +342,8 @@ class RestrictedCookieManager::Listener : public base::LinkNode<Listener> {
+         top_frame_origin_(top_frame_origin),
+         has_storage_access_(has_storage_access),
+         options_(options),
+-        mojo_listener_(std::move(mojo_listener)) {
++        mojo_listener_(std::move(mojo_listener)),
++        same_party_attribute_enabled_(same_party_attribute_enabled) {
+     // TODO(pwnall): add a constructor w/options to net::CookieChangeDispatcher.
+     cookie_store_subscription_ =
+         cookie_store->GetChangeDispatcher().AddCallbackForUrl(
+@@ -359,11 +379,16 @@ class RestrictedCookieManager::Listener : public base::LinkNode<Listener> {
+ 
+     // CookieChangeDispatcher doesn't check for inclusion against `options_`, so
+     // we need to double-check that.
++    net::CookieSamePartyStatus same_party_status =
++        net::cookie_util::GetSamePartyStatus(change.cookie, options_,
++                                             same_party_attribute_enabled_);
++
+     if (!change.cookie
+              .IncludeForRequestURL(
+                  url_, options_,
+                  net::CookieAccessParams{change.access_result.access_semantics,
+-                                         delegate_treats_url_as_trustworthy})
++                                         delegate_treats_url_as_trustworthy,
++                                         same_party_status})
+              .status.IsInclude()) {
+       return;
+     }
+@@ -414,6 +439,8 @@ class RestrictedCookieManager::Listener : public base::LinkNode<Listener> {
+ 
+   mojo::Remote<mojom::CookieChangeListener> mojo_listener_;
+ 
++  bool same_party_attribute_enabled_;
++
+   SEQUENCE_CHECKER(sequence_checker_);
+ };
+ 
+@@ -440,6 +467,8 @@ RestrictedCookieManager::RestrictedCookieManager(
+       cookie_partition_key_collection_(
+           net::CookiePartitionKeyCollection::FromOptional(
+               cookie_partition_key_)),
++      same_party_attribute_enabled_(base::FeatureList::IsEnabled(
++          net::features::kSamePartyAttributeEnabled)),
+       receiver_(this),
+       metrics_updater_(metrics_updater),
+       max_cookie_cache_count_(
+@@ -565,7 +594,8 @@ void RestrictedCookieManager::GetAllForUrl(
+   // TODO(morlovich): Try to validate site_for_cookies as well.
+ 
+   net::CookieOptions net_options =
+-      MakeOptionsForGet(role_, url, site_for_cookies, cookie_settings());
++      MakeOptionsForGet(role_, url, site_for_cookies, isolation_info_,
++                        cookie_settings(), first_party_set_metadata_);
+   // TODO(https://crbug.com/977040): remove set_return_excluded_cookies() once
+   // removing deprecation warnings.
+   net_options.set_return_excluded_cookies();
+@@ -819,7 +849,8 @@ void RestrictedCookieManager::SetCanonicalCookie(
+ 
+   net::CanonicalCookie cookie_copy = *sanitized_cookie;
+   net::CookieOptions options =
+-      MakeOptionsForSet(role_, url, site_for_cookies, cookie_settings());
++      MakeOptionsForSet(role_, url, site_for_cookies, isolation_info_,
++                        cookie_settings(), first_party_set_metadata_);
+ 
+   net::CookieAccessResult cookie_access_result(status);
+   cookie_store_->SetCanonicalCookieAsync(
+@@ -871,11 +902,12 @@ void RestrictedCookieManager::AddChangeListener(
+   }
+ 
+   net::CookieOptions net_options =
+-      MakeOptionsForGet(role_, url, site_for_cookies, cookie_settings());
++      MakeOptionsForGet(role_, url, site_for_cookies, isolation_info_,
++                        cookie_settings(), first_party_set_metadata_);
+   auto listener = std::make_unique<Listener>(
+       cookie_store_, this, url, site_for_cookies, top_frame_origin,
+       has_storage_access, cookie_partition_key_, net_options,
+-      std::move(mojo_listener));
++      std::move(mojo_listener), same_party_attribute_enabled_);
+ 
+   listener->mojo_listener().set_disconnect_handler(
+       base::BindOnce(&RestrictedCookieManager::RemoveChangeListener,
+diff --git a/services/network/restricted_cookie_manager.h b/services/network/restricted_cookie_manager.h
+index f4cab620266b2db3bb71c7974bfcb519859808d3..0ea9313a079b1063be12c5f8dd914cbaa63cdacc 100644
+--- a/services/network/restricted_cookie_manager.h
++++ b/services/network/restricted_cookie_manager.h
+@@ -329,6 +329,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) RestrictedCookieManager
+   // update filtering.
+   CookieAccessesByURLAndSite recent_cookie_accesses_;
+ 
++  bool same_party_attribute_enabled_;
++
+   // This class can optionally bind its Receiver. If that's the case it's stored
+   // done with this variable.
+   mojo::Receiver<mojom::RestrictedCookieManager> receiver_;
+diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
+index b0b972c5768087ded0ade3dfc15bd1c47e68bc3c..3b0c13ad5d3f07bd667abc81d36f8d2067d911c8 100644
+--- a/services/network/url_loader.cc
++++ b/services/network/url_loader.cc
+@@ -286,7 +286,7 @@ bool ShouldNotifyAboutCookie(net::CookieInclusionStatus status) {
+          status.HasExclusionReason(
+              net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES) ||
+          status.HasExclusionReason(
+-             net::CookieInclusionStatus::EXCLUDE_THIRD_PARTY_PHASEOUT) ||
++             net::CookieInclusionStatus::EXCLUDE_INVALID_SAMEPARTY) ||
+          status.HasExclusionReason(
+              net::CookieInclusionStatus::EXCLUDE_DOMAIN_NON_ASCII);
+ }
+@@ -586,6 +586,9 @@ URLLoader::URLLoader(
+     DCHECK(!url_request_->isolation_info().IsEmpty());
+   }
+ 
++  if (ShouldForceIgnoreTopFramePartyForCookies())
++    url_request_->set_force_ignore_top_frame_party_for_cookies(true);
++
+   // When a service worker forwards a navigation request it uses the
+   // service worker's IsolationInfo.  This causes the cookie code to fail
+   // to send SameSite=Lax cookies for main-frame navigations passed through
+@@ -2691,6 +2694,36 @@ bool URLLoader::ShouldForceIgnoreSiteForCookies(
+   return false;
+ }
+ 
++bool URLLoader::ShouldForceIgnoreTopFramePartyForCookies() const {
++  const net::IsolationInfo& isolation_info = url_request_->isolation_info();
++  const absl::optional<url::Origin>& top_frame_origin =
++      isolation_info.top_frame_origin();
++
++  if (!top_frame_origin || top_frame_origin->opaque())
++    return false;
++
++  const absl::optional<std::set<net::SchemefulSite>>& party_context =
++      isolation_info.party_context();
++  if (!party_context)
++    return false;
++
++  // The top frame origin must have access to the request URL.
++  if (cors::OriginAccessList::AccessState::kAllowed !=
++      origin_access_list_->CheckAccessState(*top_frame_origin,
++                                            url_request_->url())) {
++    return false;
++  }
++
++  // The top frame origin must have access to each site in the party_context.
++  return base::ranges::all_of(
++      *party_context,
++      [this, &top_frame_origin](const net::SchemefulSite& site) {
++        return origin_access_list_->CheckAccessState(*top_frame_origin,
++                                                     site.GetURL()) ==
++               cors::OriginAccessList::AccessState::kAllowed;
++      });
++}
++
+ void URLLoader::SetRequestCredentials(const GURL& url) {
+   bool coep_allow_credentials = CoepAllowCredentials(url);
+ 
+diff --git a/services/network/url_loader.h b/services/network/url_loader.h
+index 3082770fd7cd93da2578e55c4ea256edf819bf29..e3911d8b21fc5f9f436cdab5656d929596a7cb47 100644
+--- a/services/network/url_loader.h
++++ b/services/network/url_loader.h
+@@ -547,6 +547,10 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) URLLoader
+   // Whether `force_ignore_site_for_cookies` should be set on net::URLRequest.
+   bool ShouldForceIgnoreSiteForCookies(const ResourceRequest& request);
+ 
++  // Whether `force_ignore_top_frame_party_for_cookies` should be set on
++  // net::URLRequest.
++  bool ShouldForceIgnoreTopFramePartyForCookies() const;
++
+   // Applies Private Network Access checks to the current request.
+   //
+   // Helper for `OnConnected()`.
+diff --git a/third_party/blink/common/storage_key/storage_key.cc b/third_party/blink/common/storage_key/storage_key.cc
+index 5b863520514c1cb5011c6afa08d231a4047282e9..2712538e887b39035ac0b2718a98c5ee812087f9 100644
+--- a/third_party/blink/common/storage_key/storage_key.cc
++++ b/third_party/blink/common/storage_key/storage_key.cc
+@@ -788,7 +788,8 @@ const net::IsolationInfo StorageKey::ToPartialNetIsolationInfo() const {
+                             : url::Origin::Create(top_level_site_.GetURL());
+   return net::IsolationInfo::Create(net::IsolationInfo::RequestType::kOther,
+                                     top_frame_origin, origin_,
+-                                    ToNetSiteForCookies(), nonce_);
++                                    ToNetSiteForCookies(),
++                                    /*party_context=*/absl::nullopt, nonce_);
+ }
+ 
+ // static