[go: nahoru, domu]

Move component version into GlobalFPS class.

This simplifies some plumbing since we no longer have to explicitly pass
the version around, but more importantly, it makes the
GlobalFirstPartySets instances aware of when the public sets are
considered invalid and won't be persisted to disk. In such cases, Chrome
should not use the public sets, since future runs would not be able to
clear site data appropriately (and thus could cause a privacy issue).

Fixes: 1384184
Change-Id: Ibfed18a4c9aecf04ea286afa7b268e1768a9f372
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4035331
Reviewed-by: Andrey Zaytsev <andzaytsev@google.com>
Reviewed-by: Joshua Pawlicki <waffles@chromium.org>
Reviewed-by: Maks Orlovich <morlovich@chromium.org>
Reviewed-by: Shuran Huang <shuuran@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Commit-Queue: Joshua Pawlicki <waffles@chromium.org>
Auto-Submit: Chris Fredrickson <cfredric@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1074096}
diff --git a/base/version.h b/base/version.h
index 2c94a59..2740219 100644
--- a/base/version.h
+++ b/base/version.h
@@ -46,7 +46,7 @@
   // Version behavior (IsValid) if no wildcard is present.
   static bool IsValidWildcardString(StringPiece wildcard_string);
 
-  // Returns -1, 0, 1 for <, ==, >.
+  // Returns -1, 0, 1 for <, ==, >. `this` and `other` must both be valid.
   int CompareTo(const Version& other) const;
 
   // Given a valid version object, compare if a |wildcard_string| results in a
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 d02bf8e..9b054403 100644
--- a/chrome/browser/component_updater/first_party_sets_component_installer.cc
+++ b/chrome/browser/component_updater/first_party_sets_component_installer.cc
@@ -238,12 +238,13 @@
 
 // static
 void FirstPartySetsComponentInstallerPolicy::WriteComponentForTesting(
+    base::Version version,
     const base::FilePath& install_dir,
     base::StringPiece contents) {
   CHECK(base::WriteFile(GetInstalledPath(install_dir), contents));
 
   GetConfigPathInstance() =
-      std::make_pair(GetInstalledPath(install_dir), base::Version());
+      std::make_pair(GetInstalledPath(install_dir), std::move(version));
 }
 
 }  // namespace component_updater
diff --git a/chrome/browser/component_updater/first_party_sets_component_installer.h b/chrome/browser/component_updater/first_party_sets_component_installer.h
index 44de734..f478c828 100644
--- a/chrome/browser/component_updater/first_party_sets_component_installer.h
+++ b/chrome/browser/component_updater/first_party_sets_component_installer.h
@@ -53,7 +53,8 @@
 
   // Seeds a component at `install_dir` with the given `contents`. Only to be
   // used in testing.
