[SigninPrefs] Create the main structure for Accounts SigninPrefs

Add a structure that takes care of all the prefs related to accounts.
Accounts are mapped by their GaiaId which is used as the key of the
main dictionary prefs. The account dictionary pref will hold the
specific data prefs that will be populated in a follow up change.

The structure also allows the deletion of those prefs.
This management is expected to be controlled by changes done
through the IdentityManager. Attached those calls to the
GaiaInfoUpdateService as it already listens to most needed calls and
already update the ProfileAttributesStorage.

The creation of the prefs is done in a lazy mode. Prefs dictionary
and pref data are created when the values are set for the first time.
The GaiaIds used to create the prefs are expected to be valid, point
to a valid account in Chrome. Otherwise the pref, even though it will
be created at first, will probably be deleted after a call to
/ListAccount.

The structure could actually be replaced by some free functions since
it is currently not held anywhere or doesn't hold any independent
run time information, but restricting all access to a class scope
allows to maintain a more coupled relation with the different future
usages and allows for easier extension, such as listening to
preference changes for example that might be needed later on.

The code done is very similar to what is already done in SyncPrefs,
however it is not big/complex enough to actually share the code,
which would have made the logic more complex than it is. Also the
underlying need will have different usages, duplicating some simple
parts of the code is acceptable in this case since it is mainly
around container manipulation.

Bug: b:331767195, b:335437309
Change-Id: Ia209e5de1020bfb012d4cdc8ad43b28e3b393b73
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5487922
Reviewed-by: David Roger <[email protected]>
Commit-Queue: Ryan Sultanem <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1293739}
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index e1186a23..09569ecc 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -166,6 +166,7 @@
 #include "components/segmentation_platform/public/segmentation_platform_service.h"
 #include "components/sessions/core/session_id_generator.h"
 #include "components/signin/public/base/signin_pref_names.h"
+#include "components/signin/public/base/signin_prefs.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/site_engagement/content/site_engagement_service.h"
 #include "components/subresource_filter/content/shared/browser/ruleset_service.h"
@@ -1893,6 +1894,7 @@
       registry);
   SessionStartupPref::RegisterProfilePrefs(registry);
   SharingSyncPreference::RegisterProfilePrefs(registry);
+  SigninPrefs::RegisterProfilePrefs(registry);
   site_engagement::SiteEngagementService::RegisterProfilePrefs(registry);
   supervised_user::RegisterProfilePrefs(registry);
   sync_sessions::SessionSyncPrefs::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/profiles/gaia_info_update_service.cc b/chrome/browser/profiles/gaia_info_update_service.cc
index 9e18ea17..9cbfaea 100644
--- a/chrome/browser/profiles/gaia_info_update_service.cc
+++ b/chrome/browser/profiles/gaia_info_update_service.cc
@@ -6,6 +6,7 @@
 
 #include <stddef.h>
 
+#include "base/containers/contains.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
@@ -19,18 +20,66 @@
 #include "components/signin/public/base/avatar_icon_util.h"
 #include "components/signin/public/base/consent_level.h"
 #include "components/signin/public/base/signin_pref_names.h"
+#include "components/signin/public/base/signin_prefs.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
 #include "content/public/browser/storage_partition.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/image/image.h"
 
