| // 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 "components/browsing_data/content/browsing_data_model.h" | 
 |  | 
 | #include <variant> | 
 |  | 
 | #include "base/barrier_closure.h" | 
 | #include "base/feature_list.h" | 
 | #include "base/memory/weak_ptr.h" | 
 | #include "base/run_loop.h" | 
 | #include "base/test/bind.h" | 
 | #include "base/test/scoped_feature_list.h" | 
 | #include "components/browsing_data/content/browsing_data_model_test_util.h" | 
 | #include "components/browsing_data/content/test_browsing_data_model_delegate.h" | 
 | #include "content/public/browser/browser_context.h" | 
 | #include "content/public/test/browser_task_environment.h" | 
 | #include "content/public/test/test_browser_context.h" | 
 | #include "content/public/test/test_storage_partition.h" | 
 | #include "mojo/public/cpp/bindings/receiver.h" | 
 | #include "net/base/schemeful_site.h" | 
 | #include "net/cookies/canonical_cookie.h" | 
 | #include "net/extras/shared_dictionary/shared_dictionary_usage_info.h" | 
 | #include "net/shared_dictionary/shared_dictionary_isolation_key.h" | 
 | #include "services/network/public/cpp/features.h" | 
 | #include "services/network/public/mojom/trust_tokens.mojom.h" | 
 | #include "services/network/test/test_network_context.h" | 
 | #include "testing/gmock/include/gmock/gmock.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 | #include "third_party/blink/public/common/features.h" | 
 | #include "url/origin.h" | 
 |  | 
 | using browsing_data_model_test_util::BrowsingDataEntry; | 
 | using browsing_data_model_test_util::ValidateBrowsingDataEntries; | 
 |  | 
 | namespace { | 
 |  | 
 | class MockNetworkContext : public network::TestNetworkContext { | 
 |  public: | 
 |   explicit MockNetworkContext( | 
 |       mojo::PendingReceiver<network::mojom::NetworkContext> receiver) | 
 |       : receiver_(this, std::move(receiver)) {} | 
 |   MOCK_METHOD(void, | 
 |               GetStoredTrustTokenCounts, | 
 |               (GetStoredTrustTokenCountsCallback), | 
 |               (override)); | 
 |   MOCK_METHOD(void, | 
 |               DeleteStoredTrustTokens, | 
 |               (const url::Origin&, DeleteStoredTrustTokensCallback), | 
 |               (override)); | 
 |   MOCK_METHOD(void, | 
 |               GetSharedDictionaryUsageInfo, | 
 |               (GetSharedDictionaryUsageInfoCallback), | 
 |               (override)); | 
 |   MOCK_METHOD(void, | 
 |               ClearSharedDictionaryCacheForIsolationKey, | 
 |               (const net::SharedDictionaryIsolationKey&, | 
 |                ClearSharedDictionaryCacheForIsolationKeyCallback), | 
 |               (override)); | 
 |  | 
 |  private: | 
 |   mojo::Receiver<network::mojom::NetworkContext> receiver_; | 
 | }; | 
 |  | 
 | std::unique_ptr<net::CanonicalCookie> MakeCanonicalCookie( | 
 |     const std::string& name, | 
 |     const std::string& domain, | 
 |     std::optional<net::CookiePartitionKey> cookie_partition_key = | 
 |         std::nullopt) { | 
 |   return net::CanonicalCookie::CreateUnsafeCookieForTesting( | 
 |       name, "1", domain, /*path=*/"/", /*creation=*/base::Time(), | 
 |       /*expiration=*/base::Time(), /*last_access=*/base::Time(), | 
 |       /*last_update=*/base::Time(), | 
 |       /*secure=*/true, /*httponly=*/false, net::CookieSameSite::UNSPECIFIED, | 
 |       net::CookiePriority::COOKIE_PRIORITY_DEFAULT, cookie_partition_key); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | class BrowsingDataModelTest : public testing::Test { | 
 |  public: | 
 |   BrowsingDataModelTest() { | 
 |     mojo::PendingRemote<network::mojom::NetworkContext> network_context_remote; | 
 |     mock_network_context_ = std::make_unique<MockNetworkContext>( | 
 |         network_context_remote.InitWithNewPipeAndPassReceiver()); | 
 |     storage_partition()->SetNetworkContextForTesting( | 
 |         std::move(network_context_remote)); | 
 |     model_ = BrowsingDataModel::BuildEmpty( | 
 |         storage_partition(), | 
 |         std::make_unique<browsing_data::TestBrowsingDataModelDelegate>()); | 
 |   } | 
 |   ~BrowsingDataModelTest() override = default; | 
 |  | 
 |   void TearDown() override { mock_network_context_.reset(); } | 
 |  | 
 |  protected: | 
 |   void BuildModel(base::OnceClosure completed) { | 
 |     model()->PopulateFromDisk(std::move(completed)); | 
 |   } | 
 |  | 
 |   void DeleteModel() { model_.reset(); } | 
 |  | 
 |   content::BrowserContext* browser_context() { return &browser_context_; } | 
 |   BrowsingDataModel* model() { return model_.get(); } | 
 |   content::StoragePartition* storage_partition() { | 
 |     return browser_context()->GetDefaultStoragePartition(); | 
 |   } | 
 |   MockNetworkContext* mock_network_context() { | 
 |     return mock_network_context_.get(); | 
 |   } | 
 |   BrowsingDataModel::BrowsingDataEntries& browsing_data_entries() { | 
 |     return model_->browsing_data_entries_; | 
 |   } | 
 |  | 
 |   const url::Origin kSubdomainOrigin = | 
 |       url::Origin::Create(GURL("https://subsite.example.com")); | 
 |   const std::string kSubdomainOriginHost = "subsite.example.com"; | 
 |   const std::string kSubdomainOriginSite = "example.com"; | 
 |  | 
 |   const url::Origin kSiteOrigin = | 
 |       url::Origin::Create(GURL("https://example.com")); | 
 |   const std::string kSiteOriginHost = "example.com"; | 
 |  | 
 |   const url::Origin kAnotherSiteOrigin = | 
 |       url::Origin::Create(GURL("https://another-example.com")); | 
 |   const std::string kAnotherSiteOriginHost = "another-example.com"; | 
 |  | 
 |   const url::Origin kTestOrigin = url::Origin::Create(GURL("https://a.test")); | 
 |   const std::string kTestOriginHost = "a.test"; | 
 |  | 
 |   const url::Origin kAnotherTestOrigin = | 
 |       url::Origin::Create(GURL("https://b.test")); | 
 |   const std::string kAnotherTestOriginHost = "b.test"; | 
 |  | 
 |  private: | 
 |   content::BrowserTaskEnvironment task_environment_; | 
 |   content::TestBrowserContext browser_context_; | 
 |   std::unique_ptr<BrowsingDataModel> model_; | 
 |   content::TestStoragePartition storage_partition_; | 
 |   std::unique_ptr<MockNetworkContext> mock_network_context_; | 
 | }; | 
 |  | 
 | TEST_F(BrowsingDataModelTest, PrimaryHostMapping) { | 
 |   model()->AddBrowsingData(kSubdomainOrigin, | 
 |                            BrowsingDataModel::StorageType::kTrustTokens, 0, 1); | 
 |   model()->AddBrowsingData(blink::StorageKey::CreateFirstParty(kTestOrigin), | 
 |                            BrowsingDataModel::StorageType::kQuotaStorage, 123, | 
 |                            0); | 
 |  | 
 |   ValidateBrowsingDataEntries( | 
 |       model(), { | 
 |                    {kSubdomainOriginHost, | 
 |                     kSubdomainOrigin, | 
 |                     {{BrowsingDataModel::StorageType::kTrustTokens}, 0, 1}}, | 
 |                    {kTestOriginHost, | 
 |                     blink::StorageKey::CreateFirstParty(kTestOrigin), | 
 |                     {{BrowsingDataModel::StorageType::kQuotaStorage}, 123, 0}}, | 
 |                }); | 
 | } | 
 |  | 
 | TEST_F(BrowsingDataModelTest, EntryCoalescense) { | 
 |   // Check that multiple entries are correctly coalesced. | 
 |   // Browsing data with the same owner + data_key pair should update the | 
 |   // same entry's details. | 
 |   model()->AddBrowsingData(blink::StorageKey::CreateFirstParty(kSiteOrigin), | 
 |                            BrowsingDataModel::StorageType::kQuotaStorage, 123, | 
 |                            0); | 
 |   model()->AddBrowsingData(blink::StorageKey::CreateFirstParty(kSiteOrigin), | 
 |                            BrowsingDataModel::StorageType::kLocalStorage, 234, | 
 |                            5); | 
 |  | 
 |   auto expected_entries = std::vector<BrowsingDataEntry>( | 
 |       {{kSiteOriginHost, | 
 |         blink::StorageKey::CreateFirstParty(kSiteOrigin), | 
 |         {{BrowsingDataModel::StorageType::kQuotaStorage, | 
 |           BrowsingDataModel::StorageType::kLocalStorage}, | 
 |          123 + 234, | 
 |          5}}}); | 
 |  | 
 |   ValidateBrowsingDataEntries(model(), expected_entries); | 
 |  | 
 |   // Entries related to the same owner, but different data_keys, should | 
 |   // create a new entry. | 
 |   model()->AddBrowsingData( | 
 |       blink::StorageKey::CreateFirstParty(kAnotherSiteOrigin), | 
 |       BrowsingDataModel::StorageType::kQuotaStorage, 345, 0); | 
 |   model()->AddBrowsingData( | 
 |       kAnotherSiteOrigin, BrowsingDataModel::StorageType::kTrustTokens, 456, 6); | 
 |  | 
 |   expected_entries.push_back( | 
 |       {kAnotherSiteOriginHost, | 
 |        blink::StorageKey::CreateFirstParty(kAnotherSiteOrigin), | 
 |        {{BrowsingDataModel::StorageType::kQuotaStorage}, 345}}); | 
 |   expected_entries.push_back( | 
 |       {kAnotherSiteOriginHost, | 
 |        kAnotherSiteOrigin, | 
 |        {{BrowsingDataModel::StorageType::kTrustTokens}, 456, 6}}); | 
 |  | 
 |   browsing_data_model_test_util::ValidateBrowsingDataEntries(model(), | 
 |                                                              expected_entries); | 
 | } | 
 |  | 
 | TEST_F(BrowsingDataModelTest, ConcurrentDeletions) { | 
 |   // Check that the model is able to support multiple deletion operations in | 
 |   // flight at the same time, even if the backends finish out-of-order. | 
 |   std::vector<::network::mojom::StoredTrustTokensForIssuerPtr> tokens; | 
 |   tokens.emplace_back(std::in_place, kSubdomainOrigin, 10); | 
 |   tokens.emplace_back(std::in_place, kAnotherSiteOrigin, 20); | 
 |  | 
 |   EXPECT_CALL(*mock_network_context(), GetStoredTrustTokenCounts(testing::_)) | 
 |       .WillOnce( | 
 |           [&](network::TestNetworkContext::GetStoredTrustTokenCountsCallback | 
 |                   callback) { std::move(callback).Run(std::move(tokens)); }); | 
 |  | 
 |   if (base::FeatureList::IsEnabled( | 
 |           network::features::kCompressionDictionaryTransportBackend)) { | 
 |     EXPECT_CALL(*mock_network_context(), | 
 |                 GetSharedDictionaryUsageInfo(testing::_)) | 
 |         .WillOnce([&](network::TestNetworkContext:: | 
 |                           GetSharedDictionaryUsageInfoCallback callback) { | 
 |           std::move(callback).Run({}); | 
 |         }); | 
 |   } | 
 |  | 
 |   base::RunLoop run_loop; | 
 |   BuildModel(run_loop.QuitWhenIdleClosure()); | 
 |   run_loop.Run(); | 
 |  | 
 |   // The size of trust token storage is aliased to a small amount of data, 100B. | 
 |   auto expected_entries = std::vector<BrowsingDataEntry>{ | 
 |       {kSubdomainOriginHost, | 
 |        kSubdomainOrigin, | 
 |        {{BrowsingDataModel::StorageType::kTrustTokens}, 100, 0}}, | 
 |       {kAnotherSiteOriginHost, | 
 |        kAnotherSiteOrigin, | 
 |        {{BrowsingDataModel::StorageType::kTrustTokens}, 100, 0}}, | 
 |       {kTestOriginHost, | 
 |        kTestOrigin, | 
 |        {{static_cast<BrowsingDataModel::StorageType>( | 
 |             browsing_data::TestBrowsingDataModelDelegate::StorageType:: | 
 |                 kTestDelegateType)}, | 
 |         0, | 
 |         0}}}; | 
 |  | 
 |   browsing_data_model_test_util::ValidateBrowsingDataEntries(model(), | 
 |                                                              expected_entries); | 
 |  | 
 |   // Save the deletion callbacks provided to the mock network context, so we | 
 |   // can later fulfil them out-of-order. The mock lives on the other side of a | 
 |   // mojo interface, so the callbacks can only be populated async. | 
 |   base::RunLoop delete_run_loop; | 
 |   base::RepeatingClosure delete_barrier = base::BarrierClosure( | 
 |       2, base::BindLambdaForTesting([&]() { delete_run_loop.QuitWhenIdle(); })); | 
 |   network::TestNetworkContext::DeleteStoredTrustTokensCallback | 
 |       delete_tokens_complete_1; | 
 |   network::TestNetworkContext::DeleteStoredTrustTokensCallback | 
 |       delete_tokens_complete_2; | 
 |  | 
 |   { | 
 |     testing::InSequence sequence; | 
 |     EXPECT_CALL(*mock_network_context(), | 
 |                 DeleteStoredTrustTokens(kSubdomainOrigin, testing::_)) | 
 |         .WillOnce( | 
 |             [&](const url::Origin& origin, | 
 |                 network::TestNetworkContext::DeleteStoredTrustTokensCallback | 
 |                     callback) { | 
 |               delete_tokens_complete_1 = std::move(callback); | 
 |               delete_barrier.Run(); | 
 |             }); | 
 |     EXPECT_CALL(*mock_network_context(), | 
 |                 DeleteStoredTrustTokens(kAnotherSiteOrigin, testing::_)) | 
 |         .WillOnce( | 
 |             [&](const url::Origin& origin, | 
 |                 network::TestNetworkContext::DeleteStoredTrustTokensCallback | 
 |                     callback) { | 
 |               delete_tokens_complete_2 = std::move(callback); | 
 |               delete_barrier.Run(); | 
 |             }); | 
 |   } | 
 |  | 
 |   // As running the saved callbacks is also async, we need more run loops to | 
 |   // confirm that the model receives the deletion completed callback. | 
 |   base::RunLoop delete_callback_1_runloop; | 
 |   base::RunLoop delete_callback_2_runloop; | 
 |  | 
 |   model()->RemoveBrowsingData(kSubdomainOriginHost, | 
 |                               delete_callback_1_runloop.QuitWhenIdleClosure()); | 
 |  | 
 |   // Removal from the model should be synchronous. | 
 |   expected_entries.erase(expected_entries.begin()); | 
 |   browsing_data_model_test_util::ValidateBrowsingDataEntries(model(), | 
 |                                                              expected_entries); | 
 |  | 
 |   model()->RemoveBrowsingData(kAnotherSiteOriginHost, | 
 |                               delete_callback_2_runloop.QuitWhenIdleClosure()); | 
 |  | 
 |   expected_entries.erase(expected_entries.begin()); | 
 |   browsing_data_model_test_util::ValidateBrowsingDataEntries(model(), | 
 |                                                              expected_entries); | 
 |   delete_run_loop.Run(); | 
 |  | 
 |   // We're now holding two unfulfilled backend deletion callbacks. Delete the | 
 |   // model (for extra test coverage) and then fire them in the opposite order | 
 |   // they were called. The callbacks provided with RemoveBrowsingData should | 
 |   // still be fired in matching order. | 
 |   DeleteModel(); | 
 |  | 
 |   std::move(delete_tokens_complete_2) | 
 |       .Run( | 
 |           network::mojom::DeleteStoredTrustTokensStatus::kSuccessTokensDeleted); | 
 |   delete_callback_2_runloop.Run(); | 
 |   EXPECT_FALSE(delete_callback_1_runloop.AnyQuitCalled()); | 
 |   EXPECT_TRUE(delete_callback_2_runloop.AnyQuitCalled()); | 
 |  | 
 |   std::move(delete_tokens_complete_1) | 
 |       .Run( | 
 |           network::mojom::DeleteStoredTrustTokensStatus::kSuccessTokensDeleted); | 
 |   delete_callback_1_runloop.Run(); | 
 |  | 
 |   // The test not timing out is sufficient coverage, but this is easier to grok. | 
 |   EXPECT_TRUE(delete_callback_1_runloop.AnyQuitCalled()); | 
 |   EXPECT_TRUE(delete_callback_2_runloop.AnyQuitCalled()); | 
 | } | 
 |  | 
 | TEST_F(BrowsingDataModelTest, DelegateDataDeleted) { | 
 |   // Needed to when building model from disk, returning an empty list as it's | 
 |   // not needed for this test. | 
 |   EXPECT_CALL(*mock_network_context(), GetStoredTrustTokenCounts(testing::_)) | 
 |       .WillOnce( | 
 |           [&](network::TestNetworkContext::GetStoredTrustTokenCountsCallback | 
 |                   callback) { std::move(callback).Run({}); }); | 
 |  | 
 |   if (base::FeatureList::IsEnabled( | 
 |           network::features::kCompressionDictionaryTransportBackend)) { | 
 |     EXPECT_CALL(*mock_network_context(), | 
 |                 GetSharedDictionaryUsageInfo(testing::_)) | 
 |         .WillOnce([&](network::TestNetworkContext:: | 
 |                           GetSharedDictionaryUsageInfoCallback callback) { | 
 |           std::move(callback).Run({}); | 
 |         }); | 
 |   } | 
 |  | 
 |   base::RunLoop run_loop; | 
 |   BuildModel(run_loop.QuitWhenIdleClosure()); | 
 |   run_loop.Run(); | 
 |  | 
 |   auto expected_entries = std::vector<BrowsingDataEntry>{ | 
 |       {kTestOriginHost, | 
 |        kTestOrigin, | 
 |        {{static_cast<BrowsingDataModel::StorageType>( | 
 |             browsing_data::TestBrowsingDataModelDelegate::StorageType:: | 
 |                 kTestDelegateType)}, | 
 |         0, | 
 |         0}}}; | 
 |  | 
 |   browsing_data_model_test_util::ValidateBrowsingDataEntries(model(), | 
 |                                                              expected_entries); | 
 |   expected_entries.erase(expected_entries.begin()); | 
 |   EXPECT_TRUE(expected_entries.empty()); | 
 |   model()->RemoveBrowsingData(kTestOriginHost, base::DoNothing()); | 
 |  | 
 |   // Model should be empty after deleting delegated data. | 
 |   browsing_data_model_test_util::ValidateBrowsingDataEntries(model(), | 
 |                                                              expected_entries); | 
 | } | 
 |  | 
 | // A BrowsingDataModel::Delegate that marks all Origin-keyed data belonging | 
 | // to a given host as being owned by its origin rather than its host. | 
 | class OriginOwnershipDelegate final : public BrowsingDataModel::Delegate { | 
 |  public: | 
 |   explicit OriginOwnershipDelegate(const std::string& origin_owned_host) | 
 |       : origin_owned_host_(origin_owned_host) {} | 
 |  | 
 |   // BrowsingDataModel::Delegate: | 
 |   void GetAllDataKeys( | 
 |       base::OnceCallback<void(std::vector<DelegateEntry>)> callback) override { | 
 |     std::move(callback).Run({}); | 
 |   } | 
 |  | 
 |   void RemoveDataKey(const BrowsingDataModel::DataKey& data_key, | 
 |                      BrowsingDataModel::StorageTypeSet storage_types, | 
 |                      base::OnceClosure callback) override { | 
 |     std::move(callback).Run(); | 
 |   } | 
 |  | 
 |   std::optional<BrowsingDataModel::DataOwner> GetDataOwner( | 
 |       const BrowsingDataModel::DataKey& data_key, | 
 |       BrowsingDataModel::StorageType storage_type) const override { | 
 |     const url::Origin* origin = std::get_if<url::Origin>(&data_key); | 
 |     if (origin && origin->host() == origin_owned_host_) { | 
 |       return *origin; | 
 |     } | 
 |     return std::nullopt; | 
 |   } | 
 |  | 
 |   std::optional<bool> IsStorageTypeCookieLike( | 
 |       BrowsingDataModel::StorageType storage_type) const override { | 
 |     return false; | 
 |   } | 
 |  | 
 |   std::optional<bool> IsBlockedByThirdPartyCookieBlocking( | 
 |       const BrowsingDataModel::DataKey& data_key, | 
 |       BrowsingDataModel::StorageType storage_type) const override { | 
 |     return IsStorageTypeCookieLike(storage_type); | 
 |   } | 
 |  | 
 |   bool IsCookieDeletionDisabled(const GURL& url) override { return false; } | 
 |  | 
 |   base::WeakPtr<Delegate> AsWeakPtr() override { | 
 |     return weak_ptr_factory_.GetWeakPtr(); | 
 |   } | 
 |  | 
 |  private: | 
 |   std::string origin_owned_host_; | 
 |   base::WeakPtrFactory<OriginOwnershipDelegate> weak_ptr_factory_{this}; | 
 | }; | 
 |  | 
 | TEST_F(BrowsingDataModelTest, DelegateDataCanBeOriginOwned) { | 
 |   std::string origin_owned_host = "origin.owned.com"; | 
 |   std::unique_ptr<BrowsingDataModel> model = BrowsingDataModel::BuildEmpty( | 
 |       storage_partition(), | 
 |       std::make_unique<OriginOwnershipDelegate>(origin_owned_host)); | 
 |  | 
 |   auto httpOriginOwned = url::Origin::Create(GURL("http://origin.owned.com")); | 
 |   model->AddBrowsingData(httpOriginOwned, | 
 |                          BrowsingDataModel::StorageType::kTrustTokens, 100); | 
 |   auto httpsOriginOwned = url::Origin::Create(GURL("https://origin.owned.com")); | 
 |   model->AddBrowsingData(httpsOriginOwned, | 
 |                          BrowsingDataModel::StorageType::kTrustTokens, 100); | 
 |   auto httpHostOwned = url::Origin::Create(GURL("http://host.owned.com")); | 
 |   model->AddBrowsingData(httpHostOwned, | 
 |                          BrowsingDataModel::StorageType::kTrustTokens, 100); | 
 |   auto httpsHostOwned = url::Origin::Create(GURL("https://host.owned.com")); | 
 |   model->AddBrowsingData(httpsHostOwned, | 
 |                          BrowsingDataModel::StorageType::kTrustTokens, 100); | 
 |  | 
 |   auto expected_entries = std::vector<BrowsingDataEntry>{ | 
 |       {httpOriginOwned, | 
 |        httpOriginOwned, | 
 |        {{BrowsingDataModel::StorageType::kTrustTokens}, 100, 0}}, | 
 |       {httpsOriginOwned, | 
 |        httpsOriginOwned, | 
 |        {{BrowsingDataModel::StorageType::kTrustTokens}, 100, 0}}, | 
 |       {"host.owned.com", | 
 |        httpHostOwned, | 
 |        {{BrowsingDataModel::StorageType::kTrustTokens}, 100, 0}}, | 
 |       {"host.owned.com", | 
 |        httpsHostOwned, | 
 |        {{BrowsingDataModel::StorageType::kTrustTokens}, 100, 0}}, | 
 |   }; | 
 |  | 
 |   browsing_data_model_test_util::ValidateBrowsingDataEntries(model.get(), | 
 |                                                              expected_entries); | 
 | } | 
 |  | 
 | // Tests that the BrowsingDataModel::Iterator can handle when the outer map | 
 | // contains an empty inner map. | 
 | // | 
 | // This is done by inserting an outer map element with an empty value | 
 | // for the inner map and then attempting to iterate over the entire model. | 
 | TEST_F(BrowsingDataModelTest, IteratorCanHandleEmptyDataKeyEntriesMaps) { | 
 |   // The BrowsingDataModel is currently empty. | 
 |  | 
 |   // Insert an outer map element with an empty inner map as its value. We can do | 
 |   // this by using operator[] which will default construct a key/value pair if | 
 |   // it can't find the key. | 
 |   browsing_data_entries()[kSiteOrigin]; | 
 |  | 
 |   // Now iterate over the model. The distance should be 1 because we have our | 
 |   // empty outer element. | 
 |   EXPECT_EQ(1, std::distance(model()->begin(), model()->end())); | 
 | } | 
 |  | 
 | TEST_F(BrowsingDataModelTest, RemoveBasedOnPartitioning) { | 
 |   // TODO(crbug.com/40272946): Use helpers so this test can be broken up, likely | 
 |   // done alongside moving partition key detection to a visitor pattern. | 
 |   std::unique_ptr<BrowsingDataModel> model = BrowsingDataModel::BuildEmpty( | 
 |       storage_partition(), | 
 |       std::make_unique<browsing_data::TestBrowsingDataModelDelegate>()); | 
 |  | 
 |   // Setup 4 storage keys, partitioned and unpartitioned storage for 2 sites. | 
 |   auto first_party_storage_key = | 
 |       blink::StorageKey::CreateFirstParty(kSiteOrigin); | 
 |   auto partitioned_storage_key = | 
 |       blink::StorageKey::Create(kSiteOrigin, net::SchemefulSite(kTestOrigin), | 
 |                                 blink::mojom::AncestorChainBit::kCrossSite, | 
 |                                 /*third_party_partitioning_allowed=*/true); | 
 |   auto another_first_party_storage_key = | 
 |       blink::StorageKey::CreateFirstParty(kAnotherSiteOrigin); | 
 |   auto another_partitioned_storage_key = blink::StorageKey::Create( | 
 |       kAnotherSiteOrigin, net::SchemefulSite(kAnotherTestOrigin), | 
 |       blink::mojom::AncestorChainBit::kCrossSite, | 
 |       /*third_party_partitioning_allowed=*/true); | 
 |  | 
 |   // Add all the keys to the model and ensure they are present as 4 different | 
 |   // browsing data entries. | 
 |   model->AddBrowsingData(first_party_storage_key, | 
 |                          BrowsingDataModel::StorageType::kLocalStorage, 0); | 
 |   model->AddBrowsingData(partitioned_storage_key, | 
 |                          BrowsingDataModel::StorageType::kLocalStorage, 0); | 
 |   model->AddBrowsingData(another_first_party_storage_key, | 
 |                          BrowsingDataModel::StorageType::kLocalStorage, 0); | 
 |   model->AddBrowsingData(another_partitioned_storage_key, | 
 |                          BrowsingDataModel::StorageType::kLocalStorage, 0); | 
 |  | 
 |   auto expected_entries = std::vector<BrowsingDataEntry>{ | 
 |       {kSiteOriginHost, | 
 |        first_party_storage_key, | 
 |        {{BrowsingDataModel::StorageType::kLocalStorage}, 0, 0}}, | 
 |       {kSiteOriginHost, | 
 |        partitioned_storage_key, | 
 |        {{BrowsingDataModel::StorageType::kLocalStorage}, 0, 0}}, | 
 |       {kAnotherSiteOriginHost, | 
 |        another_first_party_storage_key, | 
 |        {{BrowsingDataModel::StorageType::kLocalStorage}, 0, 0}}, | 
 |       {kAnotherSiteOriginHost, | 
 |        another_partitioned_storage_key, | 
 |        {{BrowsingDataModel::StorageType::kLocalStorage}, 0, 0}}, | 
 |   }; | 
 |  | 
 |   browsing_data_model_test_util::ValidateBrowsingDataEntries(model.get(), | 
 |                                                              expected_entries); | 
 |  | 
 |   // Remove partitioned storage for one host, confirm that unpartitioned | 
 |   // storage for that host, and all storage for other hosts, are untouched. | 
 |   { | 
 |     base::RunLoop run_loop; | 
 |     model->RemovePartitionedBrowsingData(kSiteOriginHost, | 
 |                                          net::SchemefulSite(kTestOrigin), | 
 |                                          run_loop.QuitWhenIdleClosure()); | 
 |     run_loop.Run(); | 
 |   } | 
 |  | 
 |   expected_entries = std::vector<BrowsingDataEntry>{ | 
 |       {kSiteOriginHost, | 
 |        first_party_storage_key, | 
 |        {{BrowsingDataModel::StorageType::kLocalStorage}, 0, 0}}, | 
 |       {kAnotherSiteOriginHost, | 
 |        another_first_party_storage_key, | 
 |        {{BrowsingDataModel::StorageType::kLocalStorage}, 0, 0}}, | 
 |       {kAnotherSiteOriginHost, | 
 |        another_partitioned_storage_key, | 
 |        {{BrowsingDataModel::StorageType::kLocalStorage}, 0, 0}}, | 
 |   }; | 
 |   browsing_data_model_test_util::ValidateBrowsingDataEntries(model.get(), | 
 |                                                              expected_entries); | 
 |  | 
 |   // Remove unpartitioned storage for the other host, ensure that partitioned | 
 |   // storage for that host, and storage for first host, is unaffected. | 
 |   { | 
 |     base::RunLoop run_loop; | 
 |     model->RemoveUnpartitionedBrowsingData(kAnotherSiteOriginHost, | 
 |                                            run_loop.QuitWhenIdleClosure()); | 
 |     run_loop.Run(); | 
 |   } | 
 |  | 
 |   expected_entries = std::vector<BrowsingDataEntry>{ | 
 |       {kSiteOriginHost, | 
 |        first_party_storage_key, | 
 |        {{BrowsingDataModel::StorageType::kLocalStorage}, 0, 0}}, | 
 |       {kAnotherSiteOriginHost, | 
 |        another_partitioned_storage_key, | 
 |        {{BrowsingDataModel::StorageType::kLocalStorage}, 0, 0}}, | 
 |   }; | 
 |   browsing_data_model_test_util::ValidateBrowsingDataEntries(model.get(), | 
 |                                                              expected_entries); | 
 | } | 
 |  | 
 | TEST_F(BrowsingDataModelTest, ThirdPartyCookieTypes) { | 
 |   // Create data keys for all storage types. | 
 |   auto unpartitioned_storage_key = | 
 |       blink::StorageKey::CreateFirstParty(kSiteOrigin); | 
 |   auto partitioned_storage_key = | 
 |       blink::StorageKey::Create(kSiteOrigin, net::SchemefulSite(kTestOrigin), | 
 |                                 blink::mojom::AncestorChainBit::kCrossSite, | 
 |                                 /*third_party_partitioning_allowed=*/true); | 
 |   auto unpartitioned_session_storage_usage = | 
 |       content::SessionStorageUsageInfo{unpartitioned_storage_key, "example"}; | 
 |   auto partitioned_session_storage_usage = | 
 |       content::SessionStorageUsageInfo{partitioned_storage_key, "example"}; | 
 |  | 
 |   auto unpartitioned_shared_worker_info = browsing_data::SharedWorkerInfo( | 
 |       kSiteOrigin.GetURL(), "example", unpartitioned_storage_key, | 
 |       blink::mojom::SharedWorkerSameSiteCookies::kAll); | 
 |   auto partitioned_shared_worker_info = browsing_data::SharedWorkerInfo( | 
 |       kSiteOrigin.GetURL(), "example", partitioned_storage_key, | 
 |       blink::mojom::SharedWorkerSameSiteCookies::kNone); | 
 |  | 
 |   auto unpartitioned_cookie = MakeCanonicalCookie("name", kSiteOriginHost); | 
 |  | 
 |   auto partitioned_cookie = MakeCanonicalCookie( | 
 |       "__Host-partitioned", kSiteOriginHost, | 
 |       net::CookiePartitionKey::FromURLForTesting(GURL(kTestOriginHost))); | 
 |  | 
 |   auto partitioned_shared_dictionary_key = net::SharedDictionaryIsolationKey{ | 
 |       kSiteOrigin, net::SchemefulSite(kTestOrigin)}; | 
 |  | 
 |   content::InterestGroupManager::InterestGroupDataKey interest_group_key{ | 
 |       kSiteOrigin, kSiteOrigin}; | 
 |   content::AttributionDataModel::DataKey attribution_reporting_key{kSiteOrigin}; | 
 |   content::PrivateAggregationDataModel::DataKey private_aggregation_key{ | 
 |       kSiteOrigin}; | 
 |   net::device_bound_sessions::SessionKey device_bound_session_key( | 
 |       net::SchemefulSite(kSiteOrigin.GetURL()), | 
 |       net::device_bound_sessions::SessionKey::Id("session_id")); | 
 |  | 
 |   std::map<BrowsingDataModel::StorageType, BrowsingDataModel::DataKey> | 
 |       third_party_cookie_types = { | 
 |           {BrowsingDataModel::StorageType::kSharedStorage, | 
 |            unpartitioned_storage_key}, | 
 |           {BrowsingDataModel::StorageType::kLocalStorage, | 
 |            unpartitioned_storage_key}, | 
 |           {BrowsingDataModel::StorageType::kQuotaStorage, | 
 |            unpartitioned_storage_key}, | 
 |           {BrowsingDataModel::StorageType::kSessionStorage, | 
 |            unpartitioned_session_storage_usage}, | 
 |           {BrowsingDataModel::StorageType::kSharedWorker, | 
 |            unpartitioned_shared_worker_info}, | 
 |           {BrowsingDataModel::StorageType::kCookie, *unpartitioned_cookie}, | 
 |           {BrowsingDataModel::StorageType::kDeviceBoundSession, | 
 |            device_bound_session_key}}; | 
 |  | 
 |   std::map<BrowsingDataModel::StorageType, BrowsingDataModel::DataKey> | 
 |       non_third_party_cookie_types = { | 
 |           {BrowsingDataModel::StorageType::kSharedStorage, | 
 |            partitioned_storage_key}, | 
 |           {BrowsingDataModel::StorageType::kLocalStorage, | 
 |            partitioned_storage_key}, | 
 |           {BrowsingDataModel::StorageType::kQuotaStorage, | 
 |            partitioned_storage_key}, | 
 |           {BrowsingDataModel::StorageType::kCdmStorage, | 
 |            partitioned_storage_key}, | 
 |           {BrowsingDataModel::StorageType::kSessionStorage, | 
 |            partitioned_session_storage_usage}, | 
 |           {BrowsingDataModel::StorageType::kSharedWorker, | 
 |            partitioned_shared_worker_info}, | 
 |           {BrowsingDataModel::StorageType::kCookie, *partitioned_cookie}, | 
 |           {BrowsingDataModel::StorageType::kTrustTokens, kSiteOrigin}, | 
 |           {BrowsingDataModel::StorageType::kInterestGroup, interest_group_key}, | 
 |           {BrowsingDataModel::StorageType::kAttributionReporting, | 
 |            attribution_reporting_key}, | 
 |           {BrowsingDataModel::StorageType::kPrivateAggregation, | 
 |            private_aggregation_key}, | 
 |           {BrowsingDataModel::StorageType::kSharedDictionary, | 
 |            partitioned_shared_dictionary_key}}; | 
 |  | 
 |   std::unique_ptr<BrowsingDataModel> model = BrowsingDataModel::BuildEmpty( | 
 |       storage_partition(), | 
 |       std::make_unique<browsing_data::TestBrowsingDataModelDelegate>()); | 
 |  | 
 |   for (int i = static_cast<int>(BrowsingDataModel::StorageType::kFirstType); | 
 |        i <= static_cast<int>(BrowsingDataModel::StorageType::kLastType); i++) { | 
 |     auto type = static_cast<BrowsingDataModel::StorageType>(i); | 
 |  | 
 |     EXPECT_TRUE(third_party_cookie_types.count(type) || | 
 |                 non_third_party_cookie_types.count(type)) | 
 |         << "All storage types should be tested"; | 
 |   } | 
 |  | 
 |   for (const auto& [type, key] : third_party_cookie_types) { | 
 |     EXPECT_TRUE(model->IsBlockedByThirdPartyCookieBlocking(key, type)) | 
 |         << "Not blocking third_party_cookie_types of type: " << (int)type; | 
 |   } | 
 |  | 
 |   for (const auto& [type, key] : non_third_party_cookie_types) { | 
 |     EXPECT_FALSE(model->IsBlockedByThirdPartyCookieBlocking(key, type)) | 
 |         << "Blocking non_third_party_cookie_types of type: " << (int)type; | 
 |   } | 
 |  | 
 |   // Ensure the delegate is also consulted. | 
 |   EXPECT_TRUE(model->IsBlockedByThirdPartyCookieBlocking( | 
 |       kTestOrigin, static_cast<BrowsingDataModel::StorageType>( | 
 |                        browsing_data::TestBrowsingDataModelDelegate:: | 
 |                            StorageType::kTestDelegateType))); | 
 |   EXPECT_FALSE(model->IsBlockedByThirdPartyCookieBlocking( | 
 |       kTestOrigin, static_cast<BrowsingDataModel::StorageType>( | 
 |                        browsing_data::TestBrowsingDataModelDelegate:: | 
 |                            StorageType::kTestDelegateTypePartitioned))); | 
 | } | 
 |  | 
 | TEST_F(BrowsingDataModelTest, HasThirdPartyPartitioningSite_True) { | 
 |   // Check the logic for determining whether data is partitioned. | 
 |   std::unique_ptr<BrowsingDataModel> model = BrowsingDataModel::BuildEmpty( | 
 |       storage_partition(), | 
 |       std::make_unique<browsing_data::TestBrowsingDataModelDelegate>()); | 
 |  | 
 |   // Create a set of storage keys which all represent partitioned versions of | 
 |   // their data. | 
 |   auto partitioned_storage_key = | 
 |       blink::StorageKey::Create(kSiteOrigin, net::SchemefulSite(kTestOrigin), | 
 |                                 blink::mojom::AncestorChainBit::kCrossSite, | 
 |                                 /*third_party_partitioning_allowed=*/true); | 
 |   auto partitioned_session_storage_usage = | 
 |       content::SessionStorageUsageInfo{partitioned_storage_key, "example"}; | 
 |   auto partitioned_shared_dictionary_key = net::SharedDictionaryIsolationKey{ | 
 |       kSiteOrigin, net::SchemefulSite(kTestOrigin)}; | 
 |   auto partitioned_shared_worker_info = browsing_data::SharedWorkerInfo( | 
 |       kSiteOrigin.GetURL(), "example", partitioned_storage_key, | 
 |       blink::mojom::SharedWorkerSameSiteCookies::kNone); | 
 |  | 
 |   model->AddBrowsingData(partitioned_storage_key, | 
 |                          BrowsingDataModel::StorageType::kQuotaStorage, 0, 0); | 
 |   model->AddBrowsingData(partitioned_session_storage_usage, | 
 |                          BrowsingDataModel::StorageType::kSessionStorage, 0, 0); | 
 |   model->AddBrowsingData(partitioned_shared_dictionary_key, | 
 |                          BrowsingDataModel::StorageType::kSharedDictionary, 0, | 
 |                          0); | 
 |   model->AddBrowsingData(partitioned_shared_worker_info, | 
 |                          BrowsingDataModel::StorageType::kSharedWorker, 0, 0); | 
 |  | 
 |   // Check that every model entry is partitioned, and the site matches. | 
 |   for (const auto& entry : *model) { | 
 |     EXPECT_EQ(partitioned_storage_key.top_level_site(), | 
 |               *entry.GetThirdPartyPartitioningSite()); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(BrowsingDataModelTest, HasThirdPartyPartitioningSite_False) { | 
 |   // Check the logic for determining whether data is partitioned. | 
 |   std::unique_ptr<BrowsingDataModel> model = BrowsingDataModel::BuildEmpty( | 
 |       storage_partition(), | 
 |       std::make_unique<browsing_data::TestBrowsingDataModelDelegate>()); | 
 |  | 
 |   // Create a set of storage keys which all represent un-partitioned versions of | 
 |   // their data. | 
 |   auto unpartitioned_storage_key = | 
 |       blink::StorageKey::CreateFirstParty(kSiteOrigin); | 
 |   auto unpartitioned_session_storage_usage = | 
 |       content::SessionStorageUsageInfo{unpartitioned_storage_key, "example"}; | 
 |   auto unpartitioned_shared_dictionary_key = net::SharedDictionaryIsolationKey{ | 
 |       kSubdomainOrigin, net::SchemefulSite(kSiteOrigin)}; | 
 |   auto unpartitioned_shared_worker_info = browsing_data::SharedWorkerInfo( | 
 |       kSiteOrigin.GetURL(), "example", unpartitioned_storage_key, | 
 |       blink::mojom::SharedWorkerSameSiteCookies::kAll); | 
 |   auto non_partition_key = kAnotherTestOrigin; | 
 |  | 
 |   model->AddBrowsingData(unpartitioned_storage_key, | 
 |                          BrowsingDataModel::StorageType::kQuotaStorage, 0, 0); | 
 |   model->AddBrowsingData(unpartitioned_session_storage_usage, | 
 |                          BrowsingDataModel::StorageType::kSessionStorage, 0, 0); | 
 |   model->AddBrowsingData(unpartitioned_shared_dictionary_key, | 
 |                          BrowsingDataModel::StorageType::kSharedDictionary, 0, | 
 |                          0); | 
 |   model->AddBrowsingData(unpartitioned_shared_worker_info, | 
 |                          BrowsingDataModel::StorageType::kSharedWorker, 0, 0); | 
 |   model->AddBrowsingData(non_partition_key, | 
 |                          BrowsingDataModel::StorageType::kTrustTokens, 0, 0); | 
 |  | 
 |   // Check that every model entry is not partitioned, and so has no | 
 |   // partitioning site. | 
 |   for (const auto& entry : *model) { | 
 |     EXPECT_FALSE(entry.GetThirdPartyPartitioningSite().has_value()); | 
 |   } | 
 | } | 
 |  | 
 | class BrowsingDataModelSharedDictionaryTest : public BrowsingDataModelTest { | 
 |  public: | 
 |   BrowsingDataModelSharedDictionaryTest() { | 
 |     scoped_feature_list_.InitWithFeatures( | 
 |         /*enabled_features=*/{network::features:: | 
 |                                   kCompressionDictionaryTransportBackend}, | 
 |         /*disabled_features=*/{}); | 
 |   } | 
 |   ~BrowsingDataModelSharedDictionaryTest() override = default; | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList scoped_feature_list_; | 
 | }; | 
 |  | 
 | TEST_F(BrowsingDataModelSharedDictionaryTest, GetUsageInfo) { | 
 |   net::SharedDictionaryIsolationKey isolation_key( | 
 |       kTestOrigin, net::SchemefulSite(kTestOrigin)); | 
 |   EXPECT_CALL(*mock_network_context(), GetStoredTrustTokenCounts(testing::_)) | 
 |       .WillOnce( | 
 |           [&](network::TestNetworkContext::GetStoredTrustTokenCountsCallback | 
 |                   callback) { std::move(callback).Run({}); }); | 
 |   EXPECT_CALL(*mock_network_context(), GetSharedDictionaryUsageInfo(testing::_)) | 
 |       .WillOnce( | 
 |           [&](network::TestNetworkContext::GetSharedDictionaryUsageInfoCallback | 
 |                   callback) { | 
 |             std::move(callback).Run({net::SharedDictionaryUsageInfo{ | 
 |                 .isolation_key = isolation_key, .total_size_bytes = 1234}}); | 
 |           }); | 
 |   base::RunLoop run_loop; | 
 |   BuildModel(run_loop.QuitWhenIdleClosure()); | 
 |   run_loop.Run(); | 
 |   ValidateBrowsingDataEntries( | 
 |       model(), | 
 |       {{kTestOriginHost, | 
 |         isolation_key, | 
 |         {{BrowsingDataModel::StorageType::kSharedDictionary}, 1234, 0}}, | 
 |        {kTestOriginHost, | 
 |         kTestOrigin, | 
 |         {{static_cast<BrowsingDataModel::StorageType>( | 
 |              browsing_data::TestBrowsingDataModelDelegate::StorageType:: | 
 |                  kTestDelegateType)}, | 
 |          0, | 
 |          0}}}); | 
 | } | 
 |  | 
 | TEST_F(BrowsingDataModelSharedDictionaryTest, Delete) { | 
 |   net::SharedDictionaryIsolationKey isolation_key( | 
 |       kTestOrigin, net::SchemefulSite(kTestOrigin)); | 
 |   model()->AddBrowsingData( | 
 |       isolation_key, BrowsingDataModel::StorageType::kSharedDictionary, 1234); | 
 |   EXPECT_CALL(*mock_network_context(), | 
 |               ClearSharedDictionaryCacheForIsolationKey(testing::_, testing::_)) | 
 |       .WillOnce( | 
 |           [&](const net::SharedDictionaryIsolationKey& key, | 
 |               MockNetworkContext:: | 
 |                   ClearSharedDictionaryCacheForIsolationKeyCallback callback) { | 
 |             EXPECT_EQ(isolation_key, key); | 
 |             std::move(callback).Run(); | 
 |           }); | 
 |  | 
 |   base::RunLoop runloop; | 
 |   model()->RemoveBrowsingData(kTestOriginHost, runloop.QuitClosure()); | 
 |   runloop.Run(); | 
 | } |