| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/browsing_data/access_context_audit_service.h" |
| |
| #include "base/callback_helpers.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/i18n/time_formatting.h" |
| #include "base/task/thread_pool.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/test/test_simple_task_runner.h" |
| #include "chrome/browser/browsing_data/access_context_audit_database.h" |
| #include "chrome/browser/browsing_data/access_context_audit_service_factory.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/history/history_service_factory.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/browsing_data/content/local_shared_objects_container.h" |
| #include "components/browsing_data/core/features.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/content_settings/core/common/content_settings_types.h" |
| #include "components/history/core/browser/history_database_params.h" |
| #include "components/history/core/browser/url_row.h" |
| #include "components/history/core/test/test_history_database.h" |
| #include "content/public/browser/cookie_access_details.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/test_storage_partition.h" |
| #include "services/network/test/test_cookie_manager.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| // Checks that a record exists in |records| that matches both |cookie| and |
| // |top_frame_origin|. |
| void CheckContainsCookieRecord( |
| net::CanonicalCookie* cookie, |
| url::Origin top_frame_origin, |
| base::Time last_access_time, |
| const std::vector<AccessContextAuditDatabase::AccessRecord>& records) { |
| EXPECT_NE( |
| std::find_if( |
| records.begin(), records.end(), |
| [=](const AccessContextAuditDatabase::AccessRecord& record) { |
| return record.type == |
| AccessContextAuditDatabase::StorageAPIType::kCookie && |
| record.top_frame_origin == top_frame_origin && |
| record.name == cookie->Name() && |
| record.domain == cookie->Domain() && |
| record.path == cookie->Path() && |
| record.last_access_time == last_access_time && |
| record.is_persistent == cookie->IsPersistent(); |
| }), |
| records.end()); |
| } |
| |
| // Checks that info in |record| matches storage API access defined by |
| // |storage_origin|, |type| and |top_frame_origin| |
| void CheckContainsStorageAPIRecord( |
| url::Origin storage_origin, |
| AccessContextAuditDatabase::StorageAPIType type, |
| url::Origin top_frame_origin, |
| base::Time last_access_time, |
| const std::vector<AccessContextAuditDatabase::AccessRecord>& records) { |
| EXPECT_NE( |
| std::find_if(records.begin(), records.end(), |
| [=](const AccessContextAuditDatabase::AccessRecord& record) { |
| return record.type == type && |
| record.origin == storage_origin && |
| record.top_frame_origin == top_frame_origin && |
| record.last_access_time == last_access_time; |
| }), |
| records.end()); |
| } |
| |
| } // namespace |
| |
| class TestCookieManager : public network::TestCookieManager { |
| public: |
| void AddGlobalChangeListener( |
| mojo::PendingRemote<network::mojom::CookieChangeListener> |
| notification_pointer) override { |
| listener_registered_ = true; |
| } |
| |
| bool ListenerRegistered() { return listener_registered_; } |
| |
| protected: |
| bool listener_registered_ = false; |
| }; |
| |
| class AccessContextAuditServiceTest : public testing::Test { |
| public: |
| AccessContextAuditServiceTest() = default; |
| |
| std::unique_ptr<KeyedService> BuildTestContextAuditService( |
| content::BrowserContext* context) { |
| auto service = std::make_unique<AccessContextAuditService>( |
| static_cast<Profile*>(context)); |
| service->SetTaskRunnerForTesting(task_runner_); |
| service->Init(temp_directory_.GetPath(), cookie_manager(), |
| history_service(), storage_partition()); |
| return service; |
| } |
| |
| void SetUp() override { |
| feature_list_.InitWithFeatures(enabled_features(), disabled_features()); |
| |
| task_runner_ = base::ThreadPool::CreateUpdateableSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::BEST_EFFORT, |
| base::ThreadPolicy::PREFER_BACKGROUND, |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}); |
| |
| ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); |
| |
| history_service_ = std::make_unique<history::HistoryService>(); |
| history_service_->Init( |
| history::TestHistoryDatabaseParamsForPath(temp_directory_.GetPath())); |
| |
| TestingProfile::Builder builder; |
| builder.AddTestingFactory( |
| AccessContextAuditServiceFactory::GetInstance(), |
| base::BindRepeating( |
| &AccessContextAuditServiceTest::BuildTestContextAuditService, |
| base::Unretained(this))); |
| builder.SetPath(temp_directory_.GetPath()); |
| profile_ = builder.Build(); |
| FlushSequencedTaskRunner(); |
| browser_task_environment_.RunUntilIdle(); |
| } |
| |
| std::vector<AccessContextAuditDatabase::AccessRecord> GetAllAccessRecords() { |
| base::RunLoop run_loop; |
| std::vector<AccessContextAuditDatabase::AccessRecord> records_out; |
| service()->GetAllAccessRecords(base::BindLambdaForTesting( |
| [&](std::vector<AccessContextAuditDatabase::AccessRecord> records) { |
| records_out = records; |
| run_loop.QuitWhenIdle(); |
| })); |
| run_loop.Run(); |
| return records_out; |
| } |
| |
| void FlushSequencedTaskRunner() { |
| base::RunLoop run_loop; |
| task_runner_->PostTask(FROM_HERE, base::BindLambdaForTesting( |
| [&]() { run_loop.QuitWhenIdle(); })); |
| run_loop.Run(); |
| } |
| |
| TestCookieManager* cookie_manager() { return &cookie_manager_; } |
| content::TestStoragePartition* storage_partition() { |
| return &storage_partition_; |
| } |
| base::SimpleTestClock* clock() { return &clock_; } |
| TestingProfile* profile() { return profile_.get(); } |
| history::HistoryService* history_service() { return history_service_.get(); } |
| AccessContextAuditService* service() { |
| return AccessContextAuditServiceFactory::GetForProfile(profile()); |
| } |
| |
| protected: |
| content::BrowserTaskEnvironment browser_task_environment_; |
| std::unique_ptr<history::HistoryService> history_service_; |
| std::unique_ptr<TestingProfile> profile_; |
| base::SimpleTestClock clock_; |
| base::ScopedTempDir temp_directory_; |
| TestCookieManager cookie_manager_; |
| content::TestStoragePartition storage_partition_; |
| base::test::ScopedFeatureList feature_list_; |
| |
| scoped_refptr<base::UpdateableSequencedTaskRunner> task_runner_; |
| std::vector<AccessContextAuditDatabase::AccessRecord> records_; |
| |
| virtual std::vector<base::Feature> enabled_features() { |
| return {features::kClientStorageAccessContextAuditing}; |
| } |
| virtual std::vector<base::Feature> disabled_features() { |
| return {browsing_data::features::kEnableRemovingAllThirdPartyCookies}; |
| } |
| }; |
| |
| TEST_F(AccessContextAuditServiceTest, RegisterDeletionObservers) { |
| // Check that the service correctly registers observers for deletion. |
| EXPECT_TRUE(cookie_manager_.ListenerRegistered()); |
| EXPECT_EQ(1, storage_partition()->GetDataRemovalObserverCount()); |
| } |
| |
| TEST_F(AccessContextAuditServiceTest, CookieRecords) { |
| // Check that cookie access records are successfully stored and deleted. |
| GURL kTestCookieURL("https://example.com"); |
| std::string kTestCookieName = "test"; |
| std::string kTestNonPersistentCookieName = "test-non-persistent"; |
| const base::Time kAccessTime1 = base::Time::Now(); |
| clock()->SetNow(kAccessTime1); |
| service()->SetClockForTesting(clock()); |
| |
| auto test_cookie = net::CanonicalCookie::Create( |
| kTestCookieURL, kTestCookieName + "=1; max-age=3600", kAccessTime1, |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */); |
| auto test_non_persistent_cookie = net::CanonicalCookie::Create( |
| kTestCookieURL, kTestNonPersistentCookieName + "=1", kAccessTime1, |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */); |
| |
| // Record access to these cookies against a URL. |
| url::Origin kTopFrameOrigin = url::Origin::Create(GURL("https://test.com")); |
| service()->RecordCookieAccess({*test_cookie, *test_non_persistent_cookie}, |
| kTopFrameOrigin); |
| |
| // Ensure that the record of these accesses is correctly returned. |
| auto records = GetAllAccessRecords(); |
| EXPECT_EQ(2u, records.size()); |
| CheckContainsCookieRecord(test_cookie.get(), kTopFrameOrigin, kAccessTime1, |
| records); |
| CheckContainsCookieRecord(test_non_persistent_cookie.get(), kTopFrameOrigin, |
| kAccessTime1, records); |
| |
| // Check that informing the service of non-deletion changes to the cookies |
| // via the CookieChangeInterface is a no-op. |
| service()->OnCookieChange( |
| net::CookieChangeInfo(*test_cookie, net::CookieAccessResult(), |
| net::CookieChangeCause::OVERWRITE)); |
| service()->OnCookieChange(net::CookieChangeInfo( |
| *test_non_persistent_cookie, net::CookieAccessResult(), |
| net::CookieChangeCause::OVERWRITE)); |
| |
| records = GetAllAccessRecords(); |
| EXPECT_EQ(2u, records.size()); |
| CheckContainsCookieRecord(test_cookie.get(), kTopFrameOrigin, kAccessTime1, |
| records); |
| CheckContainsCookieRecord(test_non_persistent_cookie.get(), kTopFrameOrigin, |
| kAccessTime1, records); |
| |
| // Check that a repeated access correctly updates associated timestamp. |
| clock()->Advance(base::Hours(1)); |
| const base::Time kAccessTime2 = clock()->Now(); |
| service()->RecordCookieAccess({*test_cookie, *test_non_persistent_cookie}, |
| kTopFrameOrigin); |
| |
| records = GetAllAccessRecords(); |
| EXPECT_EQ(2u, records.size()); |
| CheckContainsCookieRecord(test_cookie.get(), kTopFrameOrigin, kAccessTime2, |
| records); |
| CheckContainsCookieRecord(test_non_persistent_cookie.get(), kTopFrameOrigin, |
| kAccessTime2, records); |
| |
| // Test GetCookieRecords by inserting a non-cookie record and then make |
| // sure GetCookieRecords does not include it in the result. |
| service()->RecordStorageAPIAccess( |
| url::Origin::Create(kTestCookieURL), |
| AccessContextAuditDatabase::StorageAPIType::kLocalStorage, |
| kTopFrameOrigin); |
| EXPECT_EQ(3u, GetAllAccessRecords().size()); |
| base::RunLoop run_loop; |
| std::vector<AccessContextAuditDatabase::AccessRecord> cookie_records; |
| service()->GetCookieAccessRecords(base::BindLambdaForTesting( |
| [&](std::vector<AccessContextAuditDatabase::AccessRecord> records) { |
| cookie_records = records; |
| run_loop.QuitWhenIdle(); |
| })); |
| run_loop.Run(); |
| EXPECT_EQ(2u, cookie_records.size()); |
| for (auto cr : cookie_records) { |
| EXPECT_EQ(AccessContextAuditDatabase::StorageAPIType::kCookie, cr.type); |
| } |
| service()->RemoveAllRecordsForOriginKeyedStorage( |
| url::Origin::Create(kTestCookieURL), |
| AccessContextAuditDatabase::StorageAPIType::kLocalStorage); |
| EXPECT_EQ(2u, GetAllAccessRecords().size()); |
| |
| // Inform the service the cookies have been deleted and check they are no |
| // longer returned. |
| service()->OnCookieChange( |
| net::CookieChangeInfo(*test_cookie, net::CookieAccessResult(), |
| net::CookieChangeCause::EXPLICIT)); |
| service()->OnCookieChange(net::CookieChangeInfo( |
| *test_non_persistent_cookie, net::CookieAccessResult(), |
| net::CookieChangeCause::EXPLICIT)); |
| records = GetAllAccessRecords(); |
| EXPECT_EQ(0u, records.size()); |
| } |
| |
| TEST_F(AccessContextAuditServiceTest, ExpiredCookies) { |
| // Check that no accesses are recorded for cookies which have already expired. |
| const GURL kTestURL("https://test.com"); |
| auto test_cookie_expired = net::CanonicalCookie::Create( |
| kTestURL, "test_1=1; expires=Thu, 01 Jan 1970 00:00:00 GMT", |
| base::Time::Now(), absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */); |
| |
| service()->RecordCookieAccess({*test_cookie_expired}, |
| url::Origin::Create(kTestURL)); |
| |
| EXPECT_EQ(0u, GetAllAccessRecords().size()); |
| } |
| |
| TEST_F(AccessContextAuditServiceTest, GetStorageRecords) { |
| GURL kTestUrl = GURL("https://example.com"); |
| const base::Time kAccessTime1 = base::Time::Now(); |
| |
| // Insert a cookie access and several storage access records into the |
| // database. |
| url::Origin kTopFrameOrigin = url::Origin::Create(GURL("https://test.com")); |
| service()->RecordCookieAccess( |
| {*net::CanonicalCookie::Create(kTestUrl, "foo=bar; max-age=3600", |
| kAccessTime1, |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */)}, |
| kTopFrameOrigin); |
| url::Origin kTestOrigin = url::Origin::Create(kTestUrl); |
| service()->RecordStorageAPIAccess( |
| kTestOrigin, AccessContextAuditDatabase::StorageAPIType::kLocalStorage, |
| kTopFrameOrigin); |
| service()->RecordStorageAPIAccess( |
| kTestOrigin, AccessContextAuditDatabase::StorageAPIType::kIndexedDB, |
| kTopFrameOrigin); |
| service()->RecordStorageAPIAccess( |
| kTestOrigin, AccessContextAuditDatabase::StorageAPIType::kServiceWorker, |
| kTopFrameOrigin); |
| EXPECT_EQ(4u, GetAllAccessRecords().size()); |
| |
| base::RunLoop run_loop; |
| std::vector<AccessContextAuditDatabase::AccessRecord> storage_records; |
| service()->GetStorageAccessRecords(base::BindLambdaForTesting( |
| [&](std::vector<AccessContextAuditDatabase::AccessRecord> records) { |
| storage_records = records; |
| run_loop.QuitWhenIdle(); |
| })); |
| run_loop.Run(); |
| EXPECT_EQ(3u, storage_records.size()); |
| for (auto sr : storage_records) { |
| EXPECT_NE(AccessContextAuditDatabase::StorageAPIType::kCookie, sr.type); |
| } |
| } |
| |
| TEST_F(AccessContextAuditServiceTest, GetThirdPartyStorageRecords) { |
| GURL kTestUrl = GURL("https://example.com"); |
| const base::Time kAccessTime1 = base::Time::Now(); |
| url::Origin kTestOrigin = url::Origin::Create(kTestUrl); |
| url::Origin kTopFrameOrigin = url::Origin::Create(GURL("https://test.com")); |
| |
| // Add a record of storage being accessed in a third-party context. |
| service()->RecordStorageAPIAccess( |
| kTestOrigin, AccessContextAuditDatabase::StorageAPIType::kIndexedDB, |
| kTopFrameOrigin); |
| // Add records of a cookie access and a storage access in a first-party |
| // context. These should be included in GetAllAccessRecords() but excluded |
| // from GetThirdPartyStorageAccessRecords(). |
| service()->RecordCookieAccess( |
| {*net::CanonicalCookie::Create(kTestUrl, "foo=bar; max-age=3600", |
| kAccessTime1, |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */)}, |
| kTopFrameOrigin); |
| service()->RecordStorageAPIAccess( |
| kTestOrigin, AccessContextAuditDatabase::StorageAPIType::kLocalStorage, |
| kTestOrigin); |
| EXPECT_EQ(3u, GetAllAccessRecords().size()); |
| |
| base::RunLoop run_loop; |
| std::vector<AccessContextAuditDatabase::AccessRecord> storage_records; |
| service()->GetThirdPartyStorageAccessRecords(base::BindLambdaForTesting( |
| [&](std::vector<AccessContextAuditDatabase::AccessRecord> records) { |
| storage_records = records; |
| run_loop.QuitWhenIdle(); |
| })); |
| run_loop.Run(); |
| EXPECT_EQ(1u, storage_records.size()); |
| EXPECT_EQ(AccessContextAuditDatabase::StorageAPIType::kIndexedDB, |
| storage_records[0].type); |
| } |
| |
| TEST_F(AccessContextAuditServiceTest, OriginKeyedStorageDeleted) { |
| // Check that informing the service that an origin's storage of a particular |
| // type as been deleted removes all records of that storage. |
| const auto kTestStorageType1 = |
| AccessContextAuditDatabase::StorageAPIType::kWebDatabase; |
| const auto kTestStorageType2 = |
| AccessContextAuditDatabase::StorageAPIType::kAppCache; |
| const url::Origin kTestOrigin1 = |
| url::Origin::Create(GURL("https://example.com")); |
| const url::Origin kTestOrigin2 = |
| url::Origin::Create(GURL("https://example2.com")); |
| const url::Origin kTestTopLevelOrigin = |
| url::Origin::Create(GURL("https://example3.com")); |
| const base::Time kAccessTime = base::Time::Now(); |
| clock()->SetNow(kAccessTime); |
| service()->SetClockForTesting(clock()); |
| |
| // Record accesses for the 4 possible test type and origin combinations. |
| service()->RecordStorageAPIAccess(kTestOrigin1, kTestStorageType1, |
| kTestTopLevelOrigin); |
| service()->RecordStorageAPIAccess(kTestOrigin1, kTestStorageType2, |
| kTestTopLevelOrigin); |
| service()->RecordStorageAPIAccess(kTestOrigin2, kTestStorageType1, |
| kTestTopLevelOrigin); |
| service()->RecordStorageAPIAccess(kTestOrigin2, kTestStorageType2, |
| kTestTopLevelOrigin); |
| |
| // Remove records for Origin1 and Type1 and ensure the record is removed, but |
| // those for Origin2 or Type2 are not. |
| service()->RemoveAllRecordsForOriginKeyedStorage(kTestOrigin1, |
| kTestStorageType1); |
| |
| auto records = GetAllAccessRecords(); |
| EXPECT_EQ(3u, records.size()); |
| CheckContainsStorageAPIRecord(kTestOrigin1, kTestStorageType2, |
| kTestTopLevelOrigin, kAccessTime, records); |
| CheckContainsStorageAPIRecord(kTestOrigin2, kTestStorageType1, |
| kTestTopLevelOrigin, kAccessTime, records); |
| CheckContainsStorageAPIRecord(kTestOrigin2, kTestStorageType2, |
| kTestTopLevelOrigin, kAccessTime, records); |
| } |
| |
| TEST_F(AccessContextAuditServiceTest, HistoryDeletion) { |
| // Check when the last record of an origin is deleted from history all records |
| // with it as a top frame origin are also removed. |
| const auto kTestStorageType = |
| AccessContextAuditDatabase::StorageAPIType::kWebDatabase; |
| const url::Origin kTestStorageOrigin = |
| url::Origin::Create(GURL("http://test.com")); |
| const GURL kTestCookieURL("https://example.com"); |
| const std::string kTestCookieName = "test"; |
| const GURL kURL1 = GURL("https://remaining-entries.com/test1"); |
| const GURL kURL2 = GURL("https://remaining-entries.com/test2"); |
| const GURL kURL3 = GURL("https://no-remaining-entries.com/test1"); |
| const url::Origin kHistoryEntriesRemainingOrigin = url::Origin::Create(kURL1); |
| const url::Origin kNoRemainingHistoryEntriesOrigin = |
| url::Origin::Create(kURL3); |
| const base::Time kAccessTime = base::Time::Now(); |
| clock()->SetNow(kAccessTime); |
| service()->SetClockForTesting(clock()); |
| |
| auto test_cookie = net::CanonicalCookie::Create( |
| kTestCookieURL, kTestCookieName + "=1; max-age=3600", kAccessTime, |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */); |
| |
| // Record access for two top level origins for the same storage and cookie. |
| service()->RecordCookieAccess({*test_cookie}, kHistoryEntriesRemainingOrigin); |
| service()->RecordCookieAccess({*test_cookie}, |
| kNoRemainingHistoryEntriesOrigin); |
| service()->RecordStorageAPIAccess(kTestStorageOrigin, kTestStorageType, |
| kHistoryEntriesRemainingOrigin); |
| service()->RecordStorageAPIAccess(kTestStorageOrigin, kTestStorageType, |
| kNoRemainingHistoryEntriesOrigin); |
| |
| // Ensure all records have been initially recorded. |
| EXPECT_EQ(4u, GetAllAccessRecords().size()); |
| |
| // Add history entries for all three URLs, then remove history entries for |
| // URL1 and URL3. This will fire a history deletion event where the shared |
| // origin of URL1 & URL2 has a remaining history entry, but no entry for the |
| // URL3 origin remains. |
| history_service()->AddPageWithDetails(kURL1, u"Test 1", 1, 1, |
| base::Time::Now(), false, |
| history::SOURCE_BROWSED); |
| history_service()->AddPageWithDetails(kURL2, u"Test 2", 1, 1, |
| base::Time::Now(), false, |
| history::SOURCE_BROWSED); |
| history_service()->AddPageWithDetails(kURL3, u"Test 3", 1, 1, |
| base::Time::Now(), false, |
| history::SOURCE_BROWSED); |
| history_service()->DeleteURLs({kURL1, kURL3}); |
| base::RunLoop run_loop; |
| history_service()->FlushForTest(run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| // Confirm that the records for the origin of URL3 have been removed, but the |
| // records for the shared origin of URL1 & URL2 remain. |
| auto records = GetAllAccessRecords(); |
| EXPECT_EQ(2u, records.size()); |
| CheckContainsCookieRecord(test_cookie.get(), kHistoryEntriesRemainingOrigin, |
| kAccessTime, records); |
| CheckContainsStorageAPIRecord(kTestStorageOrigin, kTestStorageType, |
| kHistoryEntriesRemainingOrigin, kAccessTime, |
| records); |
| } |
| |
| TEST_F(AccessContextAuditServiceTest, AllHistoryDeletion) { |
| // Test that a deletion for all history removes all records, including those |
| // for origins without any history entries. |
| const GURL kHistoryEntryURL = GURL("https://history.com/test1"); |
| const url::Origin kHistoryEntryOrigin = url::Origin::Create(kHistoryEntryURL); |
| const url::Origin kNoHistoryEntryOrigin = |
| url::Origin::Create(GURL("https://no-history-entry.com/")); |
| history_service()->AddPageWithDetails(kHistoryEntryURL, u"Test", 1, 1, |
| base::Time::Now(), false, |
| history::SOURCE_BROWSED); |
| |
| // Record two sets of unrelated accesses to cookies and storage APIs, one for |
| // the origin with a history entry, and one for the origin without. |
| service()->RecordCookieAccess( |
| {*net::CanonicalCookie::Create(GURL("https://foo.com"), |
| "foo=1; max-age=3600", base::Time::Now(), |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */)}, |
| kHistoryEntryOrigin); |
| service()->RecordCookieAccess( |
| {*net::CanonicalCookie::Create(GURL("https://bar.com"), |
| "bar=1; max-age=3600", base::Time::Now(), |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */)}, |
| kNoHistoryEntryOrigin); |
| service()->RecordStorageAPIAccess( |
| url::Origin::Create(GURL("https://foo.com")), |
| AccessContextAuditDatabase::StorageAPIType::kWebDatabase, |
| kHistoryEntryOrigin); |
| service()->RecordStorageAPIAccess( |
| url::Origin::Create(GURL("https://bar.com")), |
| AccessContextAuditDatabase::StorageAPIType::kIndexedDB, |
| kNoHistoryEntryOrigin); |
| |
| // Check access has been initially recorded. |
| EXPECT_EQ(4u, GetAllAccessRecords().size()); |
| |
| // Expire all history and confirm that all records are removed. |
| base::RunLoop run_loop; |
| base::CancelableTaskTracker task_tracker; |
| history_service()->ExpireHistoryBetween( |
| std::set<GURL>(), base::Time(), base::Time(), |
| /*user_initiated*/ true, run_loop.QuitClosure(), &task_tracker); |
| run_loop.Run(); |
| |
| EXPECT_EQ(0u, GetAllAccessRecords().size()); |
| } |
| |
| TEST_F(AccessContextAuditServiceTest, TimeRangeHistoryDeletion) { |
| // Test that deleting a time range of history records correctly removes |
| // records within the time range, as well as records for which no history |
| // entry for the top frame origin remains. |
| |
| // Create a situation where origin https://foo.com has history entries and |
| // access records with timestamps both inside and outside the deleted range. |
| // Additionally create a single history entry for origin https://bar.com |
| // inside the deleted range, and multiple access records outside the range. |
| // After deletion, the access records for https://foo.com outside the deletion |
| // range should still be present, while all access records https://bar.com |
| // should have been removed. |
| |
| const GURL kURL1 = GURL("https://foo.com/example.html"); |
| const GURL kURL2 = GURL("https://bar.com/another.html"); |
| const url::Origin kOrigin1 = url::Origin::Create(kURL1); |
| const url::Origin kOrigin2 = url::Origin::Create(kURL2); |
| const GURL kTestCookieURL = GURL("https://test.com"); |
| const auto kTestStorageType1 = |
| AccessContextAuditDatabase::StorageAPIType::kWebDatabase; |
| const auto kTestStorageType2 = |
| AccessContextAuditDatabase::StorageAPIType::kAppCache; |
| |
| clock()->SetNow(base::Time::Now()); |
| service()->SetClockForTesting(clock()); |
| const base::Time kInsideTimeRange = clock()->Now() + base::Hours(1); |
| const base::Time kOutsideTimeRange = clock()->Now() + base::Hours(3); |
| |
| history_service()->AddPageWithDetails(kURL1, u"Test1", 1, 1, kInsideTimeRange, |
| false, history::SOURCE_BROWSED); |
| history_service()->AddPageWithDetails(kURL2, u"Test2", 1, 1, kInsideTimeRange, |
| false, history::SOURCE_BROWSED); |
| history_service()->AddPageWithDetails( |
| kURL1, u"Test3", 1, 1, kOutsideTimeRange, false, history::SOURCE_BROWSED); |
| |
| // Record accesses to cookies both inside and outside the deletion range. |
| auto cookie_accessed_in_range = net::CanonicalCookie::Create( |
| kTestCookieURL, "inside=1; max-age=3600", kInsideTimeRange, |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */); |
| auto cookie_accessed_outside_range = net::CanonicalCookie::Create( |
| kTestCookieURL, "outside=1; max-age=3600", kOutsideTimeRange, |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */); |
| |
| clock()->SetNow(kInsideTimeRange); |
| service()->RecordCookieAccess({*cookie_accessed_in_range}, kOrigin1); |
| clock()->SetNow(kOutsideTimeRange); |
| service()->RecordCookieAccess({*cookie_accessed_outside_range}, kOrigin1); |
| service()->RecordCookieAccess({*cookie_accessed_outside_range}, kOrigin2); |
| |
| // Record accesses to storage APIs both inside and outside the deletion range. |
| clock()->SetNow(kInsideTimeRange); |
| service()->RecordStorageAPIAccess(kOrigin1, kTestStorageType1, kOrigin1); |
| clock()->SetNow(kOutsideTimeRange); |
| service()->RecordStorageAPIAccess(kOrigin1, kTestStorageType2, kOrigin1); |
| service()->RecordStorageAPIAccess(kOrigin2, kTestStorageType1, kOrigin2); |
| |
| // Ensure all records have been initially recorded. |
| EXPECT_EQ(6u, GetAllAccessRecords().size()); |
| |
| // Expire history in target time range. |
| base::RunLoop run_loop; |
| base::CancelableTaskTracker task_tracker; |
| history_service()->ExpireHistoryBetween( |
| std::set<GURL>(), kInsideTimeRange - base::Minutes(10), |
| kInsideTimeRange + base::Minutes(10), |
| /*user_initiated*/ true, run_loop.QuitClosure(), &task_tracker); |
| run_loop.Run(); |
| |
| // Ensure records have been removed as expected. |
| auto records = GetAllAccessRecords(); |
| EXPECT_EQ(2u, records.size()); |
| CheckContainsCookieRecord(cookie_accessed_outside_range.get(), kOrigin1, |
| kOutsideTimeRange, records); |
| CheckContainsStorageAPIRecord(kOrigin1, kTestStorageType2, kOrigin1, |
| kOutsideTimeRange, records); |
| } |
| |
| TEST_F(AccessContextAuditServiceTest, SessionOnlyRecords) { |
| // Check that data for cookie domains and storage origins are cleared on |
| // service shutdown when the associated content settings indicate they should. |
| const GURL kTestPersistentURL("https://persistent.com"); |
| const GURL kTestSessionOnlyExplicitURL("https://explicit-session-only.com"); |
| const GURL kTestSessionOnlyContentSettingURL("https://content-setting.com"); |
| url::Origin kTopFrameOrigin = url::Origin::Create(GURL("https://test.com")); |
| std::string kTestCookieName = "test"; |
| const auto kTestStorageType = |
| AccessContextAuditDatabase::StorageAPIType::kWebDatabase; |
| const base::Time kAccessTime = base::Time::Now(); |
| clock()->SetNow(kAccessTime); |
| service()->SetClockForTesting(clock()); |
| |
| // Create a cookie that will persist after shutdown. |
| auto test_cookie_persistent = net::CanonicalCookie::Create( |
| kTestPersistentURL, kTestCookieName + "=1; max-age=3600", kAccessTime, |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */); |
| |
| // Create a cookie that will persist (be cleared on next startup) because it |
| // is explicitly session only. |
| auto test_cookie_session_only_explicit = net::CanonicalCookie::Create( |
| kTestSessionOnlyExplicitURL, kTestCookieName + "=1", kAccessTime, |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */); |
| |
| // Create a cookie that will be cleared because the content setting associated |
| // with the cookie domain is set to session only. |
| auto test_cookie_session_only_content_setting = net::CanonicalCookie::Create( |
| kTestSessionOnlyContentSettingURL, kTestCookieName + "=1; max-age=3600", |
| kAccessTime, absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */); |
| |
| service()->RecordCookieAccess( |
| {*test_cookie_persistent, *test_cookie_session_only_explicit, |
| *test_cookie_session_only_content_setting}, |
| kTopFrameOrigin); |
| |
| // Record storage APIs for both persistent and content setting based session |
| // only URLs. |
| service()->RecordStorageAPIAccess(url::Origin::Create(kTestPersistentURL), |
| kTestStorageType, kTopFrameOrigin); |
| service()->RecordStorageAPIAccess( |
| url::Origin::Create(kTestSessionOnlyContentSettingURL), kTestStorageType, |
| kTopFrameOrigin); |
| |
| // Ensure all records have been initially recorded. |
| EXPECT_EQ(5u, GetAllAccessRecords().size()); |
| |
| // Apply Session Only exception. |
| HostContentSettingsMapFactory::GetForProfile(profile()) |
| ->SetContentSettingDefaultScope( |
| kTestSessionOnlyContentSettingURL, GURL(), |
| ContentSettingsType::COOKIES, |
| ContentSetting::CONTENT_SETTING_SESSION_ONLY); |
| |
| // Instruct service to clear session only records and check that they are |
| // correctly removed. |
| service()->ClearSessionOnlyRecords(); |
| |
| auto records = GetAllAccessRecords(); |
| ASSERT_EQ(3u, records.size()); |
| CheckContainsCookieRecord(test_cookie_persistent.get(), kTopFrameOrigin, |
| kAccessTime, records); |
| CheckContainsCookieRecord(test_cookie_session_only_explicit.get(), |
| kTopFrameOrigin, kAccessTime, records); |
| CheckContainsStorageAPIRecord(url::Origin::Create(GURL(kTestPersistentURL)), |
| kTestStorageType, kTopFrameOrigin, kAccessTime, |
| records); |
| |
| // Update the default content setting to SESSION_ONLY and ensure that all |
| // records are cleared. |
| HostContentSettingsMapFactory::GetForProfile(profile()) |
| ->SetDefaultContentSetting(ContentSettingsType::COOKIES, |
| ContentSetting::CONTENT_SETTING_SESSION_ONLY); |
| service()->ClearSessionOnlyRecords(); |
| records = GetAllAccessRecords(); |
| ASSERT_EQ(0u, records.size()); |
| } |
| |
| TEST_F(AccessContextAuditServiceTest, OnOriginDataCleared) { |
| // Check that providing parameters with varying levels of specificity to the |
| // OnOriginDataCleared function all clear data correctly. |
| auto kTopFrameOrigin = url::Origin::Create(GURL("https://example.com")); |
| auto kTestOrigin1 = url::Origin::Create(GURL("https://test1.com")); |
| auto kTestOrigin2 = url::Origin::Create(GURL("https://test2.com")); |
| auto kTestOrigin3 = url::Origin::Create(GURL("https://test3.com")); |
| |
| const auto kTestStorageType1 = |
| AccessContextAuditDatabase::StorageAPIType::kWebDatabase; |
| const auto kTestStorageType2 = |
| AccessContextAuditDatabase::StorageAPIType::kIndexedDB; |
| const auto kTestStorageType3 = |
| AccessContextAuditDatabase::StorageAPIType::kAppCache; |
| |
| clock()->SetNow(base::Time()); |
| service()->SetClockForTesting(clock()); |
| |
| clock()->Advance(base::Hours(1)); |
| service()->RecordStorageAPIAccess(kTestOrigin1, kTestStorageType1, |
| kTopFrameOrigin); |
| |
| clock()->Advance(base::Hours(1)); |
| const base::Time kAccessTime1 = clock()->Now(); |
| service()->RecordStorageAPIAccess(kTestOrigin2, kTestStorageType2, |
| kTopFrameOrigin); |
| |
| clock()->Advance(base::Hours(1)); |
| const base::Time kAccessTime2 = clock()->Now(); |
| service()->RecordStorageAPIAccess(kTestOrigin3, kTestStorageType3, |
| kTopFrameOrigin); |
| EXPECT_EQ(3U, GetAllAccessRecords().size()); |
| |
| // Provide all parameters such that TestOrigin1's record is removed. |
| auto origin_matcher = base::BindLambdaForTesting( |
| [&](const url::Origin& origin) { return origin == kTestOrigin1; }); |
| service()->OnOriginDataCleared( |
| content::StoragePartition::REMOVE_DATA_MASK_WEBSQL, origin_matcher, |
| base::Time() + base::Minutes(50), base::Time() + base::Minutes(80)); |
| |
| auto records = GetAllAccessRecords(); |
| ASSERT_EQ(2U, records.size()); |
| CheckContainsStorageAPIRecord(kTestOrigin2, kTestStorageType2, |
| kTopFrameOrigin, kAccessTime1, records); |
| CheckContainsStorageAPIRecord(kTestOrigin3, kTestStorageType3, |
| kTopFrameOrigin, kAccessTime2, records); |
| |
| // Provide more generalised parameters that target TestOrigin2's record. |
| service()->OnOriginDataCleared( |
| content::StoragePartition::REMOVE_DATA_MASK_ALL, base::NullCallback(), |
| base::Time() + base::Minutes(80), base::Time() + base::Minutes(130)); |
| |
| records = GetAllAccessRecords(); |
| ASSERT_EQ(1U, records.size()); |
| CheckContainsStorageAPIRecord(kTestOrigin3, kTestStorageType3, |
| kTopFrameOrigin, kAccessTime2, records); |
| |
| // Provide broadest possible parameters which should result in the final |
| // record being removed. |
| service()->OnOriginDataCleared( |
| content::StoragePartition::REMOVE_DATA_MASK_ALL, base::NullCallback(), |
| base::Time(), base::Time::Max()); |
| |
| records = GetAllAccessRecords(); |
| ASSERT_EQ(0U, records.size()); |
| } |
| |
| TEST_F(AccessContextAuditServiceTest, OpaqueOrigins) { |
| // Check that records which have opaque top frame origins are not recorded. |
| auto test_cookie = net::CanonicalCookie::Create( |
| GURL("https://example.com"), "test_1=1; max-age=3600", base::Time::Now(), |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */); |
| service()->RecordCookieAccess({*test_cookie}, url::Origin()); |
| service()->RecordStorageAPIAccess( |
| url::Origin::Create(GURL("https://example.com")), |
| AccessContextAuditDatabase::StorageAPIType::kWebDatabase, url::Origin()); |
| |
| auto records = GetAllAccessRecords(); |
| ASSERT_EQ(0U, records.size()); |
| } |
| |
| TEST_F(AccessContextAuditServiceTest, CookieAccessHelper) { |
| // Check that the CookieAccessHelper is correctly forwarding accesses as |
| // appropriate and responding to deletions. |
| url::Origin kTopFrameOrigin = url::Origin::Create(GURL("https://test.com")); |
| GURL kTestCookieURL("https://example.com"); |
| std::string kTestCookieName = "test"; |
| const base::Time kAccessTime1 = base::Time::Now(); |
| clock()->SetNow(kAccessTime1); |
| service()->SetClockForTesting(clock()); |
| |
| auto test_cookie = net::CanonicalCookie::Create( |
| kTestCookieURL, kTestCookieName + "=1; max-age=3600", kAccessTime1, |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */); |
| |
| // Record access to the cookie via a helper. |
| auto helper = std::make_unique<AccessContextAuditService::CookieAccessHelper>( |
| service()); |
| helper->RecordCookieAccess({*test_cookie}, kTopFrameOrigin); |
| |
| // Reaccess the cookie at a later time. |
| const base::Time kAccessTime2 = clock()->Now() + base::Minutes(1); |
| clock()->SetNow(kAccessTime2); |
| helper->RecordCookieAccess({*test_cookie}, kTopFrameOrigin); |
| |
| // The only record should match the second access. |
| auto records = GetAllAccessRecords(); |
| EXPECT_EQ(1u, records.size()); |
| CheckContainsCookieRecord(test_cookie.get(), kTopFrameOrigin, kAccessTime2, |
| records); |
| |
| // Inform the audit service that the cookie has been deleted, which should |
| // cause the helper to clear it from the set of accessed cookies and remove |
| // the database record. |
| service()->OnCookieChange( |
| net::CookieChangeInfo(*test_cookie, net::CookieAccessResult(), |
| net::CookieChangeCause::EXPLICIT)); |
| |
| // Flush the helper and ensure that no cookie access is recorded. |
| helper->FlushCookieRecords(); |
| records = GetAllAccessRecords(); |
| EXPECT_EQ(0u, records.size()); |
| |
| // Record a cookie access and delete the helper, the access should be flushed |
| // to the service. |
| const base::Time kAccessTime3 = clock()->Now() + base::Minutes(1); |
| clock()->SetNow(kAccessTime3); |
| helper->RecordCookieAccess({*test_cookie}, kTopFrameOrigin); |
| |
| helper.reset(); |
| records = GetAllAccessRecords(); |
| EXPECT_EQ(1u, records.size()); |
| CheckContainsCookieRecord(test_cookie.get(), kTopFrameOrigin, kAccessTime3, |
| records); |
| } |
| |
| class AccessContextAuditThirdPartyDataClearingTest |
| : public AccessContextAuditServiceTest { |
| protected: |
| void InsertAccessRecords(GURL top_level_url, GURL cross_site_url) { |
| url::Origin top_level_origin = url::Origin::Create(top_level_url); |
| url::Origin cross_site_origin = url::Origin::Create(cross_site_url); |
| |
| // Should keep records of same-site and cross-site cookie accesses. |
| // Third-party data clearing does not depend on the access context auditing |
| // service to determine which cookies to clear, so these should get deleted. |
| service()->RecordCookieAccess( |
| {*net::CanonicalCookie::Create( |
| top_level_url, "same=site; max-age=3600", base::Time::Now(), |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */)}, |
| top_level_origin); |
| service()->RecordCookieAccess( |
| {*net::CanonicalCookie::Create( |
| cross_site_url, "cross=site; max-age=3600", base::Time::Now(), |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */)}, |
| top_level_origin); |
| |
| // Set a same-site storage access record. This should get deleted. |
| service()->RecordStorageAPIAccess( |
| top_level_origin, |
| AccessContextAuditDatabase::StorageAPIType::kIndexedDB, |
| top_level_origin); |
| // Set a cross-site storage access record. This should be kept but the |
| // top-level origin should be removed. |
| service()->RecordStorageAPIAccess( |
| cross_site_origin, |
| AccessContextAuditDatabase::StorageAPIType::kLocalStorage, |
| top_level_origin); |
| |
| // Ensure all records are added. |
| EXPECT_EQ(4u, GetAllAccessRecords().size()); |
| } |
| |
| void ValidateCrossSiteStorageRecords(GURL cross_site_url) { |
| // Ensure only the cross-site storage access record remains and its |
| // top-level origin is opaque. |
| std::vector<AccessContextAuditDatabase::AccessRecord> records = |
| GetAllAccessRecords(); |
| EXPECT_EQ(1u, records.size()); |
| EXPECT_EQ(AccessContextAuditDatabase::StorageAPIType::kLocalStorage, |
| records[0].type); |
| EXPECT_EQ(url::Origin::Create(cross_site_url), records[0].origin); |
| EXPECT_TRUE(records[0].top_frame_origin.opaque()); |
| } |
| |
| std::vector<base::Feature> enabled_features() override { |
| return {features::kClientStorageAccessContextAuditing, |
| browsing_data::features::kEnableRemovingAllThirdPartyCookies}; |
| } |
| std::vector<base::Feature> disabled_features() override { return {}; } |
| }; |
| |
| // Test that when we enable user controls to clear third-party data, we do not |
| // clear records of cross-site storage access. This is because when we delete |
| // third-party data, we query the access context audit service to determine |
| // which sites accessed storage in cross-site contexts and delete storage for |
| // those sites. Our solution is to remove the top-level origin from the records |
| // when users clear history, but only clear the records when the respective |
| // storage is deleted. |
| TEST_F(AccessContextAuditThirdPartyDataClearingTest, HistoryDeletion) { |
| const GURL kTopLevelURL("https://toplevel.com/"); |
| const GURL kCrossSiteURL("https://cross.site.com/"); |
| |
| InsertAccessRecords(kTopLevelURL, kCrossSiteURL); |
| |
| // Remove history entries for the top level URL. |
| history_service()->AddPageWithDetails(kTopLevelURL, u"Test 1", 1, 1, |
| base::Time::Now(), false, |
| history::SOURCE_BROWSED); |
| history_service()->DeleteURLs({kTopLevelURL}); |
| base::RunLoop run_loop; |
| history_service()->FlushForTest(run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| ValidateCrossSiteStorageRecords(kCrossSiteURL); |
| } |
| |
| TEST_F(AccessContextAuditThirdPartyDataClearingTest, AllHistoryDeletion) { |
| const GURL kTopLevelURL("https://toplevel.com/"); |
| const GURL kCrossSiteURL("https://cross.site.com/"); |
| |
| InsertAccessRecords(kTopLevelURL, kCrossSiteURL); |
| |
| // Expire all history and confirm that all records are removed. |
| base::RunLoop run_loop; |
| base::CancelableTaskTracker task_tracker; |
| history_service()->ExpireHistoryBetween( |
| std::set<GURL>(), base::Time(), base::Time(), |
| /*user_initiated*/ true, run_loop.QuitClosure(), &task_tracker); |
| run_loop.Run(); |
| |
| ValidateCrossSiteStorageRecords(kCrossSiteURL); |
| } |
| |
| TEST_F(AccessContextAuditThirdPartyDataClearingTest, TimeRangeHistoryDeletion) { |
| const GURL kTopLevelURL("https://toplevel.com/"); |
| const GURL kInsideTimeRangeURL("https://inside.range.com/"); |
| const GURL kOutsideTimeRangeURL("https://outside.range.com/"); |
| |
| const url::Origin kTopLevelOrigin = url::Origin::Create(kTopLevelURL); |
| const url::Origin kInsideTimeRangeOrigin = |
| url::Origin::Create(kInsideTimeRangeURL); |
| const url::Origin kOutsideTimeRangeOrigin = |
| url::Origin::Create(kOutsideTimeRangeURL); |
| |
| clock()->SetNow(base::Time::Now()); |
| service()->SetClockForTesting(clock()); |
| const base::Time kInsideTimeRange = clock()->Now() + base::Hours(1); |
| const base::Time kOutsideTimeRange = clock()->Now() + base::Hours(3); |
| |
| clock()->SetNow(kOutsideTimeRange); |
| // A cookie record outside the time range should not be modified. |
| service()->RecordCookieAccess( |
| {*net::CanonicalCookie::Create( |
| kOutsideTimeRangeURL, "same=site; max-age=3600", kOutsideTimeRange, |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */)}, |
| kTopLevelOrigin); |
| clock()->SetNow(kInsideTimeRange); |
| // A cookie record inside the time range should be deleted. |
| service()->RecordCookieAccess( |
| {*net::CanonicalCookie::Create( |
| kInsideTimeRangeURL, "cross=site; max-age=3600", kInsideTimeRange, |
| absl::nullopt /* server_time */, |
| absl::nullopt /* cookie_partition_key */)}, |
| kTopLevelOrigin); |
| // A same-site storage record in the time range should be deleted. |
| service()->RecordStorageAPIAccess( |
| kTopLevelOrigin, AccessContextAuditDatabase::StorageAPIType::kIndexedDB, |
| kTopLevelOrigin); |
| // Set a cross-site storage access record in the time range. This should be |
| // kept but the top-level origin should be removed. |
| service()->RecordStorageAPIAccess( |
| kInsideTimeRangeOrigin, |
| AccessContextAuditDatabase::StorageAPIType::kLocalStorage, |
| kTopLevelOrigin); |
| clock()->SetNow(kOutsideTimeRange); |
| // A cross-site storage record outside the time range should not be modified. |
| service()->RecordStorageAPIAccess( |
| kOutsideTimeRangeOrigin, |
| AccessContextAuditDatabase::StorageAPIType::kServiceWorker, |
| kTopLevelOrigin); |
| EXPECT_EQ(5u, GetAllAccessRecords().size()); |
| |
| history_service()->AddPageWithDetails(kTopLevelURL, u"Test1", 1, 1, |
| kInsideTimeRange, false, |
| history::SOURCE_BROWSED); |
| history_service()->AddPageWithDetails(kTopLevelURL, u"Test1", 1, 1, |
| kOutsideTimeRange, false, |
| history::SOURCE_BROWSED); |
| |
| // Expire history in target time range. |
| base::RunLoop run_loop; |
| base::CancelableTaskTracker task_tracker; |
| history_service()->ExpireHistoryBetween( |
| std::set<GURL>(), kInsideTimeRange - base::Minutes(10), |
| kInsideTimeRange + base::Minutes(10), |
| /*user_initiated*/ true, run_loop.QuitClosure(), &task_tracker); |
| run_loop.Run(); |
| |
| std::vector<AccessContextAuditDatabase::AccessRecord> records = |
| GetAllAccessRecords(); |
| EXPECT_EQ(3u, records.size()); |
| size_t n_storage_records = 0; |
| for (const auto& record : records) { |
| if (record.type == AccessContextAuditDatabase::StorageAPIType::kCookie) { |
| EXPECT_EQ("outside.range.com", record.domain); |
| continue; |
| } |
| n_storage_records++; |
| EXPECT_EQ(record.origin == url::Origin::Create(kInsideTimeRangeURL), |
| record.top_frame_origin.opaque()); |
| } |
| EXPECT_EQ(2u, n_storage_records); |
| } |