+namespace {
+
+void UpdateAccountsPrefs(
+    PrefService& pref_service,
+    const signin::IdentityManager& identity_manager,
+    const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info) {
+  if (!accounts_in_cookie_jar_info.accounts_are_fresh) {
+    return;
+  }
+
+  // Get all accounts in Chrome; both signed in and signed out accounts.
+  base::flat_set<SigninPrefs::GaiaId> account_ids_in_chrome;
+  for (const auto& account : accounts_in_cookie_jar_info.signed_in_accounts) {
+    account_ids_in_chrome.insert(account.gaia_id);
+  }
+  for (const auto& account : accounts_in_cookie_jar_info.signed_out_accounts) {
+    account_ids_in_chrome.insert(account.gaia_id);
+  }
+
+  // If there is a Primary account, also keep it even if it was removed (not in
+  // the cookie jar at all).
+  // Note: Make sure that `primary_account_info` and `account_ids_in_chrome`
+  // have the same lifetime, since `IdentityManager::GetPrimaryAccountInfo()`
+  // returns a copy, and `account_ids_in_chrome` is a set of
+  // `SigninPrefs::GaiaId` which are `std::string_view` (references); in order
+  // for the reference not to outlive the actual string.
+  CoreAccountInfo primary_account_info =
+      identity_manager.GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
+  if (!primary_account_info.IsEmpty()) {
+    // Set will make sure it is not duplicated if already added.
+    account_ids_in_chrome.insert(primary_account_info.gaia);
+  }
+  // TODO(b/331767195): In case the prefs are needed for ChromeOS and Android
+  // (platforms where the account is tied to the OS) in the future, we would
+  // also need to keep the accounts that have an AccountInfo that is still
+  // present in Chrome (accounts that have refresh tokens) in addition to the
+  // above checks on cookies and primary account.
+
+  SigninPrefs signin_prefs(pref_service);
+  signin_prefs.RemoveAllAccountPrefsExcept(account_ids_in_chrome);
+}
+
+}  // namespace
+
 GAIAInfoUpdateService::GAIAInfoUpdateService(
     signin::IdentityManager* identity_manager,
     ProfileAttributesStorage* profile_attributes_storage,
+    PrefService& pref_service,
     const base::FilePath& profile_path)
     : identity_manager_(identity_manager),
       profile_attributes_storage_(profile_attributes_storage),
+      pref_service_(pref_service),
       profile_path_(profile_path) {
   identity_manager_->AddObserver(this);
 
@@ -130,6 +179,11 @@
       break;
     case signin::PrimaryAccountChangeEvent::Type::kCleared:
       ClearProfileEntry();
+
+      // When clearing the primary account, if the account is already removed
+      // from the cookie jar, we should remove the prefs as well.
+      UpdateAccountsPrefs(pref_service_.get(), *identity_manager_,
+                          identity_manager_->GetAccountsInCookieJar());
       break;
     case signin::PrimaryAccountChangeEvent::Type::kNone:
       break;
@@ -176,6 +230,9 @@
           identity_manager_->FindExtendedAccountInfoByAccountId(account.id));
     }
   }
+
+  UpdateAccountsPrefs(pref_service_.get(), *identity_manager_,
+                      accounts_in_cookie_jar_info);
 }
 
 bool GAIAInfoUpdateService::ShouldUpdatePrimaryAccount() {
diff --git a/chrome/browser/profiles/gaia_info_update_service.h b/chrome/browser/profiles/gaia_info_update_service.h
index 68f7b46..a214719 100644
--- a/chrome/browser/profiles/gaia_info_update_service.h
+++ b/chrome/browser/profiles/gaia_info_update_service.h
@@ -17,11 +17,13 @@
 
 // This service kicks off a download of the user's name and profile picture.
 // The results are saved in the profile info cache.
+// It also manages the lifecycle of the signin accounts prefs.
 class GAIAInfoUpdateService : public KeyedService,
                               public signin::IdentityManager::Observer {
  public:
   GAIAInfoUpdateService(signin::IdentityManager* identity_manager,
                         ProfileAttributesStorage* profile_attributes_storage,
+                        PrefService& pref_service,
                         const base::FilePath& profile_path);
 
   GAIAInfoUpdateService(const GAIAInfoUpdateService&) = delete;
@@ -52,6 +54,7 @@
 
   raw_ptr<signin::IdentityManager> identity_manager_;
   raw_ptr<ProfileAttributesStorage> profile_attributes_storage_;
+  raw_ref<PrefService> pref_service_;
   const base::FilePath profile_path_;
   // TODO(msalama): remove when |SigninProfileAttributesUpdater| is folded into
   // |GAIAInfoUpdateService|.
diff --git a/chrome/browser/profiles/gaia_info_update_service_factory.cc b/chrome/browser/profiles/gaia_info_update_service_factory.cc
index 8d37760..c1626a3 100644
--- a/chrome/browser/profiles/gaia_info_update_service_factory.cc
+++ b/chrome/browser/profiles/gaia_info_update_service_factory.cc
@@ -51,7 +51,7 @@
   return std::make_unique<GAIAInfoUpdateService>(
       IdentityManagerFactory::GetForProfile(profile),
       &g_browser_process->profile_manager()->GetProfileAttributesStorage(),
-      profile->GetPath());
+      *profile->GetPrefs(), profile->GetPath());
 }
 
 bool GAIAInfoUpdateServiceFactory::ServiceIsNULLWhileTesting() const {
diff --git a/chrome/browser/profiles/gaia_info_update_service_unittest.cc b/chrome/browser/profiles/gaia_info_update_service_unittest.cc
index 93656081..a808f3d 100644
--- a/chrome/browser/profiles/gaia_info_update_service_unittest.cc
+++ b/chrome/browser/profiles/gaia_info_update_service_unittest.cc
@@ -28,11 +28,12 @@
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/prefs/pref_service.h"
 #include "components/profile_metrics/state.h"
+#include "components/signin/public/base/consent_level.h"
 #include "components/signin/public/base/signin_pref_names.h"
+#include "components/signin/public/base/signin_prefs.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/sync_preferences/pref_service_syncable.h"
 #include "content/public/test/browser_task_environment.h"
-#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_unittest_util.h"
@@ -62,13 +63,18 @@
 const char kChromiumOrgDomain[] = "chromium.org";
 #endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)
 
+}  // namespace
+
 class GAIAInfoUpdateServiceTest : public testing::Test {
  protected:
   GAIAInfoUpdateServiceTest()
       : testing_profile_manager_(TestingBrowserProcess::GetGlobal()),
-        identity_test_env_(/*test_url_loader_factory=*/nullptr,
-                           /*pref_service=*/nullptr,
-                           /*test_signin_client=*/nullptr) {}
+        identity_test_env_(
+            /*test_url_loader_factory=*/nullptr,
+            /*pref_service=*/nullptr,
+            /*test_signin_client=*/nullptr) {
+    SigninPrefs::RegisterProfilePrefs(pref_service_.registry());
+  }
 
   GAIAInfoUpdateServiceTest(const GAIAInfoUpdateServiceTest&) = delete;
   GAIAInfoUpdateServiceTest& operator=(const GAIAInfoUpdateServiceTest&) =
@@ -88,7 +94,7 @@
 
     service_ = std::make_unique<GAIAInfoUpdateService>(
         identity_test_env_.identity_manager(),
-        testing_profile_manager_.profile_attributes_storage(),
+        testing_profile_manager_.profile_attributes_storage(), pref_service_,
         profile()->GetPath());
   }
 
