// Copyright 2022 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 "components/browsing_topics/browsing_topics_calculator.h" #include "base/files/scoped_temp_dir.h" #include "base/logging.h" #include "base/test/bind.h" #include "base/test/gtest_util.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" #include "components/browsing_topics/test_util.h" #include "components/browsing_topics/util.h" #include "components/content_settings/core/browser/cookie_settings.h" #include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/history/core/browser/history_database_params.h" #include "components/history/core/browser/history_service.h" #include "components/history/core/test/test_history_database.h" #include "components/optimization_guide/content/browser/page_content_annotations_service.h" #include "components/optimization_guide/content/browser/test_page_content_annotator.h" #include "components/optimization_guide/core/test_model_info_builder.h" #include "components/optimization_guide/core/test_optimization_guide_model_provider.h" #include "components/privacy_sandbox/privacy_sandbox_prefs.h" #include "components/privacy_sandbox/privacy_sandbox_settings.h" #include "components/privacy_sandbox/privacy_sandbox_test_util.h" #include "components/sync_preferences/testing_pref_service_syncable.h" #include "components/ukm/test_ukm_recorder.h" #include "content/public/test/browser_task_environment.h" #include "content/public/test/browsing_topics_test_util.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "third_party/blink/public/common/features.h" namespace browsing_topics { namespace { constexpr size_t kTaxonomySize = 349; constexpr int kTaxonomyVersion = 1; constexpr char kHost1[] = "www.foo1.com"; constexpr char kHost2[] = "www.foo2.com"; constexpr char kHost3[] = "www.foo3.com"; constexpr char kHost4[] = "www.foo4.com"; constexpr char kHost5[] = "www.foo5.com"; constexpr char kHost6[] = "www.foo6.com"; } // namespace class BrowsingTopicsCalculatorTest : public testing::Test { public: BrowsingTopicsCalculatorTest() : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) { EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); content_settings::CookieSettings::RegisterProfilePrefs(prefs_.registry()); HostContentSettingsMap::RegisterProfilePrefs(prefs_.registry()); privacy_sandbox::RegisterProfilePrefs(prefs_.registry()); host_content_settings_map_ = base::MakeRefCounted( &prefs_, /*is_off_the_record=*/false, /*store_last_modified=*/false, /*restore_session=*/false); cookie_settings_ = base::MakeRefCounted( host_content_settings_map_.get(), &prefs_, false, "chrome-extension"); auto privacy_sandbox_delegate = std::make_unique< privacy_sandbox_test_util::MockPrivacySandboxSettingsDelegate>(); privacy_sandbox_delegate->SetUpDefaultResponse(/*restricted=*/false); privacy_sandbox_settings_ = std::make_unique( std::move(privacy_sandbox_delegate), host_content_settings_map_.get(), cookie_settings_, &prefs_, /*incognito_profile=*/false); topics_site_data_manager_ = std::make_unique( temp_dir_.GetPath()); history_service_ = std::make_unique(); history_service_->Init( history::TestHistoryDatabaseParamsForPath(temp_dir_.GetPath())); optimization_guide_model_provider_ = std::make_unique< optimization_guide::TestOptimizationGuideModelProvider>(); page_content_annotations_service_ = std::make_unique( "en-US", optimization_guide_model_provider_.get(), history_service_.get(), nullptr, base::FilePath(), nullptr, nullptr); page_content_annotations_service_->OverridePageContentAnnotatorForTesting( &test_page_content_annotator_); task_environment_.RunUntilIdle(); } ~BrowsingTopicsCalculatorTest() override { host_content_settings_map_->ShutdownOnUIThread(); } EpochTopics CalculateTopics(base::circular_deque epochs = {}) { EpochTopics result = EpochTopics(base::Time()); base::RunLoop run_loop; TesterBrowsingTopicsCalculator topics_calculator = TesterBrowsingTopicsCalculator( privacy_sandbox_settings_.get(), history_service_.get(), topics_site_data_manager_.get(), page_content_annotations_service_.get(), epochs, base::BindLambdaForTesting([&](EpochTopics epoch_topics) { result = std::move(epoch_topics); run_loop.Quit(); }), /*rand_uint64_queue=*/ base::queue{{100, 101, 102, 103, 104}}); run_loop.Run(); return result; } void AddHistoryEntries(const std::vector& hosts, base::Time time) { history::HistoryAddPageArgs add_page_args; add_page_args.time = time; add_page_args.context_id = reinterpret_cast(1); for (const std::string& host : hosts) { static int nav_entry_id = 0; ++nav_entry_id; add_page_args.url = GURL(base::StrCat({"https://", host})); add_page_args.nav_entry_id = nav_entry_id; history_service_->AddPage(add_page_args); history_service_->SetBrowsingTopicsAllowed( add_page_args.context_id, nav_entry_id, add_page_args.url); } task_environment_.RunUntilIdle(); } void AddApiUsageContextEntries( const std::vector>>& main_frame_hosts_with_context_domains) { for (auto& [main_frame_host, context_domains] : main_frame_hosts_with_context_domains) { topics_site_data_manager_->OnBrowsingTopicsApiUsed( HashMainFrameHostForStorage(main_frame_host), base::flat_set(context_domains.begin(), context_domains.end()), base::Time::Now()); } task_environment_.RunUntilIdle(); } std::vector TopicsAndWeight( const std::vector& topics, double weight) { std::vector result; for (int32_t topic : topics) { result.emplace_back( optimization_guide::WeightedIdentifier(topic, weight)); } return result; } void ExpectResultTopicsEqual( const std::vector& result, std::vector>> expected) { DCHECK_EQ(expected.size(), 5u); EXPECT_EQ(result.size(), 5u); for (int i = 0; i < 5; ++i) { EXPECT_EQ(result[i].topic(), expected[i].first); EXPECT_EQ(result[i].hashed_domains(), expected[i].second); } } protected: content::BrowserTaskEnvironment task_environment_; sync_preferences::TestingPrefServiceSyncable prefs_; scoped_refptr host_content_settings_map_; scoped_refptr cookie_settings_; std::unique_ptr privacy_sandbox_settings_; std::unique_ptr topics_site_data_manager_; std::unique_ptr history_service_; std::unique_ptr optimization_guide_model_provider_; std::unique_ptr page_content_annotations_service_; optimization_guide::TestPageContentAnnotator test_page_content_annotator_; base::ScopedTempDir temp_dir_; }; TEST_F(BrowsingTopicsCalculatorTest, PermissionDenied) { base::HistogramTester histograms; privacy_sandbox_settings_->SetPrivacySandboxEnabled(false); EpochTopics result = CalculateTopics(); EXPECT_TRUE(result.empty()); histograms.ExpectUniqueSample( "BrowsingTopics.EpochTopicsCalculation.CalculatorResultStatus", /*kFailurePermissionDenied*/ 1, /*expected_bucket_count=*/1); } TEST_F(BrowsingTopicsCalculatorTest, ApiUsageContextQueryError) { base::HistogramTester histograms; topics_site_data_manager_->SetQueryFailureOverride(); EpochTopics result = CalculateTopics(); EXPECT_TRUE(result.empty()); histograms.ExpectUniqueSample( "BrowsingTopics.EpochTopicsCalculation.CalculatorResultStatus", /*kFailureApiUsageContextQueryError*/ 2, /*expected_bucket_count=*/1); } TEST_F(BrowsingTopicsCalculatorTest, AnnotationExecutionError) { base::HistogramTester histograms; EpochTopics result = CalculateTopics(); EXPECT_TRUE(result.empty()); histograms.ExpectUniqueSample( "BrowsingTopics.EpochTopicsCalculation.CalculatorResultStatus", /*kFailureAnnotationExecutionError*/ 3, /*expected_bucket_count=*/1); } class BrowsingTopicsCalculatorUnsupporedTaxonomyVersionTest : public BrowsingTopicsCalculatorTest { public: BrowsingTopicsCalculatorUnsupporedTaxonomyVersionTest() { feature_list_.InitAndEnableFeatureWithParameters( blink::features::kBrowsingTopics, {{"taxonomy_version", "999"}}); } private: base::test::ScopedFeatureList feature_list_; }; TEST_F(BrowsingTopicsCalculatorUnsupporedTaxonomyVersionTest, TaxonomyVersionNotSupportedInBinary) { base::HistogramTester histograms; test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {}); EpochTopics result = CalculateTopics(); EXPECT_TRUE(result.empty()); histograms.ExpectUniqueSample( "BrowsingTopics.EpochTopicsCalculation.CalculatorResultStatus", /*kFailureTaxonomyVersionNotSupportedInBinary*/ 4, /*expected_bucket_count=*/1); } TEST_F(BrowsingTopicsCalculatorTest, TopicsMetadata) { base::HistogramTester histograms; base::Time begin_time = base::Time::Now(); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {}); EpochTopics result1 = CalculateTopics(); EXPECT_FALSE(result1.empty()); EXPECT_EQ(result1.taxonomy_size(), kTaxonomySize); EXPECT_EQ(result1.taxonomy_version(), kTaxonomyVersion); EXPECT_EQ(result1.model_version(), 1); EXPECT_EQ(result1.calculation_time(), begin_time); histograms.ExpectUniqueSample( "BrowsingTopics.EpochTopicsCalculation.CalculatorResultStatus", /*kSuccess*/ 0, /*expected_bucket_count=*/1); task_environment_.AdvanceClock(base::Seconds(2)); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(50).Build(), {}); EpochTopics result2 = CalculateTopics(); EXPECT_FALSE(result2.empty()); EXPECT_EQ(result2.taxonomy_size(), kTaxonomySize); EXPECT_EQ(result2.taxonomy_version(), kTaxonomyVersion); EXPECT_EQ(result2.model_version(), 50); EXPECT_EQ(result2.calculation_time(), begin_time + base::Seconds(2)); histograms.ExpectUniqueSample( "BrowsingTopics.EpochTopicsCalculation.CalculatorResultStatus", /*kSuccess*/ 0, /*expected_bucket_count=*/2); } TEST_F(BrowsingTopicsCalculatorTest, TopTopicsRankedByFrequency) { base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6}, begin_time); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 3, 4, 5, 6}, 0.1)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual(result.top_topics_and_observing_domains(), {{Topic(6), {}}, {Topic(5), {}}, {Topic(4), {}}, {Topic(3), {}}, {Topic(2), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 5u); } TEST_F(BrowsingTopicsCalculatorTest, ModelHasNoTopicsForHost) { base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6}, begin_time); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, {}}, {kHost2, {}}, {kHost3, {}}, {kHost4, {}}, {kHost5, {}}, {kHost6, {}}}); task_environment_.AdvanceClock(base::Seconds(1)); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual(result.top_topics_and_observing_domains(), {{Topic(101), {}}, {Topic(102), {}}, {Topic(103), {}}, {Topic(104), {}}, {Topic(105), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 0u); } TEST_F(BrowsingTopicsCalculatorTest, TopTopicsRankedByFrequency_AlsoAffectedByHostsCount) { base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost1, kHost1, kHost1, kHost1, kHost1, kHost1, kHost2, kHost3, kHost4, kHost5, kHost6}, begin_time); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2}, 0.1)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual(result.top_topics_and_observing_domains(), {{Topic(2), {}}, {Topic(1), {}}, {Topic(6), {}}, {Topic(5), {}}, {Topic(4), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 5u); } TEST_F(BrowsingTopicsCalculatorTest, TopTopicsRankingNotAffectedByAnnotationWeight) { base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6}, begin_time); // Setting the weight for Topic(1) and Topic(2) to 0.9. This weight shouldn't // affect the top topics ordering. test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2}, 0.9)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual(result.top_topics_and_observing_domains(), {{Topic(6), {}}, {Topic(5), {}}, {Topic(4), {}}, {Topic(3), {}}, {Topic(2), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 5u); } TEST_F(BrowsingTopicsCalculatorTest, AllTopTopicsRandomlyPadded) { test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 3, 4, 5, 6}, 0.1)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual(result.top_topics_and_observing_domains(), {{Topic(101), {}}, {Topic(102), {}}, {Topic(103), {}}, {Topic(104), {}}, {Topic(105), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 0u); } TEST_F(BrowsingTopicsCalculatorTest, TopTopicsPartiallyPadded) { base::HistogramTester histograms; base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost4, kHost5, kHost6}, begin_time); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 3, 4, 5, 6}, 0.1)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual(result.top_topics_and_observing_domains(), {{Topic(6), {}}, {Topic(5), {}}, {Topic(4), {}}, {Topic(101), {}}, {Topic(102), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 3u); } TEST_F(BrowsingTopicsCalculatorTest, CalculationResultUkm_FailedCalculation) { ukm::TestAutoSetUkmRecorder ukm_recorder; privacy_sandbox_settings_->SetPrivacySandboxEnabled(false); CalculateTopics(); auto entries = ukm_recorder.GetEntriesByName( ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::kEntryName); EXPECT_EQ(1u, entries.size()); EXPECT_FALSE(ukm_recorder.GetEntryMetric( entries.back(), ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: kTopTopic0Name)); } TEST_F(BrowsingTopicsCalculatorTest, CalculationResultUkm) { ukm::TestAutoSetUkmRecorder ukm_recorder; base::HistogramTester histograms; base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost4, kHost5, kHost6}, begin_time); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 3, 4, 5, 6}, 0.1)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); CalculateTopics(); auto entries = ukm_recorder.GetEntriesByName( ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::kEntryName); EXPECT_EQ(1u, entries.size()); ukm_recorder.ExpectEntryMetric( entries.back(), ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: kTopTopic0Name, 6); ukm_recorder.ExpectEntryMetric( entries.back(), ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: kTopTopic1Name, 5); ukm_recorder.ExpectEntryMetric( entries.back(), ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: kTopTopic2Name, 4); ukm_recorder.ExpectEntryMetric( entries.back(), ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: kTopTopic3Name, 101); ukm_recorder.ExpectEntryMetric( entries.back(), ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: kTopTopic4Name, 102); ukm_recorder.ExpectEntryMetric( entries.back(), ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: kTaxonomyVersionName, 1); ukm_recorder.ExpectEntryMetric( entries.back(), ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: kModelVersionName, 1); ukm_recorder.ExpectEntryMetric( entries.back(), ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: kPaddedTopicsStartIndexName, 3); } TEST_F(BrowsingTopicsCalculatorTest, TopTopicsAndObservingDomains) { base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6}, begin_time); AddApiUsageContextEntries( {{kHost1, {}}, {kHost2, {}}, {kHost3, {HashedDomain(2)}}, {kHost4, {HashedDomain(3)}}, {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}}); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 3, 4, 5, 6}, 0.1)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual( result.top_topics_and_observing_domains(), {{Topic(6), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}, {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}, {Topic(4), {HashedDomain(2), HashedDomain(3)}}, {Topic(3), {HashedDomain(2)}}, {Topic(2), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 5u); } TEST_F( BrowsingTopicsCalculatorTest, HistoryHostsBefore21DaysAgo_IgnoredForTopTopicsDecision_IgnoredForObservingDomainsDecision) { base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6}, begin_time - base::Days(21)); AddApiUsageContextEntries( {{kHost1, {}}, {kHost2, {}}, {kHost3, {HashedDomain(2)}}, {kHost4, {HashedDomain(3)}}, {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}}); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 103, 4, 5, 6}, 0.1)}, {kHost2, TopicsAndWeight({2, 103, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({103, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual(result.top_topics_and_observing_domains(), {{Topic(101), {}}, {Topic(102), {}}, {Topic(103), {}}, {Topic(104), {}}, {Topic(105), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 0u); } TEST_F( BrowsingTopicsCalculatorTest, HistoryHostsBetween7And21Days_IgnoredForTopTopicsDecision_ConsideredForObservingDomainsDecision) { base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6}, begin_time - base::Days(20)); AddApiUsageContextEntries( {{kHost1, {}}, {kHost2, {}}, {kHost3, {HashedDomain(2)}}, {kHost4, {HashedDomain(3)}}, {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}}); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 103, 4, 5, 6}, 0.1)}, {kHost2, TopicsAndWeight({2, 103, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({103, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual(result.top_topics_and_observing_domains(), {{Topic(101), {}}, {Topic(102), {}}, {Topic(103), {HashedDomain(2)}}, {Topic(104), {}}, {Topic(105), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 0u); } TEST_F(BrowsingTopicsCalculatorTest, DataQueryBoundedByTopicsDataAccessibleSince) { base::Time begin_time = base::Time::Now(); prefs_.SetTime(prefs::kPrivacySandboxTopicsDataAccessibleSince, begin_time + base::Days(6)); AddHistoryEntries({kHost1, kHost2}, begin_time); AddApiUsageContextEntries({{kHost1, {}}, {kHost2, {}}}); task_environment_.AdvanceClock(base::Days(6)); AddHistoryEntries({kHost3, kHost4, kHost5, kHost6}, begin_time + base::Days(6)); AddApiUsageContextEntries( {{kHost3, {HashedDomain(2)}}, {kHost4, {HashedDomain(3)}}, {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}}); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 3, 4, 5, 6}, 0.1)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual( result.top_topics_and_observing_domains(), {{Topic(6), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}, {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}, {Topic(4), {HashedDomain(2), HashedDomain(3)}}, {Topic(3), {HashedDomain(2)}}, {Topic(101), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 4u); } TEST_F(BrowsingTopicsCalculatorTest, HistoryDataBoundedByLastEpochCalculationTime) { base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost1, kHost2, kHost3}, begin_time); AddApiUsageContextEntries({{kHost3, {HashedDomain(5)}}}); task_environment_.AdvanceClock(base::Days(4)); AddHistoryEntries({kHost2, kHost3}, begin_time + base::Days(4)); AddApiUsageContextEntries({{kHost3, {HashedDomain(2)}}}); task_environment_.AdvanceClock(base::Days(2)); AddHistoryEntries({kHost3, kHost4, kHost5, kHost6}, begin_time + base::Days(6)); AddApiUsageContextEntries( {{kHost4, {HashedDomain(3)}}, {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}}); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 3, 4, 5, 6}, 0.1)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); base::circular_deque epochs; epochs.push_back(EpochTopics(begin_time + base::Days(6))); EpochTopics result = CalculateTopics(std::move(epochs)); // The topics are only from hosts since `begin_time + base::Days(6)`. The // observing domains are from data since `begin_time`. ExpectResultTopicsEqual( result.top_topics_and_observing_domains(), {{Topic(6), {HashedDomain(1), HashedDomain(2), HashedDomain(3), HashedDomain(5)}}, {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3), HashedDomain(5)}}, {Topic(4), {HashedDomain(2), HashedDomain(3), HashedDomain(5)}}, {Topic(3), {HashedDomain(2), HashedDomain(5)}}, {Topic(101), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 4u); } TEST_F(BrowsingTopicsCalculatorTest, HistoryDataAndApiUsageContextDataBoundedByPriorEpochsCalculationTime) { base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost1, kHost2, kHost3}, begin_time); AddApiUsageContextEntries({{kHost3, {HashedDomain(5)}}}); task_environment_.AdvanceClock(base::Days(4)); AddHistoryEntries({kHost2, kHost3}, begin_time + base::Days(4)); AddApiUsageContextEntries({{kHost3, {HashedDomain(2)}}}); task_environment_.AdvanceClock(base::Days(2)); AddHistoryEntries({kHost3, kHost4, kHost5, kHost6}, begin_time + base::Days(6)); AddApiUsageContextEntries( {{kHost4, {HashedDomain(3)}}, {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}}); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 3, 4, 5, 6}, 0.1)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); base::circular_deque epochs; epochs.push_back(EpochTopics(begin_time + base::Days(4))); epochs.push_back(EpochTopics(begin_time + base::Days(5))); epochs.push_back(EpochTopics(begin_time + base::Days(6))); EpochTopics result = CalculateTopics(std::move(epochs)); // The topics are only from hosts since `begin_time + base::Days(6)`. The // observing domains are from data since `begin_time + base::Days(4)`. ExpectResultTopicsEqual( result.top_topics_and_observing_domains(), {{Topic(6), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}, {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}, {Topic(4), {HashedDomain(2), HashedDomain(3)}}, {Topic(3), {HashedDomain(2)}}, {Topic(101), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 4u); } TEST_F(BrowsingTopicsCalculatorTest, TopTopicsAndObservingDomains_DomainsSizeExceedsLimit) { base::Time begin_time = base::Time::Now(); std::set large_size_domains; for (int i = 1; i <= 1001; ++i) { large_size_domains.insert(HashedDomain(i)); } AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6}, begin_time); AddApiUsageContextEntries({{kHost1, {}}, {kHost2, {}}, {kHost3, {HashedDomain(2)}}, {kHost4, {HashedDomain(3)}}, {kHost5, large_size_domains}}); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 3, 4, 5, 6}, 0.1)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); std::set expected_domains_after_capping = large_size_domains; expected_domains_after_capping.erase(HashedDomain(1)); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual(result.top_topics_and_observing_domains(), {{Topic(6), expected_domains_after_capping}, {Topic(5), expected_domains_after_capping}, {Topic(4), {HashedDomain(2), HashedDomain(3)}}, {Topic(3), {HashedDomain(2)}}, {Topic(2), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 5u); } TEST_F(BrowsingTopicsCalculatorTest, TopicBlocked) { base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6}, begin_time); AddApiUsageContextEntries( {{kHost1, {}}, {kHost2, {}}, {kHost3, {HashedDomain(2)}}, {kHost4, {HashedDomain(3)}}, {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}}); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 3, 4, 5, 6}, 0.1)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); privacy_sandbox_settings_->SetTopicAllowed( privacy_sandbox::CanonicalTopic(Topic(6), kTaxonomyVersion), /*allowed=*/false); privacy_sandbox_settings_->SetTopicAllowed( privacy_sandbox::CanonicalTopic(Topic(4), kTaxonomyVersion), /*allowed=*/false); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual( result.top_topics_and_observing_domains(), {{Topic(0), {}}, {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}, {Topic(0), {}}, {Topic(3), {HashedDomain(2)}}, {Topic(2), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 5u); } TEST_F(BrowsingTopicsCalculatorTest, PaddedTopicsDoNotDuplicate) { base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost4, kHost5, kHost6}, begin_time); AddApiUsageContextEntries( {{kHost1, {}}, {kHost2, {}}, {kHost3, {HashedDomain(2)}}, {kHost4, {HashedDomain(3)}}, {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}}); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 3, 4, 5, 102}, 0.1)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 102}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 102}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 102}, 0.1)}, {kHost5, TopicsAndWeight({5, 102}, 0.1)}, {kHost6, TopicsAndWeight({102}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual( result.top_topics_and_observing_domains(), {{Topic(102), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}, {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}, {Topic(4), {HashedDomain(3)}}, {Topic(101), {}}, {Topic(103), {}}}); } TEST_F(BrowsingTopicsCalculatorTest, Metrics) { base::HistogramTester histograms; base::Time begin_time = base::Time::Now(); AddHistoryEntries({kHost4, kHost5, kHost6}, begin_time); AddApiUsageContextEntries( {{kHost1, {}}, {kHost2, {}}, {kHost3, {HashedDomain(2)}}, {kHost4, {HashedDomain(3)}}, {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}}); test_page_content_annotator_.UsePageTopics( *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), {{kHost1, TopicsAndWeight({1, 2, 3, 4, 5, 6}, 0.1)}, {kHost2, TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, {kHost3, TopicsAndWeight({3, 4, 5, 6}, 0.1)}, {kHost4, TopicsAndWeight({4, 5, 6}, 0.1)}, {kHost5, TopicsAndWeight({5, 6}, 0.1)}, {kHost6, TopicsAndWeight({6}, 0.1)}}); task_environment_.AdvanceClock(base::Seconds(1)); EpochTopics result = CalculateTopics(); ExpectResultTopicsEqual( result.top_topics_and_observing_domains(), {{Topic(6), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}, {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}, {Topic(4), {HashedDomain(3)}}, {Topic(101), {}}, {Topic(102), {}}}); EXPECT_EQ(result.padded_top_topics_start_index(), 3u); histograms.ExpectUniqueSample( "BrowsingTopics.EpochTopicsCalculation.EligibleDistinctHistoryHostsCount", /*sample=*/3, /*expected_bucket_count=*/1); histograms.ExpectUniqueSample( "BrowsingTopics.EpochTopicsCalculation.TopTopicsCountBeforePadding", /*sample=*/3, /*expected_bucket_count=*/1); histograms.ExpectTotalCount( "BrowsingTopics.EpochTopicsCalculation." "ObservationContextDomainsCountPerTopTopic", /*count=*/5); histograms.ExpectBucketCount( "BrowsingTopics.EpochTopicsCalculation." "ObservationContextDomainsCountPerTopTopic", /*sample=*/0, /*expected_count=*/2); histograms.ExpectBucketCount( "BrowsingTopics.EpochTopicsCalculation." "ObservationContextDomainsCountPerTopTopic", /*sample=*/1, /*expected_count=*/1); histograms.ExpectBucketCount( "BrowsingTopics.EpochTopicsCalculation." "ObservationContextDomainsCountPerTopTopic", /*sample=*/3, /*expected_count=*/2); } } // namespace browsing_topics