-  static void WriteComponentForTesting(const base::FilePath& install_dir,
+  static void WriteComponentForTesting(base::Version version,
+                                       const base::FilePath& install_dir,
                                        base::StringPiece contents);
 
  private:
diff --git a/chrome/browser/first_party_sets/first_party_sets_policy_service_unittest.cc b/chrome/browser/first_party_sets/first_party_sets_policy_service_unittest.cc
index d2cae38..3b81b2a5 100644
--- a/chrome/browser/first_party_sets/first_party_sets_policy_service_unittest.cc
+++ b/chrome/browser/first_party_sets/first_party_sets_policy_service_unittest.cc
@@ -46,6 +46,10 @@
 
 namespace first_party_sets {
 
+namespace {
+const base::Version kVersion("1.2.3");
+}
+
 class MockFirstPartySetsAccessDelegate
     : public network::mojom::FirstPartySetsAccessDelegate {
  public:
@@ -302,6 +306,7 @@
   // { primary: "https://primary.test",
   // associatedSites: ["https://associate1.test"}
   SetGlobalSets(net::GlobalFirstPartySets(
+      kVersion,
       {{associate1_site,
         {net::FirstPartySetEntry(primary_site, net::SiteType::kAssociated,
                                  0)}}},
@@ -329,6 +334,7 @@
   // { primary: "https://primary.test",
   // associatedSites: ["https://associate1.test"}
   SetGlobalSets(net::GlobalFirstPartySets(
+      kVersion,
       {{associate1_site,
         {net::FirstPartySetEntry(primary_site, net::SiteType::kAssociated,
                                  0)}}},
@@ -365,8 +371,8 @@
   // Simulate the global First-Party Sets with the following set:
   // { primary: "https://primary.test",
   // associatedSites: ["https://associate1.test"}
-  SetGlobalSets(
-      net::GlobalFirstPartySets({{associate1_site, {associate1_entry}}}, {}));
+  SetGlobalSets(net::GlobalFirstPartySets(
+      kVersion, {{associate1_site, {associate1_entry}}}, {}));
 
   // Verify that FindEntry returns empty if both sources of sets aren't ready
   // yet.
@@ -402,8 +408,8 @@
   // Simulate the global First-Party Sets with the following set:
   // { primary: "https://primary.test",
   // associatedSites: ["https://associate.test"}
-  SetGlobalSets(
-      net::GlobalFirstPartySets({{associate_site, {associate_entry}}}, {}));
+  SetGlobalSets(net::GlobalFirstPartySets(
+      kVersion, {{associate_site, {associate_entry}}}, {}));
 
   // Simulate the profile set overrides are empty.
   service()->InitForTesting();
@@ -441,8 +447,8 @@
   // Simulate the global First-Party Sets with the following set:
   // { primary: "https://primary.test",
   // associatedSites: ["https://associate.test"}
-  SetGlobalSets(
-      net::GlobalFirstPartySets({{associate_site, {associate_entry}}}, {}));
+  SetGlobalSets(net::GlobalFirstPartySets(
+      kVersion, {{associate_site, {associate_entry}}}, {}));
 
   // Simulate the profile set overrides are empty.
   service()->InitForTesting();
diff --git a/chrome/browser/net/system_network_context_manager_browsertest.cc b/chrome/browser/net/system_network_context_manager_browsertest.cc
index 70378f3..5fc363f5 100644
--- a/chrome/browser/net/system_network_context_manager_browsertest.cc
+++ b/chrome/browser/net/system_network_context_manager_browsertest.cc
@@ -237,7 +237,8 @@
     base::ScopedAllowBlockingForTesting allow_blocking;
 
     component_updater::FirstPartySetsComponentInstallerPolicy::
-        WriteComponentForTesting(component_dir_.GetPath(),
+        WriteComponentForTesting(base::Version("1.2.3"),
+                                 component_dir_.GetPath(),
                                  GetComponentContents());
   }
 
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
index 562cd23..d9c25b80 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
@@ -73,6 +73,8 @@
 const char kPrivacySandboxStartupHistogram[] =
     "Settings.PrivacySandbox.StartupState";
 
+const base::Version kFirstPartySetsVersion("1.2.3");
+
 class TestInterestGroupManager : public content::InterestGroupManager {
  public:
   void SetInterestGroupJoiningOrigins(const std::vector<url::Origin>& origins) {
@@ -2123,6 +2125,7 @@
   // { primary: "https://primary.test",
   // associatedSites: ["https://associate1.test"}
   net::GlobalFirstPartySets global_sets(
+      kFirstPartySetsVersion,
       {{associate1_site,
         {net::FirstPartySetEntry(primary_site, net::SiteType::kAssociated,
                                  0)}}},
@@ -2162,6 +2165,7 @@
   // { primary: "https://primary.test",
   // associatedSites: ["https://associate1.test"}
   net::GlobalFirstPartySets global_sets(
+      kFirstPartySetsVersion,
       {{associate1_site,
         {net::FirstPartySetEntry(primary_site, net::SiteType::kAssociated,
                                  0)}}},
@@ -2203,6 +2207,7 @@
   // { primary: "https://primary.test",
   // associatedSites: ["https://associate1.test"}
   net::GlobalFirstPartySets global_sets(
+      kFirstPartySetsVersion,
       {{associate1_site,
         {net::FirstPartySetEntry(primary_site, net::SiteType::kAssociated,
                                  0)}}},
@@ -2247,6 +2252,7 @@
   // { primary: "https://primary.test",
   // associatedSites: ["https://associate1.test"}
   net::GlobalFirstPartySets global_sets(
+      kFirstPartySetsVersion,
       {{associate1_site,
         {net::FirstPartySetEntry(primary_site, net::SiteType::kAssociated,
                                  0)}}},
@@ -2290,6 +2296,7 @@
   // { primary: "https://primary.test",
   // associatedSites: ["https://associate1.test"}
   net::GlobalFirstPartySets global_sets(
+      kFirstPartySetsVersion,
       {{associate1_site,
         {net::FirstPartySetEntry(primary_site, net::SiteType::kAssociated,
                                  0)}}},
@@ -2393,6 +2400,7 @@
   // { primary: "https://primary.test",
   // associatedSites: ["https://associate1.test", "https://associate2.test"] }
   mock_first_party_sets_handler().SetGlobalSets(net::GlobalFirstPartySets(
+      kFirstPartySetsVersion,
       {{associate1_site,
         {net::FirstPartySetEntry(primary_site, net::SiteType::kAssociated, 0)}},
        {associate2_site,
@@ -2519,6 +2527,7 @@
   net::SchemefulSite youtube_site(youtube_gurl);
 
   mock_first_party_sets_handler().SetGlobalSets(net::GlobalFirstPartySets(
+      kFirstPartySetsVersion,
       {{youtube_site,
         {net::FirstPartySetEntry(youtube_primary_site,
                                  net::SiteType::kAssociated, 0)}}},
diff --git a/content/browser/first_party_sets/database/first_party_sets_database.cc b/content/browser/first_party_sets/database/first_party_sets_database.cc
index d7bd203..110a875 100644
--- a/content/browser/first_party_sets/database/first_party_sets_database.cc
+++ b/content/browser/first_party_sets/database/first_party_sets_database.cc
@@ -146,7 +146,6 @@
 
 bool FirstPartySetsDatabase::PersistSets(
     const std::string& browser_context_id,
-    const base::Version& public_sets_version,
     const net::GlobalFirstPartySets& sets,
     const net::FirstPartySetsContextConfig& config) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -158,8 +157,8 @@
     return false;
 
   // Only persist public sets if the version is valid.
-  if (public_sets_version.IsValid() &&
-      !SetPublicSets(browser_context_id, public_sets_version, sets)) {
+  if (sets.public_sets_version().IsValid() &&
+      !SetPublicSets(browser_context_id, sets)) {
     return false;
   }
 
@@ -174,13 +173,12 @@
 
 bool FirstPartySetsDatabase::SetPublicSets(
     const std::string& browser_context_id,
-    const base::Version& sets_version,
     const net::GlobalFirstPartySets& sets) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_EQ(db_status_, InitStatus::kSuccess);
-  DCHECK(sets_version.IsValid());
+  DCHECK(sets.public_sets_version().IsValid());
 
-  const std::string& version = sets_version.GetString();
+  const std::string& version = sets.public_sets_version().GetString();
   // Checks if the version of the current public sets is referenced by *any*
   // browser context in the public_sets_version table. If so, that means the
   // sets already exist in public_sets table and we don't need to write them to
@@ -376,8 +374,9 @@
       db_->GetCachedStatement(SQL_FROM_HERE, kVersionSql));
   version_statement.BindString(0, browser_context_id);
 
+  std::string version;
   if (version_statement.Step()) {
-    const std::string version = version_statement.ColumnString(0);
+    version = version_statement.ColumnString(0);
 
     static constexpr char kSelectSql[] =
         "SELECT site,primary_site,site_type FROM public_sets WHERE version=?";
@@ -414,7 +413,8 @@
 
   // Aliases are merged with entries inside of the public sets table so it is
   // sufficient to declare the global sets object with only the entries field.
-  net::GlobalFirstPartySets global_sets(entries, /*aliases=*/{});
+  net::GlobalFirstPartySets global_sets(base::Version(version), entries,
+                                        /*aliases=*/{});
 
   // Query & apply manual set.
   global_sets.ApplyManuallySpecifiedSet(FetchManualSets(browser_context_id));
diff --git a/content/browser/first_party_sets/database/first_party_sets_database.h b/content/browser/first_party_sets/database/first_party_sets_database.h
index 49ef6f91..b520e8a8 100644
--- a/content/browser/first_party_sets/database/first_party_sets_database.h
+++ b/content/browser/first_party_sets/database/first_party_sets_database.h
@@ -20,10 +20,6 @@
 #include "content/common/content_export.h"
 #include "sql/meta_table.h"
 
-namespace base {
-class Version;
-}  // namespace base
-
 namespace net {
 class FirstPartySetEntry;
 class FirstPartySetsCacheFilter;
@@ -78,7 +74,6 @@
   // database in one transaction.
   [[nodiscard]] bool PersistSets(
       const std::string& browser_context_id,
-      const base::Version& public_sets_version,
       const net::GlobalFirstPartySets& sets,
       const net::FirstPartySetsContextConfig& config);
 
@@ -123,7 +118,6 @@
   // the sets version used by `browser_context_id`. `sets_version` must be
   // valid. Returns true on success.
   [[nodiscard]] bool SetPublicSets(const std::string& browser_context_id,
-                                   const base::Version& sets_version,
                                    const net::GlobalFirstPartySets& sets);
 
   // Stores the Manual Sets into manual_sets table, and returns true on success.
diff --git a/content/browser/first_party_sets/database/first_party_sets_database_unittest.cc b/content/browser/first_party_sets/database/first_party_sets_database_unittest.cc
index 39b99ae..1d0e241 100644
--- a/content/browser/first_party_sets/database/first_party_sets_database_unittest.cc
+++ b/content/browser/first_party_sets/database/first_party_sets_database_unittest.cc
@@ -301,14 +301,14 @@
   const std::string site_member2 = "https://member2.test";
 
   net::GlobalFirstPartySets global_sets(
-      /*entries=*/{{net::SchemefulSite(GURL(site)),
-                    net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
-                                            net::SiteType::kAssociated,
-                                            absl::nullopt)},
-                   {net::SchemefulSite(GURL(primary)),
-                    net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
-                                            net::SiteType::kPrimary,
-                                            absl::nullopt)}},
+      version,
+      /*entries=*/
+      {{net::SchemefulSite(GURL(site)),
+        net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
+                                net::SiteType::kAssociated, absl::nullopt)},
+       {net::SchemefulSite(GURL(primary)),
+        net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
+                                net::SiteType::kPrimary, absl::nullopt)}},
       /*aliases=*/{});
   base::flat_map<net::SchemefulSite, net::FirstPartySetEntry> manual_sets = {
       {net::SchemefulSite(GURL(manual_site)),
@@ -327,8 +327,7 @@
 
   OpenDatabase();
   // Trigger the lazy-initialization.
-  EXPECT_TRUE(
-      db()->PersistSets(browser_context_id, version, global_sets, config));
+  EXPECT_TRUE(db()->PersistSets(browser_context_id, global_sets, config));
   CloseDatabase();
 
   sql::Database db;
@@ -412,14 +411,14 @@
   const std::string site_member2 = "https://member2.test";
 
   net::GlobalFirstPartySets global_sets(
-      /*entries=*/{{net::SchemefulSite(GURL(site)),
-                    net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
-                                            net::SiteType::kAssociated,
-                                            absl::nullopt)},
-                   {net::SchemefulSite(GURL(primary)),
-                    net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
-                                            net::SiteType::kPrimary,
-                                            absl::nullopt)}},
+      base::Version(),
+      /*entries=*/
+      {{net::SchemefulSite(GURL(site)),
+        net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
+                                net::SiteType::kAssociated, absl::nullopt)},
+       {net::SchemefulSite(GURL(primary)),
+        net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
+                                net::SiteType::kPrimary, absl::nullopt)}},
       /*aliases=*/{});
 
   base::flat_map<net::SchemefulSite, net::FirstPartySetEntry> manual_sets = {
@@ -439,8 +438,7 @@
 
   OpenDatabase();
   // Trigger the lazy-initialization.
-  EXPECT_TRUE(db()->PersistSets(browser_context_id, base::Version(),
-                                global_sets, config));
+  EXPECT_TRUE(db()->PersistSets(browser_context_id, global_sets, config));
   CloseDatabase();
 
   sql::Database db;
@@ -557,14 +555,14 @@
   const std::string site_member2 = "https://member4.test";
 
   net::GlobalFirstPartySets global_sets(
-      /*entries=*/{{net::SchemefulSite(GURL(site)),
-                    net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
-                                            net::SiteType::kAssociated,
-                                            absl::nullopt)},
-                   {net::SchemefulSite(GURL(primary)),
-                    net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
-                                            net::SiteType::kPrimary,
-                                            absl::nullopt)}},
+      version,
+      /*entries=*/
+      {{net::SchemefulSite(GURL(site)),
+        net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
+                                net::SiteType::kAssociated, absl::nullopt)},
+       {net::SchemefulSite(GURL(primary)),
+        net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
+                                net::SiteType::kPrimary, absl::nullopt)}},
       /*aliases=*/{});
 
   base::flat_map<net::SchemefulSite, net::FirstPartySetEntry> manual_sets = {
@@ -584,8 +582,7 @@
 
   OpenDatabase();
   // Trigger the lazy-initialization.
-  EXPECT_TRUE(
-      db()->PersistSets(browser_context_id, version, global_sets, config));
+  EXPECT_TRUE(db()->PersistSets(browser_context_id, global_sets, config));
   CloseDatabase();
 
   // Verify data is inserted.
@@ -685,19 +682,19 @@
   const std::string primary = "https://site2.test";
 
   net::GlobalFirstPartySets input(
-      /*entries=*/{{net::SchemefulSite(GURL(site)),
-                    net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
-                                            net::SiteType::kAssociated,
-                                            absl::nullopt)},
-                   {net::SchemefulSite(GURL(primary)),
-                    net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
-                                            net::SiteType::kPrimary,
-                                            absl::nullopt)}},
+      version,
+      /*entries=*/
+      {{net::SchemefulSite(GURL(site)),
+        net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
+                                net::SiteType::kAssociated, absl::nullopt)},
+       {net::SchemefulSite(GURL(primary)),
+        net::FirstPartySetEntry(net::SchemefulSite(GURL(primary)),
+                                net::SiteType::kPrimary, absl::nullopt)}},
       /*aliases=*/{});
 
   OpenDatabase();
   // Trigger the lazy-initialization.
-  EXPECT_TRUE(db()->PersistSets(browser_context_id, version, input,
+  EXPECT_TRUE(db()->PersistSets(browser_context_id, input,
                                 net::FirstPartySetsContextConfig()));
   CloseDatabase();
 
@@ -977,12 +974,12 @@
   const net::SchemefulSite manual_primary(GURL("https://bbb.test"));
 
   net::GlobalFirstPartySets global_sets(
-      /*entries=*/{{site,
-                    net::FirstPartySetEntry(primary, net::SiteType::kAssociated,
-                                            absl::nullopt)},
-                   {primary,
-                    net::FirstPartySetEntry(primary, net::SiteType::kPrimary,
-                                            absl::nullopt)}},
+      base::Version(),
+      /*entries=*/
+      {{site, net::FirstPartySetEntry(primary, net::SiteType::kAssociated,
+                                      absl::nullopt)},
+       {primary, net::FirstPartySetEntry(primary, net::SiteType::kPrimary,
+                                         absl::nullopt)}},
       /*aliases=*/{});
 
   base::flat_map<net::SchemefulSite, net::FirstPartySetEntry> manual_sets = {
@@ -997,8 +994,7 @@
   OpenDatabase();
   // Trigger the lazy-initialization and insert data with a invalid version, so
   // that public sets will not be persisted.
-  ASSERT_TRUE(db()->PersistSets(browser_context_id, base::Version(),
-                                global_sets,
+  ASSERT_TRUE(db()->PersistSets(browser_context_id, global_sets,
                                 net::FirstPartySetsContextConfig()));
   EXPECT_THAT(
       db()->GetGlobalSets(browser_context_id)
@@ -1080,15 +1076,15 @@
   const net::SchemefulSite config_site_member2(GURL("https://member2.test"));
 
   net::GlobalFirstPartySets global_sets(
-      /*entries=*/{{associated_site,
-                    net::FirstPartySetEntry(primary, net::SiteType::kAssociated,
-                                            absl::nullopt)},
-                   {service_site,
-                    net::FirstPartySetEntry(primary, net::SiteType::kService,
-                                            absl::nullopt)},
-                   {primary,
-                    net::FirstPartySetEntry(primary, net::SiteType::kPrimary,
-                                            absl::nullopt)}},
+      version,
+      /*entries=*/
+      {{associated_site,
+        net::FirstPartySetEntry(primary, net::SiteType::kAssociated,
+                                absl::nullopt)},
+       {service_site, net::FirstPartySetEntry(primary, net::SiteType::kService,
+                                              absl::nullopt)},
+       {primary, net::FirstPartySetEntry(primary, net::SiteType::kPrimary,
+                                         absl::nullopt)}},
       /*aliases=*/{});
   base::flat_map<net::SchemefulSite, net::FirstPartySetEntry> manual_sets = {
       {manual_associated_site,
@@ -1110,8 +1106,7 @@
 
   OpenDatabase();
   // Trigger the lazy-initialization.
-  EXPECT_TRUE(
-      db()->PersistSets(browser_context_id, version, global_sets, config));
+  EXPECT_TRUE(db()->PersistSets(browser_context_id, global_sets, config));
 
   EXPECT_EQ(db()->GetGlobalSets(browser_context_id), global_sets);
   EXPECT_EQ(db()->FetchPolicyConfigurations(browser_context_id), config);
diff --git a/content/browser/first_party_sets/first_party_sets_handler_database_helper.cc b/content/browser/first_party_sets/first_party_sets_handler_database_helper.cc
index 3107bf3..0a7775b 100644
--- a/content/browser/first_party_sets/first_party_sets_handler_database_helper.cc
+++ b/content/browser/first_party_sets/first_party_sets_handler_database_helper.cc
@@ -88,12 +88,11 @@
 
 void FirstPartySetsHandlerDatabaseHelper::PersistSets(
     const std::string& browser_context_id,
-    const base::Version& version,
     const net::GlobalFirstPartySets& sets,
     const net::FirstPartySetsContextConfig& config) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!browser_context_id.empty());
-  if (!db_->PersistSets(browser_context_id, version, sets, config))
+  if (!db_->PersistSets(browser_context_id, sets, config))
     DVLOG(1) << "Failed to write sets into the database.";
 }
 
diff --git a/content/browser/first_party_sets/first_party_sets_handler_database_helper.h b/content/browser/first_party_sets/first_party_sets_handler_database_helper.h
index 85faf0f..a0a0674 100644
--- a/content/browser/first_party_sets/first_party_sets_handler_database_helper.h
+++ b/content/browser/first_party_sets/first_party_sets_handler_database_helper.h
@@ -17,10 +17,6 @@
 #include "content/common/content_export.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
-namespace base {
-class Version;
-}  // namespace base
-
 namespace net {
 class FirstPartySetsCacheFilter;
 class FirstPartySetsContextConfig;
@@ -85,7 +81,6 @@
 
   // Wraps FirstPartySetsDatabase::PersistSets.
   void PersistSets(const std::string& browser_context_id,
-                   const base::Version& version,
                    const net::GlobalFirstPartySets& sets,
                    const net::FirstPartySetsContextConfig& config);
 
diff --git a/content/browser/first_party_sets/first_party_sets_handler_database_helper_unittest.cc b/content/browser/first_party_sets/first_party_sets_handler_database_helper_unittest.cc
index 5bd5914..49f6bb7 100644
--- a/content/browser/first_party_sets/first_party_sets_handler_database_helper_unittest.cc
+++ b/content/browser/first_party_sets/first_party_sets_handler_database_helper_unittest.cc
@@ -28,7 +28,7 @@
 
 namespace content {
 namespace {
-
+const base::Version kVersion("1.2.3");
 }  // namespace
 
 TEST(FirstPartySetsHandlerDatabaseHelper, ComputeSetsDiff_SitesJoined) {
@@ -39,16 +39,18 @@
   net::SchemefulSite member3(GURL("https://member3.test"));
 
   net::GlobalFirstPartySets old_sets(
-      /*entries=*/{{example,
-                    net::FirstPartySetEntry(example, net::SiteType::kPrimary,
-                                            absl::nullopt)},
-                   {member1, net::FirstPartySetEntry(
-                                 example, net::SiteType::kAssociated, 0)},
-                   {member3, net::FirstPartySetEntry(
-                                 example, net::SiteType::kAssociated, 1)}},
+      kVersion,
+      /*entries=*/
+      {{example, net::FirstPartySetEntry(example, net::SiteType::kPrimary,
+                                         absl::nullopt)},
+       {member1,
+        net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)},
+       {member3,
+        net::FirstPartySetEntry(example, net::SiteType::kAssociated, 1)}},
       /*aliases=*/{});
 
   net::GlobalFirstPartySets current_sets(
+      kVersion,
       /*entries=*/
       {
           {example, net::FirstPartySetEntry(example, net::SiteType::kPrimary,
@@ -81,25 +83,26 @@
   net::SchemefulSite member3(GURL("https://member3.test"));
 
   net::GlobalFirstPartySets old_sets(
-      /*entries=*/{{example,
-                    net::FirstPartySetEntry(example, net::SiteType::kPrimary,
-                                            absl::nullopt)},
-                   {member1, net::FirstPartySetEntry(
-                                 example, net::SiteType::kAssociated, 0)},
-                   {member3, net::FirstPartySetEntry(
-                                 example, net::SiteType::kAssociated, 1)},
-                   {foo, net::FirstPartySetEntry(foo, net::SiteType::kPrimary,
-                                                 absl::nullopt)},
-                   {member2, net::FirstPartySetEntry(
-                                 foo, net::SiteType::kAssociated, 0)}},
+      kVersion,
+      /*entries=*/
+      {{example, net::FirstPartySetEntry(example, net::SiteType::kPrimary,
+                                         absl::nullopt)},
+       {member1,
+        net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)},
+       {member3,
+        net::FirstPartySetEntry(example, net::SiteType::kAssociated, 1)},
+       {foo,
+        net::FirstPartySetEntry(foo, net::SiteType::kPrimary, absl::nullopt)},
+       {member2, net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)}},
       /*aliases=*/{});
 
   net::GlobalFirstPartySets current_sets(
-      /*entries=*/{{example,
-                    net::FirstPartySetEntry(example, net::SiteType::kPrimary,
-                                            absl::nullopt)},
-                   {member1, net::FirstPartySetEntry(
-                                 example, net::SiteType::kAssociated, 0)}},
+      kVersion,
+      /*entries=*/
+      {{example, net::FirstPartySetEntry(example, net::SiteType::kPrimary,
+                                         absl::nullopt)},
+       {member1,
+        net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)}},
       /*aliases=*/{});
 
   // Expected diff: "https://foo.test", "https://member2.test" and
@@ -119,31 +122,30 @@
   net::SchemefulSite member3(GURL("https://member3.test"));
 
   net::GlobalFirstPartySets old_sets(
-      /*entries=*/{{example,
-                    net::FirstPartySetEntry(example, net::SiteType::kPrimary,
-                                            absl::nullopt)},
-                   {member1, net::FirstPartySetEntry(
-                                 example, net::SiteType::kAssociated, 0)},
-                   {foo, net::FirstPartySetEntry(foo, net::SiteType::kPrimary,
-                                                 absl::nullopt)},
-                   {member2, net::FirstPartySetEntry(
-                                 foo, net::SiteType::kAssociated, 0)},
-                   {member3, net::FirstPartySetEntry(
-                                 foo, net::SiteType::kAssociated, 1)}},
+      kVersion,
+      /*entries=*/
+      {{example, net::FirstPartySetEntry(example, net::SiteType::kPrimary,
+                                         absl::nullopt)},
+       {member1,
+        net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)},
+       {foo,
+        net::FirstPartySetEntry(foo, net::SiteType::kPrimary, absl::nullopt)},
+       {member2, net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)},
+       {member3, net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 1)}},
       /*aliases=*/{});
 
   net::GlobalFirstPartySets current_sets(
-      /*entries=*/{{example,
-                    net::FirstPartySetEntry(example, net::SiteType::kPrimary,
-                                            absl::nullopt)},
-                   {member1, net::FirstPartySetEntry(
-                                 example, net::SiteType::kAssociated, 0)},
-                   {member3, net::FirstPartySetEntry(
-                                 example, net::SiteType::kAssociated, 1)},
-                   {foo, net::FirstPartySetEntry(foo, net::SiteType::kPrimary,
-                                                 absl::nullopt)},
-                   {member2, net::FirstPartySetEntry(
-                                 foo, net::SiteType::kAssociated, 0)}},
+      kVersion,
+      /*entries=*/
+      {{example, net::FirstPartySetEntry(example, net::SiteType::kPrimary,
+                                         absl::nullopt)},
+       {member1,
+        net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)},
+       {member3,
+        net::FirstPartySetEntry(example, net::SiteType::kAssociated, 1)},
+       {foo,
+        net::FirstPartySetEntry(foo, net::SiteType::kPrimary, absl::nullopt)},
+       {member2, net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)}},
       /*aliases=*/{});
 
   // Expected diff: "https://member3.test" changed owner.
@@ -160,20 +162,20 @@
   net::SchemefulSite bar(GURL("https://bar.test"));
 
   net::GlobalFirstPartySets old_sets(
-      /*entries=*/{{example,
-                    net::FirstPartySetEntry(example, net::SiteType::kPrimary,
-                                            absl::nullopt)},
-                   {foo, net::FirstPartySetEntry(
-                             example, net::SiteType::kAssociated, 0)},
-                   {bar, net::FirstPartySetEntry(
-                             example, net::SiteType::kAssociated, 1)}},
+      kVersion,
+      /*entries=*/
+      {{example, net::FirstPartySetEntry(example, net::SiteType::kPrimary,
+                                         absl::nullopt)},
+       {foo, net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)},
+       {bar, net::FirstPartySetEntry(example, net::SiteType::kAssociated, 1)}},
       /*aliases=*/{});
 
   net::GlobalFirstPartySets current_sets(
-      /*entries=*/{{foo, net::FirstPartySetEntry(foo, net::SiteType::kPrimary,
-                                                 absl::nullopt)},
-                   {bar, net::FirstPartySetEntry(
-                             foo, net::SiteType::kAssociated, 0)}},
+      kVersion,
+      /*entries=*/
+      {{foo,
+        net::FirstPartySetEntry(foo, net::SiteType::kPrimary, absl::nullopt)},
+       {bar, net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)}},
       /*aliases=*/{});
 
   // Expected diff: "https://example.test" left FPSs, "https://foo.test" and
@@ -193,18 +195,19 @@
   net::SchemefulSite foo(GURL("https://foo.test"));
 
   net::GlobalFirstPartySets old_sets(
-      /*entries=*/{{example,
-                    net::FirstPartySetEntry(example, net::SiteType::kPrimary,
-                                            absl::nullopt)},
-                   {foo, net::FirstPartySetEntry(
-                             example, net::SiteType::kAssociated, 0)}},
+      kVersion,
+      /*entries=*/
+      {{example, net::FirstPartySetEntry(example, net::SiteType::kPrimary,
+                                         absl::nullopt)},
+       {foo, net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)}},
       /*aliases=*/{});
 
   net::GlobalFirstPartySets current_sets(
-      /*entries=*/{{example, net::FirstPartySetEntry(
-                                 foo, net::SiteType::kAssociated, 0)},
-                   {foo, net::FirstPartySetEntry(foo, net::SiteType::kPrimary,
-                                                 absl::nullopt)}},
+      kVersion,
+      /*entries=*/
+      {{example, net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)},
+       {foo,
+        net::FirstPartySetEntry(foo, net::SiteType::kPrimary, absl::nullopt)}},
       /*aliases=*/{});
 
   // Expected diff: "https://example.test" and "https://foo.test" changed owner.
@@ -223,11 +226,12 @@
 
   // Empty old_sets.
   net::GlobalFirstPartySets current_sets(
-      /*entries=*/{{example,
-                    net::FirstPartySetEntry(example, net::SiteType::kPrimary,
-                                            absl::nullopt)},
-                   {member1, net::FirstPartySetEntry(
-                                 example, net::SiteType::kAssociated, 0)}},
+      kVersion,
+      /*entries=*/
+      {{example, net::FirstPartySetEntry(example, net::SiteType::kPrimary,
+                                         absl::nullopt)},
+       {member1,
+        net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)}},
       /*aliases=*/{});
 
   EXPECT_THAT(
@@ -244,11 +248,12 @@
 
   // Empty current sets.
   net::GlobalFirstPartySets old_sets(
-      /*entries=*/{{example,
-                    net::FirstPartySetEntry(example, net::SiteType::kPrimary,
-                                            absl::nullopt)},
-                   {member1, net::FirstPartySetEntry(
-                                 example, net::SiteType::kAssociated, 0)}},
+      kVersion,
+      /*entries=*/
+      {{example, net::FirstPartySetEntry(example, net::SiteType::kPrimary,
+                                         absl::nullopt)},
+       {member1,
+        net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)}},
       /*aliases=*/{});
 
   EXPECT_THAT(FirstPartySetsHandlerDatabaseHelper::ComputeSetsDiff(
@@ -285,11 +290,12 @@
   net::SchemefulSite member1(GURL("https://member1.test"));
 
   net::GlobalFirstPartySets sets(
-      /*entries=*/{{example,
-                    net::FirstPartySetEntry(example, net::SiteType::kPrimary,
-                                            absl::nullopt)},
-                   {member1, net::FirstPartySetEntry(
-                                 example, net::SiteType::kAssociated, 0)}},
+      kVersion,
+      /*entries=*/
+      {{example, net::FirstPartySetEntry(example, net::SiteType::kPrimary,
+                                         absl::nullopt)},
+       {member1,
+        net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)}},
       /*aliases=*/{});
 
   // "https://example.test" was removed from FPSs by policy modifications.
@@ -432,28 +438,30 @@
   const std::string browser_context_id("b");
 
   db_helper_->PersistSets(
-      browser_context_id, base::Version("0.0.1"),
+      browser_context_id,
       net::GlobalFirstPartySets(
-          /*entries=*/{{example,
-                        net::FirstPartySetEntry(
-                            example, net::SiteType::kPrimary, absl::nullopt)},
-                       {member1, net::FirstPartySetEntry(
-                                     example, net::SiteType::kAssociated, 0)},
-                       {member3, net::FirstPartySetEntry(
-                                     example, net::SiteType::kAssociated, 1)},
-                       {foo, net::FirstPartySetEntry(
-                                 foo, net::SiteType::kPrimary, absl::nullopt)},
-                       {member2, net::FirstPartySetEntry(
-                                     foo, net::SiteType::kAssociated, 0)}},
+          base::Version("0.0.1"),
+          /*entries=*/
+          {{example, net::FirstPartySetEntry(example, net::SiteType::kPrimary,
+                                             absl::nullopt)},
+           {member1,
+            net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)},
+           {member3,
+            net::FirstPartySetEntry(example, net::SiteType::kAssociated, 1)},
+           {foo, net::FirstPartySetEntry(foo, net::SiteType::kPrimary,
+                                         absl::nullopt)},
+           {member2,
+            net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)}},
           /*aliases=*/{}),
       /*config=*/net::FirstPartySetsContextConfig());
 
   net::GlobalFirstPartySets current_sets(
-      /*entries=*/{{example,
-                    net::FirstPartySetEntry(example, net::SiteType::kPrimary,
-                                            absl::nullopt)},
-                   {member1, net::FirstPartySetEntry(
-                                 example, net::SiteType::kAssociated, 0)}},
+      kVersion,
+      /*entries=*/
+      {{example, net::FirstPartySetEntry(example, net::SiteType::kPrimary,
+                                         absl::nullopt)},
+       {member1,
+        net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)}},
       /*aliases=*/{});
 
   std::pair<std::vector<net::SchemefulSite>, net::FirstPartySetsCacheFilter>
diff --git a/content/browser/first_party_sets/first_party_sets_handler_impl.cc b/content/browser/first_party_sets/first_party_sets_handler_impl.cc
index 2a088f2..c0ea195 100644
--- a/content/browser/first_party_sets/first_party_sets_handler_impl.cc
+++ b/content/browser/first_party_sets/first_party_sets_handler_impl.cc
@@ -187,7 +187,7 @@
   if (IsEnabled()) {
     sets_loader_->SetManuallySpecifiedSet(local_set);
     if (!embedder_will_provide_public_sets_) {
-      sets_loader_->SetComponentSets(base::File());
+      sets_loader_->SetComponentSets(base::Version(), base::File());
     }
   } else {
     SetCompleteSets(net::GlobalFirstPartySets());
@@ -206,9 +206,8 @@
   DCHECK(enabled_);
   DCHECK(embedder_will_provide_public_sets_);
 
-  // TODO(crbug.com/1219656): Use this value to compute sets diff.
-  version_ = version;
-  sets_loader_->SetComponentSets(std::move(sets_file));
+  // TODO(crbug.com/1219656): Use the version to compute sets diff.
+  sets_loader_->SetComponentSets(version, std::move(sets_file));
 }
 
 void FirstPartySetsHandlerImpl::GetPersistedGlobalSetsForTesting(
@@ -419,7 +418,7 @@
   }
 
   db_helper_.AsyncCall(&FirstPartySetsHandlerDatabaseHelper::PersistSets)
-      .WithArgs(browser_context_id, version_, global_sets_->Clone(),
+      .WithArgs(browser_context_id, global_sets_->Clone(),
                 context_config.Clone());
   std::move(callback).Run(std::move(context_config), std::move(cache_filter));
 }
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 e2298b5..69800147 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
@@ -230,13 +230,6 @@
   absl::optional<net::GlobalFirstPartySets> global_sets_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
-  // The version of the public First-Party Sets. It is invalid until the
-  // `SetPublicFirstPartySets()` is called with a valid version.
-  //
-  // TODO(crbug.com/1384184): move `version_` into GlobalFirstPartySets class to
-  // guarantee that we don't use public sets content if version is invalid.
-  base::Version version_;
-
   bool enabled_ GUARDED_BY_CONTEXT(sequence_checker_);
   bool embedder_will_provide_public_sets_ GUARDED_BY_CONTEXT(sequence_checker_);
 
diff --git a/content/browser/first_party_sets/first_party_sets_handler_impl_unittest.cc b/content/browser/first_party_sets/first_party_sets_handler_impl_unittest.cc
index bda34e17..bdc395f 100644
--- a/content/browser/first_party_sets/first_party_sets_handler_impl_unittest.cc
+++ b/content/browser/first_party_sets/first_party_sets_handler_impl_unittest.cc
@@ -546,7 +546,7 @@
       R"({"primary": "https://example.test", )"
       R"("associatedSites": ["https://associatedsite.test"]})";
   ASSERT_TRUE(base::JSONReader::Read(input));
-  handler().SetPublicFirstPartySets(base::Version(),
+  handler().SetPublicFirstPartySets(base::Version("1.2.3"),
                                     WritePublicSetsFile(input));
 
   handler().Init(scoped_dir_.GetPath(), LocalSetDeclaration());
@@ -582,7 +582,7 @@
       R"({"primary": "https://example.test", )"
       R"("associatedSites": ["https://associatedsite.test"]})";
   ASSERT_TRUE(base::JSONReader::Read(input));
-  handler().SetPublicFirstPartySets(base::Version(),
+  handler().SetPublicFirstPartySets(base::Version("1.2.3"),
                                     WritePublicSetsFile(input));
 
   EXPECT_THAT(
@@ -612,7 +612,7 @@
   handler().Init(scoped_dir_.GetPath(), LocalSetDeclaration());
 
   handler().SetPublicFirstPartySets(
-      base::Version(),
+      base::Version("1.2.3"),
       WritePublicSetsFile(
           R"({"primary": "https://example.test", )"
           R"("associatedSites": ["https://associatedsite.test"]})"));
@@ -646,7 +646,7 @@
   handler().Init(scoped_dir_.GetPath(), LocalSetDeclaration());
 
   handler().SetPublicFirstPartySets(
-      base::Version(),
+      base::Version("1.2.3"),
       WritePublicSetsFile(
           R"({"primary": "https://example.test", )"
           R"("associatedSites": ["https://associatedsite.test"]})"));
@@ -682,7 +682,7 @@
         R"({"primary": "https://primary1.test", )"
         R"("associatedSites": ["https://associatedsite1.test", "https://associatedsite2.test"]})";
     ASSERT_TRUE(base::JSONReader::Read(input));
-    handler().SetPublicFirstPartySets(base::Version(),
+    handler().SetPublicFirstPartySets(base::Version("1.2.3"),
                                       WritePublicSetsFile(input));
 
     ASSERT_THAT(
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 d01dfa7..6bbf868e 100644
--- a/content/browser/first_party_sets/first_party_sets_loader.cc
+++ b/content/browser/first_party_sets/first_party_sets_loader.cc
@@ -17,6 +17,7 @@
 #include "base/ranges/algorithm.h"
 #include "base/sequence_checker.h"
 #include "base/task/thread_pool.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 "net/base/schemeful_site.h"
@@ -55,7 +56,8 @@
   MaybeFinishLoading();
 }
 
-void FirstPartySetsLoader::SetComponentSets(base::File sets_file) {
+void FirstPartySetsLoader::SetComponentSets(base::Version version,
+                                            base::File sets_file) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (component_sets_parse_progress_ != Progress::kNotStarted) {
     DisposeFile(std::move(sets_file));
@@ -64,8 +66,8 @@
 
   component_sets_parse_progress_ = Progress::kStarted;
 
-  if (!sets_file.IsValid()) {
-    OnReadSetsFile("");
+  if (!sets_file.IsValid() || !version.IsValid()) {
+    OnReadSetsFile(base::Version(), "");
     return;
   }
 
@@ -75,17 +77,19 @@
       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
       base::BindOnce(&ReadSetsFile, std::move(sets_file)),
       base::BindOnce(&FirstPartySetsLoader::OnReadSetsFile,
-                     weak_factory_.GetWeakPtr()));
+                     weak_factory_.GetWeakPtr(), std::move(version)));
 }
 
-void FirstPartySetsLoader::OnReadSetsFile(const std::string& raw_sets) {
+void FirstPartySetsLoader::OnReadSetsFile(base::Version version,
+                                          const std::string& raw_sets) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_EQ(component_sets_parse_progress_, Progress::kStarted);
 
   std::istringstream stream(raw_sets);
   FirstPartySetParser::SetsAndAliases public_sets =
       FirstPartySetParser::ParseSetsFromStream(stream, /*emit_errors=*/false);
-  sets_ = net::GlobalFirstPartySets(std::move(public_sets.first),
+  sets_ = net::GlobalFirstPartySets(std::move(version),
+                                    std::move(public_sets.first),
                                     std::move(public_sets.second));
 
   component_sets_parse_progress_ = Progress::kFinished;
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 5504ea85..2ccf2af 100644
--- a/content/browser/first_party_sets/first_party_sets_loader.h
+++ b/content/browser/first_party_sets/first_party_sets_loader.h
@@ -45,7 +45,7 @@
   //
   // Only the first call to SetComponentSets can have any effect; subsequent
   // invocations are ignored.
-  void SetComponentSets(base::File sets_file);
+  void SetComponentSets(base::Version version, base::File sets_file);
 
   // Close the file on thread pool that allows blocking.
   void DisposeFile(base::File sets_file);
@@ -53,7 +53,7 @@
  private:
   // Parses the contents of `raw_sets` as a collection of First-Party Set
   // declarations, and stores the result.
-  void OnReadSetsFile(const std::string& raw_sets);
+  void OnReadSetsFile(base::Version version, const std::string& raw_sets);
 
   // Checks the required inputs have been received, and if so, invokes the
   // callback `on_load_complete_`, after merging sets appropriately.
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 ee6a719..7b5f058 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
@@ -33,7 +33,9 @@
 
 namespace {
 
-void SetComponentSets(FirstPartySetsLoader& loader, base::StringPiece content) {
+void SetComponentSets(FirstPartySetsLoader& loader,
+                      base::Version version,
+                      base::StringPiece content) {
   base::ScopedTempDir temp_dir;
   CHECK(temp_dir.CreateUniqueTempDir());
   base::FilePath path =
@@ -41,7 +43,7 @@
   CHECK(base::WriteFile(path, content));
 
   loader.SetComponentSets(
-      base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ));
+      version, base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ));
 }
 
 }  // namespace
@@ -62,7 +64,22 @@
 
 TEST_F(FirstPartySetsLoaderTest, IgnoresInvalidFile) {
   loader().SetManuallySpecifiedSet(LocalSetDeclaration());
-  SetComponentSets(loader(), "certainly not valid JSON");
+  SetComponentSets(loader(), base::Version("1.2.3"),
+                   "certainly not valid JSON");
+  EXPECT_EQ(WaitAndGetResult().FindEntry(
+                net::SchemefulSite(GURL("https://example.test")),
+                net::FirstPartySetsContextConfig()),
+            absl::nullopt);
+}
+
+TEST_F(FirstPartySetsLoaderTest, IgnoresInvalidVersion) {
+  loader().SetManuallySpecifiedSet(LocalSetDeclaration());
+  SetComponentSets(
+      loader(), base::Version(),
+      "{\"primary\": \"https://example.test\",\"associatedSites\": "
+      "[\"https://associatedsite1.test\"]}\n"
+      "{\"primary\": \"https://foo.test\",\"associatedSites\": "
+      "[\"https://associatedsite2.test\"]}");
   EXPECT_EQ(WaitAndGetResult().FindEntry(
                 net::SchemefulSite(GURL("https://example.test")),
                 net::FirstPartySetsContextConfig()),
@@ -76,7 +93,7 @@
   net::SchemefulSite associated2(GURL("https://associatedsite2.test"));
 
   SetComponentSets(
-      loader(),
+      loader(), base::Version("1.2.3"),
       "{\"primary\": \"https://example.test\",\"associatedSites\": "
       "[\"https://associatedsite1.test\"]}\n"
       "{\"primary\": \"https://foo.test\",\"associatedSites\": "
@@ -104,13 +121,13 @@
   net::SchemefulSite foo(GURL("https://foo.test"));
   net::SchemefulSite foo2(GURL("https://foo2.test"));
 
-  SetComponentSets(loader(),
+  SetComponentSets(loader(), base::Version("1.2.3"),
                    R"({"primary": "https://example.test",)"
                    R"("associatedSites": ["https://associatedsite1.test"]})"
                    "\n"
                    R"({"primary": "https://foo.test",)"
                    R"("associatedSites": ["https://associatedsite2.test"]})");
-  SetComponentSets(loader(),
+  SetComponentSets(loader(), base::Version("1.2.3"),
                    R"({ "primary": "https://example2.test",)"
                    R"("associatedSites": ["https://associatedsite1.test"]})"
                    "\n"
@@ -131,7 +148,7 @@
 }
 
 TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified) {
-  SetComponentSets(loader(),
+  SetComponentSets(loader(), base::Version("1.2.3"),
                    R"({"primary": "https://example.test", "associatedSites": )"
                    R"(["https://associatedsite1.test"]})");
   loader().SetManuallySpecifiedSet(LocalSetDeclaration(
diff --git a/mojo/public/cpp/base/BUILD.gn b/mojo/public/cpp/base/BUILD.gn
index a041512..ecb18f9 100644
--- a/mojo/public/cpp/base/BUILD.gn
+++ b/mojo/public/cpp/base/BUILD.gn
@@ -113,6 +113,8 @@
     "unguessable_token_mojom_traits.h",
     "values_mojom_traits.cc",
     "values_mojom_traits.h",
+    "version_mojom_traits.cc",
+    "version_mojom_traits.h",
   ]
 
   defines = [ "IS_MOJO_BASE_SHARED_TRAITS_IMPL" ]
@@ -148,6 +150,7 @@
     "token_unittest.cc",
     "unguessable_token_unittest.cc",
     "values_unittest.cc",
+    "version_unittest.cc",
   ]
 
   public_deps = [
diff --git a/mojo/public/cpp/base/version_mojom_traits.cc b/mojo/public/cpp/base/version_mojom_traits.cc
new file mode 100644
index 0000000..49bf6f9
--- /dev/null
+++ b/mojo/public/cpp/base/version_mojom_traits.cc
@@ -0,0 +1,23 @@
+// 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 "mojo/public/cpp/base/version_mojom_traits.h"
+
+#include <cstdint>
+#include <vector>
+
+namespace mojo {
+
+bool StructTraits<mojo_base::mojom::VersionDataView, base::Version>::Read(
+    mojo_base::mojom::VersionDataView data,
+    base::Version* out) {
+  std::vector<uint32_t> components;
+  if (!data.ReadComponents(&components))
+    return false;
+
+  *out = base::Version(std::move(components));
+  return true;
+}
+
+}  // namespace mojo
diff --git a/mojo/public/cpp/base/version_mojom_traits.h b/mojo/public/cpp/base/version_mojom_traits.h
new file mode 100644
index 0000000..b77db625
--- /dev/null
+++ b/mojo/public/cpp/base/version_mojom_traits.h
@@ -0,0 +1,29 @@
+// 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.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_VERSION_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_VERSION_MOJOM_TRAITS_H_
+
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/version.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/mojom/base/version.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+    StructTraits<mojo_base::mojom::VersionDataView, base::Version> {
+  static const std::vector<uint32_t>& components(const base::Version& in) {
+    return in.components();
+  }
+
+  static bool Read(mojo_base::mojom::VersionDataView data, base::Version* out);
+};
+
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_BASE_VERSION_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/version_unittest.cc b/mojo/public/cpp/base/version_unittest.cc
new file mode 100644
index 0000000..20a4674
--- /dev/null
+++ b/mojo/public/cpp/base/version_unittest.cc
@@ -0,0 +1,27 @@
+// 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 "base/version.h"
+#include "mojo/public/cpp/base/version_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/version.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+
+TEST(VersionStructTraitsTest, InvalidVersion) {
+  base::Version in;
+  base::Version out;
+  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Version>(in, out));
+  EXPECT_FALSE(out.IsValid());
+}
+
+TEST(VersionStructTraitsTest, ValidVersion) {
+  base::Version in("9.8.7.6");
+  base::Version out;
+  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Version>(in, out));
+  EXPECT_EQ(in, out);
+}
+
+}  // namespace mojo_base
diff --git a/mojo/public/mojom/base/BUILD.gn b/mojo/public/mojom/base/BUILD.gn
index 552313e..3eeeddf 100644
--- a/mojo/public/mojom/base/BUILD.gn
+++ b/mojo/public/mojom/base/BUILD.gn
@@ -35,6 +35,7 @@
     "token.mojom",
     "unguessable_token.mojom",
     "values.mojom",
+    "version.mojom",
   ]
 
   if (is_win) {
@@ -374,6 +375,19 @@
         "//third_party/abseil-cpp:absl",
       ]
     },
+    {
+      types = [
+        {
+          mojom = "mojo_base.mojom.Version"
+          cpp = "::base::Version"
+        },
+      ]
+      traits_headers = [ "//mojo/public/cpp/base/version_mojom_traits.h" ]
+      traits_public_deps = [
+        "//base",
+        "//mojo/public/cpp/base:shared_typemap_traits",
+      ]
+    },
   ]
 
   cpp_typemaps = common_typemaps
diff --git a/mojo/public/mojom/base/version.mojom b/mojo/public/mojom/base/version.mojom
new file mode 100644
index 0000000..c2484ef
--- /dev/null
+++ b/mojo/public/mojom/base/version.mojom
@@ -0,0 +1,11 @@
+// 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.
+
+module mojo_base.mojom;
+
+// Version represents a dotted version number, like "1.2.3.4".
+
+struct Version {
+  array<uint32> components;
+};
diff --git a/net/first_party_sets/global_first_party_sets.cc b/net/first_party_sets/global_first_party_sets.cc
index 84891b07..fe7e738 100644
--- a/net/first_party_sets/global_first_party_sets.cc
+++ b/net/first_party_sets/global_first_party_sets.cc
@@ -77,19 +77,28 @@
 GlobalFirstPartySets::GlobalFirstPartySets() = default;
 
 GlobalFirstPartySets::GlobalFirstPartySets(
+    base::Version public_sets_version,
     base::flat_map<SchemefulSite, FirstPartySetEntry> entries,
     base::flat_map<SchemefulSite, SchemefulSite> aliases)
-    : GlobalFirstPartySets(std::move(entries),
-                           std::move(aliases),
-                           /*manual_sets=*/{},
-                           FirstPartySetsContextConfig()) {}
+    : GlobalFirstPartySets(
+          public_sets_version,
+          public_sets_version.IsValid()
+              ? std::move(entries)
+              : base::flat_map<SchemefulSite, FirstPartySetEntry>(),
+          public_sets_version.IsValid()
+              ? std::move(aliases)
+              : base::flat_map<SchemefulSite, SchemefulSite>(),
+          /*manual_sets=*/{},
+          FirstPartySetsContextConfig()) {}
 
 GlobalFirstPartySets::GlobalFirstPartySets(
+    base::Version public_sets_version,
     base::flat_map<SchemefulSite, FirstPartySetEntry> entries,
     base::flat_map<SchemefulSite, SchemefulSite> aliases,
     base::flat_map<SchemefulSite, FirstPartySetEntry> manual_sets,
     FirstPartySetsContextConfig manual_config)
-    : entries_(std::move(entries)),
+    : public_sets_version_(std::move(public_sets_version)),
+      entries_(std::move(entries)),
       aliases_(std::move(aliases)),
       manual_sets_(std::move(manual_sets)),
       manual_config_(std::move(manual_config)) {
@@ -107,9 +116,10 @@
 GlobalFirstPartySets::~GlobalFirstPartySets() = default;
 
 bool GlobalFirstPartySets::operator==(const GlobalFirstPartySets& other) const {
-  return std::tie(entries_, aliases_, manual_sets_, manual_config_) ==
-         std::tie(other.entries_, other.aliases_, other.manual_sets_,
-                  other.manual_config_);
+  return std::tie(public_sets_version_, entries_, aliases_, manual_sets_,
+                  manual_config_) ==
+         std::tie(other.public_sets_version_, other.entries_, other.aliases_,
+                  other.manual_sets_, other.manual_config_);
 }
 
 bool GlobalFirstPartySets::operator!=(const GlobalFirstPartySets& other) const {
@@ -117,8 +127,8 @@
 }
 
 GlobalFirstPartySets GlobalFirstPartySets::Clone() const {
-  return GlobalFirstPartySets(entries_, aliases_, manual_sets_,
-                              manual_config_.Clone());
+  return GlobalFirstPartySets(public_sets_version_, entries_, aliases_,
+                              manual_sets_, manual_config_.Clone());
 }
 
 absl::optional<FirstPartySetEntry> GlobalFirstPartySets::FindEntry(
diff --git a/net/first_party_sets/global_first_party_sets.h b/net/first_party_sets/global_first_party_sets.h
index 7ab6f3ba..d77ce4d 100644
--- a/net/first_party_sets/global_first_party_sets.h
+++ b/net/first_party_sets/global_first_party_sets.h
@@ -10,6 +10,7 @@
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
 #include "base/functional/function_ref.h"
+#include "base/version.h"
 #include "net/base/net_export.h"
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/first_party_set_entry.h"
@@ -37,6 +38,7 @@
  public:
   GlobalFirstPartySets();
   GlobalFirstPartySets(
+      base::Version public_sets_version,
       base::flat_map<SchemefulSite, FirstPartySetEntry> entries,
       base::flat_map<SchemefulSite, SchemefulSite> aliases);
 
@@ -112,6 +114,10 @@
   // Whether the global sets are empty.
   bool empty() const { return entries_.empty() && manual_config_.empty(); }
 
+  const base::Version& public_sets_version() const {
+    return public_sets_version_;
+  }
+
   const base::flat_map<SchemefulSite, FirstPartySetEntry>& manual_sets() const {
     return manual_sets_;
   }
@@ -125,6 +131,7 @@
                                              const GlobalFirstPartySets& sets);
 
   GlobalFirstPartySets(
+      base::Version public_sets_version,
       base::flat_map<SchemefulSite, FirstPartySetEntry> entries,
       base::flat_map<SchemefulSite, SchemefulSite> aliases,
       base::flat_map<SchemefulSite, FirstPartySetEntry> manual_sets,
@@ -176,6 +183,9 @@
     return manual_config_;
   }
 
+  // The version associated with the component_updater-provided public sets.
+  base::Version public_sets_version_;
+
   // Represents the mapping of site -> entry, where keys are sites within sets,
   // and values are entries of the sets.
   base::flat_map<SchemefulSite, FirstPartySetEntry> entries_;
diff --git a/net/first_party_sets/global_first_party_sets_unittest.cc b/net/first_party_sets/global_first_party_sets_unittest.cc
index e6b0f894..4cc7397 100644
--- a/net/first_party_sets/global_first_party_sets_unittest.cc
+++ b/net/first_party_sets/global_first_party_sets_unittest.cc
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/containers/flat_map.h"
+#include "base/version.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"
@@ -27,6 +28,7 @@
 
 namespace {
 
+const base::Version kVersion("1.2.3");
 const SchemefulSite kPrimary(GURL("https://primary.test"));
 const SchemefulSite kPrimary2(GURL("https://primary2.test"));
 const SchemefulSite kPrimary3(GURL("https://primary3.test"));
@@ -65,7 +67,24 @@
   GlobalFirstPartySetsTest() = default;
 };
 
+TEST_F(GlobalFirstPartySetsTest, CtorSkipsInvalidVersion) {
+  GlobalFirstPartySets sets(
+      base::Version(), /*entries=*/
+      {
+          {kPrimary,
+           FirstPartySetEntry(kPrimary, SiteType::kPrimary, absl::nullopt)},
+          {kAssociated1,
+           FirstPartySetEntry(kPrimary, SiteType::kAssociated, 0)},
+      },
+      /*aliases=*/{});
+
+  EXPECT_THAT(
+      sets.FindEntries({kPrimary, kAssociated1}, FirstPartySetsContextConfig()),
+      IsEmpty());
+}
+
 TEST_F(GlobalFirstPartySetsTest, Clone) {
+  base::Version version("1.2.3.4.5");
   const SchemefulSite example(GURL("https://example.test"));
   const SchemefulSite example_cctld(GURL("https://example.cctld"));
   const SchemefulSite member1(GURL("https://member1.test"));
@@ -77,10 +96,10 @@
   const FirstPartySetEntry foo_entry(foo, SiteType::kPrimary, absl::nullopt);
   const FirstPartySetEntry member2_entry(foo, SiteType::kAssociated, 1);
 
-  GlobalFirstPartySets sets(
-      /*entries=*/
-      {{example, entry}, {member1, member1_entry}},
-      /*aliases=*/{{example_cctld, example}});
+  GlobalFirstPartySets sets(version,
+                            /*entries=*/
+                            {{example, entry}, {member1, member1_entry}},
+                            /*aliases=*/{{example_cctld, example}});
   sets.ApplyManuallySpecifiedSet({{foo, foo_entry}, {member2, member2_entry}});
 
   EXPECT_EQ(sets, sets.Clone());
@@ -100,12 +119,12 @@
   FirstPartySetEntry entry(example, SiteType::kPrimary, absl::nullopt);
   FirstPartySetEntry decoy_entry(example, SiteType::kAssociated, 1);
 
-  EXPECT_THAT(GlobalFirstPartySets(
-                  {
-                      {example, entry},
-                      {decoy_site, decoy_entry},
-                  },
-                  {})
+  EXPECT_THAT(GlobalFirstPartySets(kVersion,
+                                   {
+                                       {example, entry},
+                                       {decoy_site, decoy_entry},
+                                   },
+                                   {})
                   .FindEntry(example, FirstPartySetsContextConfig()),
               Optional(entry));
 }
@@ -115,11 +134,11 @@
   SchemefulSite wss_example(GURL("wss://example.test"));
   FirstPartySetEntry entry(https_example, SiteType::kPrimary, absl::nullopt);
 
-  EXPECT_THAT(GlobalFirstPartySets(
-                  {
-                      {https_example, entry},
-                  },
-                  {})
+  EXPECT_THAT(GlobalFirstPartySets(kVersion,
+                                   {
+                                       {https_example, entry},
+                                   },
+                                   {})
                   .FindEntry(wss_example, FirstPartySetsContextConfig()),
               Optional(entry));
 }
@@ -131,11 +150,11 @@
 
   FirstPartySetsContextConfig config({{example, override_entry}});
 
-  EXPECT_THAT(GlobalFirstPartySets(
-                  {
-                      {example, public_entry},
-                  },
-                  {})
+  EXPECT_THAT(GlobalFirstPartySets(kVersion,
+                                   {
+                                       {example, public_entry},
+                                   },
+                                   {})
                   .FindEntry(example, config),
               Optional(override_entry));
 }
@@ -146,11 +165,11 @@
 
   FirstPartySetsContextConfig config({{example, absl::nullopt}});
 
-  EXPECT_THAT(GlobalFirstPartySets(
-                  {
-                      {example, public_entry},
-                  },
-                  {})
+  EXPECT_THAT(GlobalFirstPartySets(kVersion,
+                                   {
+                                       {example, public_entry},
+                                   },
+                                   {})
                   .FindEntry(example, config),
               absl::nullopt);
 }
@@ -160,11 +179,11 @@
   SchemefulSite example_cctld(GURL("https://example.cctld"));
   FirstPartySetEntry entry(example, SiteType::kPrimary, absl::nullopt);
 
-  EXPECT_THAT(GlobalFirstPartySets(
-                  {
-                      {example, entry},
-                  },
-                  {{example_cctld, example}})
+  EXPECT_THAT(GlobalFirstPartySets(kVersion,
+                                   {
+                                       {example, entry},
+                                   },
+                                   {{example_cctld, example}})
                   .FindEntry(example_cctld, FirstPartySetsContextConfig()),
               Optional(entry));
 }
@@ -177,11 +196,11 @@
 
   FirstPartySetsContextConfig config({{example_cctld, override_entry}});
 
-  EXPECT_THAT(GlobalFirstPartySets(
-                  {
-                      {example, public_entry},
-                  },
-                  {{example_cctld, example}})
+  EXPECT_THAT(GlobalFirstPartySets(kVersion,
+                                   {
+                                       {example, public_entry},
+                                   },
+                                   {{example_cctld, example}})
                   .FindEntry(example_cctld, config),
               Optional(override_entry));
 }
@@ -193,11 +212,11 @@
 
   FirstPartySetsContextConfig config({{example_cctld, absl::nullopt}});
 
-  EXPECT_THAT(GlobalFirstPartySets(
-                  {
-                      {example, public_entry},
-                  },
-                  {{example_cctld, example}})
+  EXPECT_THAT(GlobalFirstPartySets(kVersion,
+                                   {
+                                       {example, public_entry},
+                                   },
+                                   {{example_cctld, example}})
                   .FindEntry(example_cctld, config),
               absl::nullopt);
 }
@@ -212,11 +231,11 @@
 
   // FindEntry should ignore aliases when using the customizations. Public
   // aliases only apply to sites in the public sets.
-  EXPECT_THAT(GlobalFirstPartySets(
-                  {
-                      {example, public_entry},
-                  },
-                  {{example_cctld, example}})
+  EXPECT_THAT(GlobalFirstPartySets(kVersion,
+                                   {
+                                       {example, public_entry},
+                                   },
+                                   {{example_cctld, example}})
                   .FindEntry(example_cctld, config),
               public_entry);
 }
@@ -228,6 +247,7 @@
 TEST_F(GlobalFirstPartySetsTest, Empty_NonemptyEntries) {
   EXPECT_FALSE(
       GlobalFirstPartySets(
+          kVersion,
           {
               {kPrimary,
                FirstPartySetEntry(kPrimary, SiteType::kPrimary, absl::nullopt)},
@@ -248,6 +268,36 @@
   EXPECT_FALSE(sets.empty());
 }
 
+TEST_F(GlobalFirstPartySetsTest, InvalidPublicSetsVersion_NonemptyManualSet) {
+  GlobalFirstPartySets sets(
+      base::Version(), /*entries=*/
+      {
+          {kPrimary,
+           FirstPartySetEntry(kPrimary, SiteType::kPrimary, absl::nullopt)},
+          {kAssociated1,
+           FirstPartySetEntry(kPrimary, SiteType::kAssociated, 0)},
+      },
+      /*aliases=*/{});
+  ASSERT_TRUE(sets.empty());
+  sets.ApplyManuallySpecifiedSet({
+      {kPrimary,
+       FirstPartySetEntry(kPrimary, SiteType::kPrimary, absl::nullopt)},
+      {kAssociated4, FirstPartySetEntry(kPrimary, SiteType::kAssociated, 0)},
+  });
+
+  // The manual set should still be available, even though the component was
+  // invalid.
+  EXPECT_FALSE(sets.empty());
+  EXPECT_THAT(
+      sets.FindEntries({kPrimary, kAssociated1, kAssociated4},
+                       FirstPartySetsContextConfig()),
+      UnorderedElementsAre(
+          Pair(kPrimary,
+               FirstPartySetEntry(kPrimary, SiteType::kPrimary, absl::nullopt)),
+          Pair(kAssociated4,
+               FirstPartySetEntry(kPrimary, SiteType::kAssociated, 0))));
+}
+
 TEST_F(GlobalFirstPartySetsTest,
        ForEachEffectiveSetEntry_ManualSetAndConfig_FullIteration) {
   GlobalFirstPartySets global_sets;
@@ -301,6 +351,7 @@
  public:
   PopulatedGlobalFirstPartySetsTest()
       : global_sets_(
+            kVersion,
             {
                 {kPrimary, FirstPartySetEntry(kPrimary,
                                               SiteType::kPrimary,
@@ -1010,6 +1061,7 @@
 
 TEST_F(GlobalFirstPartySetsTest, ComputeConfig_Empty) {
   EXPECT_EQ(GlobalFirstPartySets(
+                kVersion,
                 /*entries=*/
                 {
                     {kPrimary, FirstPartySetEntry(kPrimary, SiteType::kPrimary,
@@ -1025,6 +1077,7 @@
 TEST_F(GlobalFirstPartySetsTest,
        ComputeConfig_Replacements_NoIntersection_NoRemoval) {
   GlobalFirstPartySets sets(
+      kVersion,
       /*entries=*/
       {
           {kPrimary,
@@ -1061,6 +1114,7 @@
     GlobalFirstPartySetsTest,
     ComputeConfig_Replacements_ReplacesExistingAssociatedSite_RemovedFromFormerSet) {
   GlobalFirstPartySets sets(
+      kVersion,
       /*entries=*/
       {
           {kPrimary,
@@ -1099,6 +1153,7 @@
     GlobalFirstPartySetsTest,
     ComputeConfig_Replacements_ReplacesExistingPrimary_RemovesFormerAssociatedSites) {
   GlobalFirstPartySets sets(
+      kVersion,
       /*entries=*/
       {
           {kPrimary,
@@ -1136,6 +1191,7 @@
     GlobalFirstPartySetsTest,
     ComputeConfig_Replacements_ReplacesExistingAssociatedSite_RemovesSingletons) {
   GlobalFirstPartySets sets(
+      kVersion,
       /*entries=*/
       {
           {kPrimary,
@@ -1171,6 +1227,7 @@
 TEST_F(GlobalFirstPartySetsTest,
        ComputeConfig_Additions_NoIntersection_AddsWithoutUpdating) {
   GlobalFirstPartySets sets(
+      kVersion,
       /*entries=*/
       {
           {kPrimary,
@@ -1207,6 +1264,7 @@
     GlobalFirstPartySetsTest,
     ComputeConfig_Additions_PolicyPrimaryIsExistingAssociatedSite_PolicySetAbsorbsExistingSet) {
   GlobalFirstPartySets sets(
+      kVersion,
       /*entries=*/
       {
           {kPrimary,
@@ -1254,6 +1312,7 @@
     GlobalFirstPartySetsTest,
     ComputeConfig_Additions_PolicyPrimaryIsExistingPrimary_PolicySetAbsorbsExistingAssociatedSites) {
   GlobalFirstPartySets sets(
+      kVersion,
       /*entries=*/
       {
           {kPrimary,
@@ -1291,6 +1350,7 @@
     GlobalFirstPartySetsTest,
     ComputeConfig_ReplacementsAndAdditions_SetListsOverlapWithSameExistingSet) {
   GlobalFirstPartySets sets(
+      kVersion,
       /*entries=*/
       {
           {kPrimary,
@@ -1352,6 +1412,7 @@
   // the normalized addition set since it was provided first. The other addition
   // sets are unaffected.
   GlobalFirstPartySets sets(
+      kVersion,
       /*entries=*/
       {
           {primary1,
@@ -1433,6 +1494,7 @@
   // the normalized addition set since it was provided first. The other addition
   // sets are unaffected.
   GlobalFirstPartySets sets(
+      kVersion,
       /*entries=*/
       {
           {primary2,
@@ -1500,6 +1562,51 @@
                                              absl::nullopt))));
 }
 
+TEST_F(GlobalFirstPartySetsTest, InvalidPublicSetsVersion_ComputeConfig) {
+  const GlobalFirstPartySets sets(
+      base::Version(), /*entries=*/
+      {
+          {kPrimary,
+           FirstPartySetEntry(kPrimary, SiteType::kPrimary, absl::nullopt)},
+          {kAssociated1,
+           FirstPartySetEntry(kPrimary, SiteType::kAssociated, 0)},
+      },
+      /*aliases=*/{});
+  ASSERT_TRUE(sets.empty());
+
+  FirstPartySetsContextConfig config = sets.ComputeConfig(
+      /*replacement_sets=*/
+      {
+          {
+              {kPrimary2, FirstPartySetEntry(kPrimary2, SiteType::kPrimary,
+                                             absl::nullopt)},
+              {kAssociated2,
+               FirstPartySetEntry(kPrimary2, SiteType::kAssociated,
+                                  absl::nullopt)},
+          },
+      },
+      /*addition_sets=*/{});
+
+  // The config should still be nonempty, even though the component was invalid.
+  EXPECT_FALSE(config.empty());
+
+  EXPECT_THAT(
+      sets.FindEntries(
+          {
+              kPrimary,
+              kPrimary2,
+              kAssociated1,
+              kAssociated2,
+          },
+          config),
+      UnorderedElementsAre(
+          Pair(kAssociated2,
+               FirstPartySetEntry(kPrimary2, SiteType::kAssociated,
+                                  absl::nullopt)),
+          Pair(kPrimary2, FirstPartySetEntry(kPrimary2, SiteType::kPrimary,
+                                             absl::nullopt))));
+}
+
 class GlobalFirstPartySetsWithConfigTest
     : public PopulatedGlobalFirstPartySetsTest {
  public:
diff --git a/services/network/first_party_sets/first_party_sets_access_delegate_unittest.cc b/services/network/first_party_sets/first_party_sets_access_delegate_unittest.cc
index c63983b..f98668fa 100644
--- a/services/network/first_party_sets/first_party_sets_access_delegate_unittest.cc
+++ b/services/network/first_party_sets/first_party_sets_access_delegate_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/functional/callback_helpers.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
+#include "base/version.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -83,6 +84,7 @@
             /*params=*/nullptr,
             &first_party_sets_manager_) {
     first_party_sets_manager_.SetCompleteSets(net::GlobalFirstPartySets(
+        base::Version("1.2.3"),
         /*entries=*/
         {
             {kSet1Member1, net::FirstPartySetEntry(
@@ -143,6 +145,7 @@
                   CreateFirstPartySetsAccessDelegateParams(enabled),
                   &first_party_sets_manager_) {
     first_party_sets_manager_.SetCompleteSets(net::GlobalFirstPartySets(
+        base::Version("1.2.3"),
         /*entries=*/
         {
             {kSet1Member1, net::FirstPartySetEntry(
diff --git a/services/network/first_party_sets/first_party_sets_manager_unittest.cc b/services/network/first_party_sets/first_party_sets_manager_unittest.cc
index 36bf63e35..b61ed22 100644
--- a/services/network/first_party_sets/first_party_sets_manager_unittest.cc
+++ b/services/network/first_party_sets/first_party_sets_manager_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/functional/callback_helpers.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
+#include "base/version.h"
 #include "net/base/schemeful_site.h"
 #include "net/cookies/cookie_constants.h"
 #include "net/first_party_sets/first_party_set_entry.h"
@@ -42,7 +43,8 @@
       const base::flat_map<net::SchemefulSite, net::FirstPartySetEntry>&
           content,
       const base::flat_map<net::SchemefulSite, net::SchemefulSite>& aliases) {
-    manager_.SetCompleteSets(net::GlobalFirstPartySets(content, aliases));
+    manager_.SetCompleteSets(
+        net::GlobalFirstPartySets(base::Version("1.2.3"), content, aliases));
   }
 
   FirstPartySetsManager::EntriesResult FindEntriesAndWait(
diff --git a/services/network/public/cpp/BUILD.gn b/services/network/public/cpp/BUILD.gn
index 0303be9..abedbe9 100644
--- a/services/network/public/cpp/BUILD.gn
+++ b/services/network/public/cpp/BUILD.gn
@@ -314,6 +314,7 @@
   ]
   deps = [
     ":schemeful_site_mojom_support",
+    "//mojo/public/cpp/base:shared_typemap_traits",
     "//net",
     "//services/network/public/mojom:mojom_first_party_sets_shared",
   ]
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 a714fd6b..933c281 100644
--- a/services/network/public/cpp/first_party_sets_mojom_traits.cc
+++ b/services/network/public/cpp/first_party_sets_mojom_traits.cc
@@ -6,6 +6,8 @@
 
 #include "base/containers/flat_map.h"
 #include "base/types/optional_util.h"
+#include "base/version.h"
+#include "mojo/public/cpp/base/version_mojom_traits.h"
 #include "mojo/public/cpp/bindings/enum_traits.h"
 #include "mojo/public/cpp/bindings/struct_traits.h"
 #include "net/base/schemeful_site.h"
@@ -148,6 +150,10 @@
                   net::GlobalFirstPartySets>::
     Read(network::mojom::GlobalFirstPartySetsDataView sets,
          net::GlobalFirstPartySets* out_sets) {
+  base::Version public_sets_version;
+  if (!sets.ReadPublicSetsVersion(&public_sets_version))
+    return false;
+
   base::flat_map<net::SchemefulSite, net::FirstPartySetEntry> entries;
   if (!sets.ReadSets(&entries))
     return false;
@@ -164,8 +170,9 @@
   if (!sets.ReadManualConfig(&manual_config))
     return false;
 
-  *out_sets = net::GlobalFirstPartySets(entries, aliases, manual_entries,
-                                        std::move(manual_config));
+  *out_sets = net::GlobalFirstPartySets(
+      std::move(public_sets_version), std::move(entries), std::move(aliases),
+      std::move(manual_entries), std::move(manual_config));
 
   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 059cc0f..3368479 100644
--- a/services/network/public/cpp/first_party_sets_mojom_traits.h
+++ b/services/network/public/cpp/first_party_sets_mojom_traits.h
@@ -6,6 +6,7 @@
 #define SERVICES_NETWORK_PUBLIC_CPP_FIRST_PARTY_SETS_MOJOM_TRAITS_H_
 
 #include "base/containers/flat_map.h"
+#include "base/version.h"
 #include "mojo/public/cpp/bindings/enum_traits.h"
 #include "mojo/public/cpp/bindings/struct_traits.h"
 #include "net/base/schemeful_site.h"
@@ -110,6 +111,11 @@
 struct COMPONENT_EXPORT(FIRST_PARTY_SETS_MOJOM_TRAITS)
     StructTraits<network::mojom::GlobalFirstPartySetsDataView,
                  net::GlobalFirstPartySets> {
+  static const base::Version& public_sets_version(
+      const net::GlobalFirstPartySets& sets) {
+    return sets.public_sets_version_;
+  }
+
   static const base::flat_map<net::SchemefulSite, net::FirstPartySetEntry>&
   sets(const net::GlobalFirstPartySets& sets) {
     return sets.entries();
diff --git a/services/network/public/cpp/first_party_sets_mojom_traits_unittest.cc b/services/network/public/cpp/first_party_sets_mojom_traits_unittest.cc
index 02a1a6e1..79b1ac4 100644
--- a/services/network/public/cpp/first_party_sets_mojom_traits_unittest.cc
+++ b/services/network/public/cpp/first_party_sets_mojom_traits_unittest.cc
@@ -126,6 +126,7 @@
   net::SchemefulSite c(GURL("https://c.test"));
 
   net::GlobalFirstPartySets original(
+      base::Version("1.2.3"),
       /*entries=*/
       {
           {a,
@@ -147,6 +148,7 @@
           original, round_tripped));
 
   EXPECT_EQ(original, round_tripped);
+  EXPECT_FALSE(round_tripped.empty());
 }
 
 TEST(FirstPartySetsTraitsTest, RoundTrips_FirstPartySetsContextConfig) {
diff --git a/services/network/public/mojom/first_party_sets.mojom b/services/network/public/mojom/first_party_sets.mojom
index cd0b73e..b733608 100644
--- a/services/network/public/mojom/first_party_sets.mojom
+++ b/services/network/public/mojom/first_party_sets.mojom
@@ -4,6 +4,7 @@
 
 module network.mojom;
 
+import "mojo/public/mojom/base/version.mojom";
 import "services/network/public/mojom/schemeful_site.mojom";
 
 // This struct should match net::FirstPartySetEntry::SiteIndex in
@@ -81,6 +82,9 @@
 // This struct must match the class fields defined in
 // //net/first_party_sets/global_first_party_sets.h.
 struct GlobalFirstPartySets {
+  // The version of the public sets component.
+  mojo_base.mojom.Version public_sets_version;
+
   // The mapping from site to FPS entry from public sets.
   map<SchemefulSite, FirstPartySetEntry> sets;
 
diff --git a/services/network/restricted_cookie_manager_unittest.cc b/services/network/restricted_cookie_manager_unittest.cc
index b16bc382..e7f276f 100644
--- a/services/network/restricted_cookie_manager_unittest.cc
+++ b/services/network/restricted_cookie_manager_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
 #include "base/time/time.h"
+#include "base/version.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
@@ -466,6 +467,7 @@
             CreateFirstPartySetsAccessDelegateParams(),
             &first_party_sets_manager_) {
     first_party_sets_manager_.SetCompleteSets(net::GlobalFirstPartySets(
+        base::Version("1.2.3"),
         /*entries=*/
         {
             {net::SchemefulSite(GURL("https://example.com")),