@@ -121,13 +127,22 @@
         base::UTF8ToUTF16(name), 0, TestingProfile::TestingFactories());
   }
 
+  bool HasAccountPrefs(const std::string& gaia_id) {
+    return SigninPrefs(pref_service_).HasAccountPrefs(gaia_id);
+  }
+
+  void InitializeAccountPref(const std::string& gaia_id) {
+    // 5 is just a random value to create the pref.
+    SigninPrefs(pref_service_).SetDummyValue(gaia_id, 5);
+  }
+
   content::BrowserTaskEnvironment task_environment_;
   TestingProfileManager testing_profile_manager_;
   raw_ptr<TestingProfile> profile_ = nullptr;
   signin::IdentityTestEnvironment identity_test_env_;
+  TestingPrefServiceSimple pref_service_;
   std::unique_ptr<GAIAInfoUpdateService> service_;
 };
-}  // namespace
 
 TEST_F(GAIAInfoUpdateServiceTest, SyncOnSyncOff) {
   AccountInfo info =
@@ -349,3 +364,99 @@
   EXPECT_FALSE(entry->GetGAIAPicture());
   EXPECT_TRUE(entry->GetHostedDomain().empty());
 }
+
+TEST_F(GAIAInfoUpdateServiceTest,
+       SigninPrefsWithSignedInAccountAndSecondaryAccount) {
+  const std::string primary_gaia_id = "primary_gaia_id";
+  ASSERT_FALSE(HasAccountPrefs(primary_gaia_id));
+
+  AccountInfo primary_info = identity_test_env()->MakeAccountAvailable(
+      "[email protected]",
+      {.primary_account_consent_level = signin::ConsentLevel::kSignin,
+       .set_cookie = true,
+       .gaia_id = primary_gaia_id});
+  ASSERT_EQ(primary_gaia_id, primary_info.gaia);
+  InitializeAccountPref(primary_gaia_id);
+  EXPECT_TRUE(HasAccountPrefs(primary_gaia_id));
+
+  // Add a secondary account.
+  const std::string secondary_gaia_id = "secondary_gaia_id";
+  ASSERT_FALSE(HasAccountPrefs(secondary_gaia_id));
+  AccountInfo secondary_info = identity_test_env()->MakeAccountAvailable(
+      "[email protected]",
+      {.set_cookie = true, .gaia_id = secondary_gaia_id});
+  ASSERT_EQ(secondary_gaia_id, secondary_info.gaia);
+  InitializeAccountPref(secondary_gaia_id);
+  EXPECT_TRUE(HasAccountPrefs(secondary_gaia_id));
+
+  // Set the accounts as signed out.
+  identity_test_env()->SetCookieAccounts(
+      {{primary_info.email, primary_info.gaia, /*signed_out=*/true},
+       {secondary_info.email, secondary_info.gaia, /*signed_out=*/true}});
+  // Prefs should remain as the cookies are not cleared yet.
+  EXPECT_TRUE(HasAccountPrefs(primary_gaia_id));
+  EXPECT_TRUE(HasAccountPrefs(secondary_gaia_id));
+
+  // Clear all cookies.
+  identity_test_env()->SetCookieAccounts({});
+  ASSERT_TRUE(identity_test_env()->identity_manager()->HasPrimaryAccount(
+      signin::ConsentLevel::kSignin));
+  // Primary account prefs should remain since the account is still signed in.
+  EXPECT_TRUE(HasAccountPrefs(primary_gaia_id));
+  // Secondary account prefs should be cleared.
+  EXPECT_FALSE(HasAccountPrefs(secondary_gaia_id));
+
+  // Clearing primary account should now clear the account prefs as well since
+  // the cookie is already cleared.
+  identity_test_env()->ClearPrimaryAccount();
+  EXPECT_FALSE(HasAccountPrefs(primary_gaia_id));
+}
+
+TEST_F(GAIAInfoUpdateServiceTest, SigninPrefsWithSignedInWebOnly) {
+  const std::string gaia_id = "gaia_id";
+  ASSERT_FALSE(HasAccountPrefs(gaia_id));
+  AccountInfo info = identity_test_env()->MakeAccountAvailable(
+      "[email protected]", {.set_cookie = true, .gaia_id = gaia_id});
+  ASSERT_EQ(gaia_id, info.gaia);
+  ASSERT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount(
+      signin::ConsentLevel::kSignin));
+  InitializeAccountPref(gaia_id);
+  EXPECT_TRUE(HasAccountPrefs(gaia_id));
+
+  // Web sign out keeps the prefs.
+  identity_test_env()->SetCookieAccounts(
+      {{info.email, info.gaia, /*signed_out=*/true}});
+  EXPECT_TRUE(HasAccountPrefs(gaia_id));
+
+  // Clearing the cookie removes the prefs.
+  identity_test_env()->SetCookieAccounts({});
+  EXPECT_FALSE(HasAccountPrefs(gaia_id));
+}
+
+TEST_F(GAIAInfoUpdateServiceTest, SigninPrefsWithGaiaIdNotInChrome) {
+  // Use an account in Chrome.
+  const std::string gaia_id = "gaia_id";
+  ASSERT_FALSE(HasAccountPrefs(gaia_id));
+  AccountInfo info = identity_test_env()->MakeAccountAvailable(
+      "[email protected]", {.set_cookie = true, .gaia_id = gaia_id});
+  ASSERT_EQ(gaia_id, info.gaia);
+  ASSERT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount(
+      signin::ConsentLevel::kSignin));
+  InitializeAccountPref(gaia_id);
+  ASSERT_TRUE(HasAccountPrefs(gaia_id));
+
+  // Use an account that is not in Chrome.
+  const std::string gaia_id_not_in_chrome = "gaia_id_not_in_chrome";
+  ASSERT_FALSE(HasAccountPrefs(gaia_id_not_in_chrome));
+
+  // This is possible even if the account is not in Chrome.
+  InitializeAccountPref(gaia_id_not_in_chrome);
+  EXPECT_TRUE(HasAccountPrefs(gaia_id_not_in_chrome));
+
+  // Refreshing the cookie jar should remove the account not in Chrome.
+  identity_test_env()->TriggerListAccount();
+
+  // Prefs for the Account in Chrome remains, not for the account not in Chrome.
+  EXPECT_TRUE(HasAccountPrefs(gaia_id));
+  EXPECT_FALSE(HasAccountPrefs(gaia_id_not_in_chrome));
+}
diff --git a/components/signin/public/base/BUILD.gn b/components/signin/public/base/BUILD.gn
index 6b92b4c..8ca80d63 100644
--- a/components/signin/public/base/BUILD.gn
+++ b/components/signin/public/base/BUILD.gn
@@ -49,6 +49,8 @@
     "signin_client.h",
     "signin_metrics.cc",
     "signin_metrics.h",
+    "signin_prefs.cc",
+    "signin_prefs.h",
     "wait_for_network_callback_helper.cc",
     "wait_for_network_callback_helper.h",
   ]
@@ -140,6 +142,7 @@
     "persistent_repeating_timer_unittest.cc",
     "session_binding_utils_unittest.cc",
     "signin_metrics_unittest.cc",
+    "signin_prefs_unittest.cc",
   ]
 
   if (enable_bound_session_credentials) {
diff --git a/components/signin/public/base/signin_prefs.cc b/components/signin/public/base/signin_prefs.cc
new file mode 100644
index 0000000..fcfff322
--- /dev/null
+++ b/components/signin/public/base/signin_prefs.cc
@@ -0,0 +1,77 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/signin/public/base/signin_prefs.h"
+
+#include "base/containers/contains.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+namespace {
+
+// Name of the main pref dictionary holding the account dictionaries of the
+// underlying prefs, with the key as the GaiaIds. The prefs stored through this
+// dict here are information not directly related to the account itself, only
+// metadata.
+constexpr char kSigninAccountPrefs[] = "signin.accounts_metadata_dict";
+
+// Dummy value pref; will later be replaced with real values.
+constexpr char kSigninAccountDummyValuePref[] = "dummy_value";
+
+}  // namespace
+
+SigninPrefs::SigninPrefs(PrefService& pref_service)
+    : pref_service_(pref_service) {}
+
+void SigninPrefs::RegisterProfilePrefs(PrefRegistrySimple* registry) {
+  registry->RegisterDictionaryPref(kSigninAccountPrefs);
+}
+
+bool SigninPrefs::HasAccountPrefs(GaiaId gaia_id) const {
+  return pref_service_->GetDict(kSigninAccountPrefs).contains(gaia_id);
+}
+
+void SigninPrefs::RemoveAllAccountPrefsExcept(
+    const base::flat_set<GaiaId>& gaia_ids_to_keep) {
+  // Get the list of all accounts that should be removed, not in
+  // `gaia_ids_to_keep`. Use `std::string` instead of `GaiaId`  because a
+  // reference might loose it's value on removal of items in the next step.
+  std::vector<std::string> accounts_prefs_to_remove;
+  for (const std::pair<const std::string&, const base::Value&> account_prefs :
+       pref_service_->GetDict(kSigninAccountPrefs)) {
+    if (!base::Contains(gaia_ids_to_keep, account_prefs.first)) {
+      accounts_prefs_to_remove.push_back(account_prefs.first);
+    }
+  }
+
+  // Remove the account prefs that should not be kept.
+  ScopedDictPrefUpdate scoped_update(&pref_service_.get(), kSigninAccountPrefs);
+  for (GaiaId account_prefs_to_remove : accounts_prefs_to_remove) {
+    scoped_update->Remove(account_prefs_to_remove);
+  }
+}
+
+void SigninPrefs::SetDummyValue(GaiaId gaia_id, int dummy_value) {
+  ScopedDictPrefUpdate scoped_update(&pref_service_.get(), kSigninAccountPrefs);
+
+  // `EnsureDict` gets or create the dictionary.
+  base::Value::Dict* account_dict = scoped_update->EnsureDict(gaia_id);
+  // `Set` will add an entry if it doesn't already exists, or if it does, it
+  // will overwrite it.
+  account_dict->Set(kSigninAccountDummyValuePref, dummy_value);
+}
+
+int SigninPrefs::GetDummyValue(GaiaId gaia_id) const {
+  const base::Value::Dict* account_dict =
+      pref_service_->GetDict(kSigninAccountPrefs).FindDict(gaia_id);
+  // If the account dict does not exist yet; return the default value.
+  if (!account_dict) {
+    return 0;
+  }
+
+  // Return the pref value if it exists, otherwise return the default value.
+  return account_dict->FindInt(kSigninAccountDummyValuePref).value_or(0);
+}
diff --git a/components/signin/public/base/signin_prefs.h b/components/signin/public/base/signin_prefs.h
new file mode 100644
index 0000000..6b765c1
--- /dev/null
+++ b/components/signin/public/base/signin_prefs.h
@@ -0,0 +1,64 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SIGNIN_PUBLIC_BASE_SIGNIN_PREFS_H_
+#define COMPONENTS_SIGNIN_PUBLIC_BASE_SIGNIN_PREFS_H_
+
+#include <string_view>
+
+#include "base/containers/flat_set.h"
+#include "base/memory/raw_ref.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+// Wrapper around `PrefService` to access/update account signin prefs.
+// The prefs used here are Chrome specific prefs that are tied to the accounts
+// (per account per profile), they do not contain information about the
+// accounts, see also `AccountInfo`.
+// Creating the pref entries is lazy. It will be first be created when writing a
+// value. Reading from an non-existing pref will return the default value of
+// that pref.
+// Allows managing the prefs lifecycle per account, destroying them when needed.
+// This is expected to be done when the account cookies are cleared, except for
+// the primary account.
+// Account information are stored in a dictionary for which the key is the
+// `GaiaId` of the account. The account's `GaiaId` used are expected to be
+// accounts known in Chrome (Signed in/out accounts or primary account).
+class SigninPrefs {
+ public:
+  // Used as a key to dictionary pref used for accounts.
+  using GaiaId = std::string_view;
+
+  explicit SigninPrefs(PrefService& pref_service);
+
+  // Pref access:
+  // Writing a value will create the account dictionary and the pref value if
+  // any of those do not exist yet. It is expected be to used with a valid
+  // `gaia_id` for an account that is in Chrome.
+  // Reading a value from a pref dictionary or a data pref that do not exist yet
+  // will return the default value of that pref.
+
+  // Dummy value to show the interface/implementation. To be removed when adding
+  // a real value.
+  void SetDummyValue(GaiaId gaia_id, int dummy_value);
+  int GetDummyValue(GaiaId gaia_id) const;
+
+  // Checks if the an account pref with the given `gaia_id` exists.
+  bool HasAccountPrefs(GaiaId gaia_id) const;
+
+  // Keeps all prefs with the gaia ids given in `gaia_ids_to_keep`.
+  // This is done this way since we usually are not aware of the accounts that
+  // are not there anymore, so we remove all accounts that should not be kept
+  // instead of removing a specific account.
+  void RemoveAllAccountPrefsExcept(
+      const base::flat_set<GaiaId>& gaia_ids_to_keep);
+
+  static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+
+ private:
+  const raw_ref<PrefService> pref_service_;
+};
+
+#endif  // COMPONENTS_SIGNIN_PUBLIC_BASE_SIGNIN_PREFS_H_
diff --git a/components/signin/public/base/signin_prefs_unittest.cc b/components/signin/public/base/signin_prefs_unittest.cc
new file mode 100644
index 0000000..d02d53a
--- /dev/null
+++ b/components/signin/public/base/signin_prefs_unittest.cc
@@ -0,0 +1,87 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/signin/public/base/signin_prefs.h"
+
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class SigninPrefsTest : public ::testing::Test {
+ public:
+  SigninPrefsTest() : signin_prefs_(pref_service_) {
+    SigninPrefs::RegisterProfilePrefs(pref_service_.registry());
+  }
+
+  SigninPrefs& signin_prefs() { return signin_prefs_; }
+
+  bool HasAccountPrefs(const std::string& gaia_id) const {
+    return signin_prefs_.HasAccountPrefs(gaia_id);
+  }
+
+ private:
+  TestingPrefServiceSimple pref_service_;
+  SigninPrefs signin_prefs_;
+};
+
+TEST_F(SigninPrefsTest, AccountPrefsInitialization) {
+  const std::string gaia_id = "gaia_id";
+  ASSERT_FALSE(HasAccountPrefs(gaia_id));
+
+  // Reading a value from a pref dict that do not exist yet should return a
+  // default value and not create the pref dict entry.
+  EXPECT_EQ(signin_prefs().GetDummyValue(gaia_id), 0);
+  EXPECT_FALSE(HasAccountPrefs(gaia_id));
+
+  int dummy_value = 10;
+  signin_prefs().SetDummyValue(gaia_id, dummy_value);
+  EXPECT_TRUE(HasAccountPrefs(gaia_id));
+  EXPECT_EQ(signin_prefs().GetDummyValue(gaia_id), dummy_value);
+}
+
+TEST_F(SigninPrefsTest, RemovingAccountPrefs) {
+  const std::string gaia_id1 = "gaia_id1";
+  const std::string gaia_id2 = "gaia_id2";
+  const std::string gaia_id3 = "gaia_id3";
+
+  // Setting values should create the dict entry for the given gaia id.
+  signin_prefs().SetDummyValue(gaia_id1, 1);
+  signin_prefs().SetDummyValue(gaia_id2, 2);
+  signin_prefs().SetDummyValue(gaia_id3, 3);
+  ASSERT_TRUE(HasAccountPrefs(gaia_id1));
+  ASSERT_TRUE(HasAccountPrefs(gaia_id2));
+  ASSERT_TRUE(HasAccountPrefs(gaia_id3));
+
+  // Should remove `gaia_id3`.
+  signin_prefs().RemoveAllAccountPrefsExcept({gaia_id1, gaia_id2});
+  EXPECT_TRUE(HasAccountPrefs(gaia_id1));
+  EXPECT_TRUE(HasAccountPrefs(gaia_id2));
+  EXPECT_FALSE(HasAccountPrefs(gaia_id3));
+
+  // Should remove `gaia_id2`. Adding a non existing pref should have no effect
+  // (`gaia_id3`).
+  signin_prefs().RemoveAllAccountPrefsExcept({gaia_id1, gaia_id3});
+  EXPECT_TRUE(HasAccountPrefs(gaia_id1));
+  EXPECT_FALSE(HasAccountPrefs(gaia_id2));
+  EXPECT_FALSE(HasAccountPrefs(gaia_id3));
+}
+
+TEST_F(SigninPrefsTest, RemovingAllAccountPrefs) {
+  const std::string gaia_id1 = "gaia_id1";
+  const std::string gaia_id2 = "gaia_id2";
+  const std::string gaia_id3 = "gaia_id3";
+
+  // Setting values should create the dict entry for the given gaia id.
+  signin_prefs().SetDummyValue(gaia_id1, 1);
+  signin_prefs().SetDummyValue(gaia_id2, 2);
+  signin_prefs().SetDummyValue(gaia_id3, 3);
+  ASSERT_TRUE(HasAccountPrefs(gaia_id1));
+  ASSERT_TRUE(HasAccountPrefs(gaia_id2));
+  ASSERT_TRUE(HasAccountPrefs(gaia_id3));
+
+  // Passing no accounts in the arguments should clear all prefs.
+  signin_prefs().RemoveAllAccountPrefsExcept({});
+  EXPECT_FALSE(HasAccountPrefs(gaia_id1));
+  EXPECT_FALSE(HasAccountPrefs(gaia_id2));
+  EXPECT_FALSE(HasAccountPrefs(gaia_id3));
+}
diff --git a/components/signin/public/identity_manager/identity_test_environment.cc b/components/signin/public/identity_manager/identity_test_environment.cc
index 3db4d8e..900d060 100644
--- a/components/signin/public/identity_manager/identity_test_environment.cc
+++ b/components/signin/public/identity_manager/identity_test_environment.cc
@@ -30,6 +30,7 @@
 #include "components/signin/internal/identity_manager/primary_account_mutator_impl.h"
 #include "components/signin/public/base/consent_level.h"
 #include "components/signin/public/base/test_signin_client.h"
+#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
 #include "components/signin/public/identity_manager/accounts_mutator.h"
 #include "components/signin/public/identity_manager/device_accounts_synchronizer.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
@@ -513,6 +514,24 @@
                             cookie_accounts);
 }
 
+void IdentityTestEnvironment::TriggerListAccount() {
+  const AccountsInCookieJarInfo& cookie_jar =
+      identity_manager()->GetAccountsInCookieJar();
+  // Construct the cookie params with the actual cookies in the cookie jar.
+  std::vector<CookieParamsForTest> cookie_params;
+  for (auto& account : cookie_jar.signed_in_accounts) {
+    cookie_params.emplace_back(account.email, account.gaia_id,
+                               /*signed_out=*/false);
+  }
+  for (auto& account : cookie_jar.signed_out_accounts) {
+    cookie_params.emplace_back(account.email, account.gaia_id,
+                               /*signed_out=*/true);
+  }
+
+  // Trigger the /ListAccount with the current cookie information.
+  SetCookieAccounts(cookie_params);
+}
+
 void IdentityTestEnvironment::SetAutomaticIssueOfAccessTokens(bool grant) {
   fake_token_service()->set_auto_post_fetch_response_on_message_loop(grant);
 }
diff --git a/components/signin/public/identity_manager/identity_test_environment.h b/components/signin/public/identity_manager/identity_test_environment.h
index c9f651c..1a3cd47b 100644
--- a/components/signin/public/identity_manager/identity_test_environment.h
+++ b/components/signin/public/identity_manager/identity_test_environment.h
@@ -238,6 +238,10 @@
   void SetCookieAccounts(
       const std::vector<CookieParamsForTest>& cookie_accounts);
 
+  // Triggers a fake /ListAccount call with the current accounts in the cookie
+  // jar. It will notify all observers.
+  void TriggerListAccount();
+
   // When this is set, access token requests will be automatically granted with
   // an access token value of "access_token".
   void SetAutomaticIssueOfAccessTokens(bool grant);
diff --git a/components/signin/public/identity_manager/identity_test_environment_unittest.cc b/components/signin/public/identity_manager/identity_test_environment_unittest.cc
index cec26a1..5afc08c 100644
--- a/components/signin/public/identity_manager/identity_test_environment_unittest.cc
+++ b/components/signin/public/identity_manager/identity_test_environment_unittest.cc
@@ -6,8 +6,12 @@
 
 #include "base/functional/bind.h"
 #include "base/test/task_environment.h"
+#include "components/signin/public/base/consent_level.h"
 #include "components/signin/public/identity_manager/access_token_info.h"
+#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
+#include "components/signin/public/identity_manager/test_identity_manager_observer.h"
 #include "google_apis/gaia/gaia_constants.h"
+#include "services/network/test/test_url_loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace signin {
@@ -130,4 +134,52 @@
   EXPECT_FALSE(identity_manager->HasPrimaryAccount(ConsentLevel::kSync));
 }
 
+TEST_F(IdentityTestEnvironmentTest, TriggerListAccount) {
+  std::unique_ptr<IdentityTestEnvironment> identity_test_environment =
+      std::make_unique<IdentityTestEnvironment>();
+
+  const std::string kPrimaryEmail = "[email protected]";
+  identity_test_environment->MakeAccountAvailable(
+      kPrimaryEmail, {.primary_account_consent_level = ConsentLevel::kSignin,
+                      .set_cookie = true});
+  ASSERT_EQ(identity_test_environment->identity_manager()
+                ->GetAccountsInCookieJar()
+                .signed_in_accounts.size(),
+            1u);
+
+  {
+    TestIdentityManagerObserver observer(
+        identity_test_environment->identity_manager());
+
+    // Changes should be reflected when triggering list account.
+    identity_test_environment->TriggerListAccount();
+
+    const AccountsInCookieJarInfo& observed_cookie_jar =
+        observer.AccountsInfoFromAccountsInCookieUpdatedCallback();
+    auto signed_in_accounts = observed_cookie_jar.signed_in_accounts;
+    ASSERT_EQ(signed_in_accounts.size(), 1u);
+    EXPECT_EQ(signed_in_accounts[0].email, kPrimaryEmail);
+  }
+
+  // Add a secondary account
+  const std::string secondary_email = "[email protected]";
+  identity_test_environment->MakeAccountAvailable(secondary_email,
+                                                  {.set_cookie = true});
+
+  {
+    TestIdentityManagerObserver observer(
+        identity_test_environment->identity_manager());
+
+    // Changes should be reflected when triggering list account.
+    identity_test_environment->TriggerListAccount();
+
+    const AccountsInCookieJarInfo& observed_cookie_jar =
+        observer.AccountsInfoFromAccountsInCookieUpdatedCallback();
+    auto signed_in_accounts = observed_cookie_jar.signed_in_accounts;
+    ASSERT_EQ(signed_in_accounts.size(), 2u);
+    EXPECT_EQ(signed_in_accounts[0].email, kPrimaryEmail);
+    EXPECT_EQ(signed_in_accounts[1].email, secondary_email);
+  }
+}
+
 }  // namespace signin