1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/browsing_topics/browsing_topics_calculator.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/logging.h"
10 #include "base/strings/strcat.h"
11 #include "base/test/bind.h"
12 #include "base/test/gtest_util.h"
13 #include "base/test/metrics/histogram_tester.h"
14 #include "base/test/scoped_feature_list.h"
15 #include "components/browsing_topics/test_util.h"
16 #include "components/browsing_topics/util.h"
17 #include "components/content_settings/core/browser/cookie_settings.h"
18 #include "components/content_settings/core/browser/host_content_settings_map.h"
19 #include "components/history/core/browser/history_database_params.h"
20 #include "components/history/core/browser/history_service.h"
21 #include "components/history/core/test/test_history_database.h"
22 #include "components/optimization_guide/core/test_model_info_builder.h"
23 #include "components/privacy_sandbox/privacy_sandbox_prefs.h"
24 #include "components/privacy_sandbox/privacy_sandbox_settings_impl.h"
25 #include "components/privacy_sandbox/privacy_sandbox_test_util.h"
26 #include "components/privacy_sandbox/tracking_protection_settings.h"
27 #include "components/sync_preferences/testing_pref_service_syncable.h"
28 #include "components/ukm/test_ukm_recorder.h"
29 #include "content/public/test/browser_task_environment.h"
30 #include "content/public/test/browsing_topics_test_util.h"
31 #include "services/metrics/public/cpp/ukm_builders.h"
32 #include "third_party/blink/public/common/features.h"
34 namespace browsing_topics {
38 constexpr int kTaxonomyVersion = 1;
40 constexpr char kHost1[] = "www.foo1.com";
41 constexpr char kHost2[] = "www.foo2.com";
42 constexpr char kHost3[] = "www.foo3.com";
43 constexpr char kHost4[] = "www.foo4.com";
44 constexpr char kHost5[] = "www.foo5.com";
45 constexpr char kHost6[] = "www.foo6.com";
49 class BrowsingTopicsCalculatorTest : public testing::Test {
51 BrowsingTopicsCalculatorTest()
52 : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
53 EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
55 content_settings::CookieSettings::RegisterProfilePrefs(prefs_.registry());
56 HostContentSettingsMap::RegisterProfilePrefs(prefs_.registry());
57 privacy_sandbox::RegisterProfilePrefs(prefs_.registry());
59 host_content_settings_map_ = base::MakeRefCounted<HostContentSettingsMap>(
60 &prefs_, /*is_off_the_record=*/false, /*store_last_modified=*/false,
61 /*restore_session=*/false, /*should_record_metrics=*/false);
62 tracking_protection_settings_ =
63 std::make_unique<privacy_sandbox::TrackingProtectionSettings>(
65 /*onboarding_service=*/nullptr, /*is_incognito=*/false);
66 cookie_settings_ = base::MakeRefCounted<content_settings::CookieSettings>(
67 host_content_settings_map_.get(), &prefs_,
68 tracking_protection_settings_.get(), false, "chrome-extension");
69 auto privacy_sandbox_delegate = std::make_unique<
70 privacy_sandbox_test_util::MockPrivacySandboxSettingsDelegate>();
71 privacy_sandbox_delegate->SetUpIsPrivacySandboxRestrictedResponse(
72 /*restricted=*/false);
73 privacy_sandbox_delegate->SetUpIsIncognitoProfileResponse(
75 privacy_sandbox_settings_ =
76 std::make_unique<privacy_sandbox::PrivacySandboxSettingsImpl>(
77 std::move(privacy_sandbox_delegate),
78 host_content_settings_map_.get(), cookie_settings_,
79 tracking_protection_settings_.get(), &prefs_);
80 privacy_sandbox_settings_->SetAllPrivacySandboxAllowedForTesting();
82 topics_site_data_manager_ =
83 std::make_unique<content::TesterBrowsingTopicsSiteDataManager>(
86 history_service_ = std::make_unique<history::HistoryService>();
87 history_service_->Init(
88 history::TestHistoryDatabaseParamsForPath(temp_dir_.GetPath()));
90 task_environment_.RunUntilIdle();
93 ~BrowsingTopicsCalculatorTest() override {
94 cookie_settings_->ShutdownOnUIThread();
95 host_content_settings_map_->ShutdownOnUIThread();
96 tracking_protection_settings_->Shutdown();
99 EpochTopics CalculateTopics(base::circular_deque<EpochTopics> epochs = {}) {
100 EpochTopics result = EpochTopics(base::Time());
102 base::RunLoop run_loop;
104 TesterBrowsingTopicsCalculator topics_calculator =
105 TesterBrowsingTopicsCalculator(
106 privacy_sandbox_settings_.get(), history_service_.get(),
107 topics_site_data_manager_.get(), &test_annotator_, epochs,
108 base::BindLambdaForTesting([&](EpochTopics epoch_topics) {
109 result = std::move(epoch_topics);
112 /*rand_uint64_queue=*/
113 base::queue<uint64_t>{{100, 101, 102, 103, 104}});
120 void AddHistoryEntries(const std::vector<std::string>& hosts,
122 history::HistoryAddPageArgs add_page_args;
123 add_page_args.time = time;
124 add_page_args.context_id = 1;
126 for (const std::string& host : hosts) {
127 static int nav_entry_id = 0;
130 add_page_args.url = GURL(base::StrCat({"https://", host}));
131 add_page_args.nav_entry_id = nav_entry_id;
133 history_service_->AddPage(add_page_args);
134 history_service_->SetBrowsingTopicsAllowed(
135 add_page_args.context_id, nav_entry_id, add_page_args.url);
138 task_environment_.RunUntilIdle();
141 void AddApiUsageContextEntries(
142 const std::vector<std::pair<std::string, std::set<HashedDomain>>>&
143 main_frame_hosts_with_context_domains) {
144 for (auto& [main_frame_host, context_domains] :
145 main_frame_hosts_with_context_domains) {
146 for (const HashedDomain& context_domain : context_domains) {
147 topics_site_data_manager_->OnBrowsingTopicsApiUsed(
148 HashMainFrameHostForStorage(main_frame_host), context_domain,
149 base::NumberToString(context_domain.value()), base::Time::Now());
153 task_environment_.RunUntilIdle();
156 void ExpectResultTopicsEqual(
157 const std::vector<TopicAndDomains>& result,
158 std::vector<std::pair<Topic, std::set<HashedDomain>>> expected) {
159 DCHECK_EQ(expected.size(), 5u);
160 EXPECT_EQ(result.size(), 5u);
162 for (int i = 0; i < 5; ++i) {
163 EXPECT_EQ(result[i].topic(), expected[i].first);
164 EXPECT_EQ(result[i].hashed_domains(), expected[i].second);
169 content::BrowserTaskEnvironment task_environment_;
171 sync_preferences::TestingPrefServiceSyncable prefs_;
172 scoped_refptr<HostContentSettingsMap> host_content_settings_map_;
173 scoped_refptr<content_settings::CookieSettings> cookie_settings_;
174 std::unique_ptr<privacy_sandbox::TrackingProtectionSettings>
175 tracking_protection_settings_;
176 std::unique_ptr<privacy_sandbox::PrivacySandboxSettings>
177 privacy_sandbox_settings_;
178 TestAnnotator test_annotator_;
180 std::unique_ptr<content::TesterBrowsingTopicsSiteDataManager>
181 topics_site_data_manager_;
183 std::unique_ptr<history::HistoryService> history_service_;
185 base::ScopedTempDir temp_dir_;
188 TEST_F(BrowsingTopicsCalculatorTest, PermissionDenied) {
189 base::HistogramTester histograms;
191 privacy_sandbox_settings_->SetTopicsBlockedForTesting();
193 EpochTopics result = CalculateTopics();
194 EXPECT_TRUE(result.empty());
196 histograms.ExpectUniqueSample(
197 "BrowsingTopics.EpochTopicsCalculation.CalculatorResultStatus",
198 /*kFailurePermissionDenied*/ 1,
199 /*expected_bucket_count=*/1);
202 TEST_F(BrowsingTopicsCalculatorTest, ApiUsageContextQueryError) {
203 base::HistogramTester histograms;
205 topics_site_data_manager_->SetQueryFailureOverride();
207 EpochTopics result = CalculateTopics();
208 EXPECT_TRUE(result.empty());
210 histograms.ExpectUniqueSample(
211 "BrowsingTopics.EpochTopicsCalculation.CalculatorResultStatus",
212 /*kFailureApiUsageContextQueryError*/ 2,
213 /*expected_bucket_count=*/1);
216 TEST_F(BrowsingTopicsCalculatorTest, AnnotationExecutionError) {
217 base::HistogramTester histograms;
219 EpochTopics result = CalculateTopics();
220 EXPECT_TRUE(result.empty());
222 histograms.ExpectUniqueSample(
223 "BrowsingTopics.EpochTopicsCalculation.CalculatorResultStatus",
224 /*kFailureAnnotationExecutionError*/ 3,
225 /*expected_bucket_count=*/1);
228 class BrowsingTopicsCalculatorUnsupporedTaxonomyVersionTest
229 : public BrowsingTopicsCalculatorTest {
231 BrowsingTopicsCalculatorUnsupporedTaxonomyVersionTest() {
232 feature_list_.InitAndEnableFeatureWithParameters(
233 blink::features::kBrowsingTopicsParameters,
234 {{"taxonomy_version", "999"}});
238 base::test::ScopedFeatureList feature_list_;
241 TEST_F(BrowsingTopicsCalculatorUnsupporedTaxonomyVersionTest,
242 TaxonomyVersionNotSupportedInBinary) {
243 base::HistogramTester histograms;
245 test_annotator_.UseModelInfo(
246 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
248 EpochTopics result = CalculateTopics();
249 EXPECT_TRUE(result.empty());
251 histograms.ExpectUniqueSample(
252 "BrowsingTopics.EpochTopicsCalculation.CalculatorResultStatus",
253 /*kFailureTaxonomyVersionNotSupportedInBinary*/ 4,
254 /*expected_bucket_count=*/1);
257 TEST_F(BrowsingTopicsCalculatorTest, TopicsMetadata) {
258 base::HistogramTester histograms;
259 base::Time begin_time = base::Time::Now();
261 test_annotator_.UseModelInfo(
262 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
264 EpochTopics result1 = CalculateTopics();
265 EXPECT_FALSE(result1.empty());
266 EXPECT_EQ(result1.taxonomy_version(), kTaxonomyVersion);
267 EXPECT_EQ(result1.model_version(), 1);
268 EXPECT_EQ(result1.calculation_time(), begin_time);
270 histograms.ExpectUniqueSample(
271 "BrowsingTopics.EpochTopicsCalculation.CalculatorResultStatus",
273 /*expected_bucket_count=*/1);
275 task_environment_.AdvanceClock(base::Seconds(2));
277 test_annotator_.UseModelInfo(
278 *optimization_guide::TestModelInfoBuilder().SetVersion(50).Build());
280 EpochTopics result2 = CalculateTopics();
281 EXPECT_FALSE(result2.empty());
283 EXPECT_EQ(result2.taxonomy_version(), kTaxonomyVersion);
284 EXPECT_EQ(result2.model_version(), 50);
285 EXPECT_EQ(result2.calculation_time(), begin_time + base::Seconds(2));
287 histograms.ExpectUniqueSample(
288 "BrowsingTopics.EpochTopicsCalculation.CalculatorResultStatus",
290 /*expected_bucket_count=*/2);
293 // Regression test for crbug/1495959.
294 TEST_F(BrowsingTopicsCalculatorTest, ModelAvailableAfterDelay) {
295 test_annotator_.SetModelAvailable(false);
297 base::Time begin_time = base::Time::Now();
299 AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6},
302 task_environment_.AdvanceClock(base::Seconds(1));
304 // This PostTask will run when the |CalculateTopics| run loop starts and will
305 // signal to the calculator that the model is ready, triggering it to start.
306 task_environment_.GetMainThreadTaskRunner()->PostTask(
309 [](TestAnnotator* annotator) {
310 annotator->UseModelInfo(*optimization_guide::TestModelInfoBuilder()
313 annotator->UseAnnotations({
314 {kHost1, {1, 2, 3, 4, 5, 6}},
315 {kHost2, {2, 3, 4, 5, 6}},
316 {kHost3, {3, 4, 5, 6}},
321 annotator->SetModelAvailable(true);
325 EpochTopics result = CalculateTopics();
326 ExpectResultTopicsEqual(result.top_topics_and_observing_domains(),
333 EXPECT_EQ(result.padded_top_topics_start_index(), 5u);
336 TEST_F(BrowsingTopicsCalculatorTest, TopTopicsRankedByFrequency) {
337 base::Time begin_time = base::Time::Now();
339 AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6},
342 test_annotator_.UseModelInfo(
343 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
344 test_annotator_.UseAnnotations({
345 {kHost1, {1, 2, 3, 4, 5, 6}},
346 {kHost2, {2, 3, 4, 5, 6}},
347 {kHost3, {3, 4, 5, 6}},
353 task_environment_.AdvanceClock(base::Seconds(1));
355 EpochTopics result = CalculateTopics();
356 ExpectResultTopicsEqual(result.top_topics_and_observing_domains(),
363 EXPECT_EQ(result.padded_top_topics_start_index(), 5u);
366 TEST_F(BrowsingTopicsCalculatorTest, ModelHasNoTopicsForHost) {
367 base::Time begin_time = base::Time::Now();
369 AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6},
372 test_annotator_.UseModelInfo(
373 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
375 task_environment_.AdvanceClock(base::Seconds(1));
377 EpochTopics result = CalculateTopics();
378 ExpectResultTopicsEqual(result.top_topics_and_observing_domains(),
385 EXPECT_EQ(result.padded_top_topics_start_index(), 0u);
388 TEST_F(BrowsingTopicsCalculatorTest,
389 TopTopicsRankedByFrequency_AlsoAffectedByHostsCount) {
390 base::Time begin_time = base::Time::Now();
392 AddHistoryEntries({kHost1, kHost1, kHost1, kHost1, kHost1, kHost1, kHost2,
393 kHost3, kHost4, kHost5, kHost6},
396 test_annotator_.UseModelInfo(
397 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
398 test_annotator_.UseAnnotations({
400 {kHost2, {2, 3, 4, 5, 6}},
401 {kHost3, {3, 4, 5, 6}},
407 task_environment_.AdvanceClock(base::Seconds(1));
409 EpochTopics result = CalculateTopics();
410 ExpectResultTopicsEqual(result.top_topics_and_observing_domains(),
417 EXPECT_EQ(result.padded_top_topics_start_index(), 5u);
420 TEST_F(BrowsingTopicsCalculatorTest, AllTopTopicsRandomlyPadded) {
421 test_annotator_.UseModelInfo(
422 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
423 test_annotator_.UseAnnotations({
424 {kHost1, {1, 2, 3, 4, 5, 6}},
425 {kHost2, {2, 3, 4, 5, 6}},
426 {kHost3, {3, 4, 5, 6}},
432 EpochTopics result = CalculateTopics();
433 ExpectResultTopicsEqual(result.top_topics_and_observing_domains(),
440 EXPECT_EQ(result.padded_top_topics_start_index(), 0u);
443 TEST_F(BrowsingTopicsCalculatorTest, TopTopicsPartiallyPadded) {
444 base::HistogramTester histograms;
446 base::Time begin_time = base::Time::Now();
448 AddHistoryEntries({kHost4, kHost5, kHost6}, begin_time);
450 test_annotator_.UseModelInfo(
451 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
452 test_annotator_.UseAnnotations({
453 {kHost1, {1, 2, 3, 4, 5, 6}},
454 {kHost2, {2, 3, 4, 5, 6}},
455 {kHost3, {3, 4, 5, 6}},
461 task_environment_.AdvanceClock(base::Seconds(1));
463 EpochTopics result = CalculateTopics();
464 ExpectResultTopicsEqual(result.top_topics_and_observing_domains(),
471 EXPECT_EQ(result.padded_top_topics_start_index(), 3u);
474 TEST_F(BrowsingTopicsCalculatorTest, CalculationResultUkm_FailedCalculation) {
475 ukm::TestAutoSetUkmRecorder ukm_recorder;
476 privacy_sandbox_settings_->SetTopicsBlockedForTesting();
480 auto entries = ukm_recorder.GetEntriesByName(
481 ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::kEntryName);
482 EXPECT_EQ(1u, entries.size());
484 EXPECT_FALSE(ukm_recorder.GetEntryMetric(
486 ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::
490 TEST_F(BrowsingTopicsCalculatorTest, CalculationResultUkm) {
491 ukm::TestAutoSetUkmRecorder ukm_recorder;
492 base::HistogramTester histograms;
494 base::Time begin_time = base::Time::Now();
496 AddHistoryEntries({kHost4, kHost5, kHost6}, begin_time);
498 test_annotator_.UseModelInfo(
499 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
500 test_annotator_.UseAnnotations({
501 {kHost1, {1, 2, 3, 4, 5, 6}},
502 {kHost2, {2, 3, 4, 5, 6}},
503 {kHost3, {3, 4, 5, 6}},
509 task_environment_.AdvanceClock(base::Seconds(1));
513 auto entries = ukm_recorder.GetEntriesByName(
514 ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::kEntryName);
515 EXPECT_EQ(1u, entries.size());
517 ukm_recorder.ExpectEntryMetric(
519 ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::
522 ukm_recorder.ExpectEntryMetric(
524 ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::
527 ukm_recorder.ExpectEntryMetric(
529 ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::
532 ukm_recorder.ExpectEntryMetric(
534 ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::
537 ukm_recorder.ExpectEntryMetric(
539 ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::
542 ukm_recorder.ExpectEntryMetric(
544 ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::
545 kTaxonomyVersionName,
547 ukm_recorder.ExpectEntryMetric(
549 ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::
552 ukm_recorder.ExpectEntryMetric(
554 ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::
555 kPaddedTopicsStartIndexName,
559 TEST_F(BrowsingTopicsCalculatorTest, TopTopicsAndObservingDomains) {
560 base::Time begin_time = base::Time::Now();
562 AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6},
565 AddApiUsageContextEntries(
568 {kHost3, {HashedDomain(2)}},
569 {kHost4, {HashedDomain(3)}},
570 {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}});
572 test_annotator_.UseModelInfo(
573 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
574 test_annotator_.UseAnnotations({
575 {kHost1, {1, 2, 3, 4, 5, 6}},
576 {kHost2, {2, 3, 4, 5, 6}},
577 {kHost3, {3, 4, 5, 6}},
583 task_environment_.AdvanceClock(base::Seconds(1));
585 EpochTopics result = CalculateTopics();
586 ExpectResultTopicsEqual(
587 result.top_topics_and_observing_domains(),
588 {{Topic(6), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
589 {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
590 {Topic(4), {HashedDomain(2), HashedDomain(3)}},
591 {Topic(3), {HashedDomain(2)}},
594 EXPECT_EQ(result.padded_top_topics_start_index(), 5u);
598 BrowsingTopicsCalculatorTest,
599 HistoryHostsBefore21DaysAgo_IgnoredForTopTopicsDecision_IgnoredForObservingDomainsDecision) {
600 base::Time begin_time = base::Time::Now();
602 AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6},
603 begin_time - base::Days(21));
605 AddApiUsageContextEntries(
608 {kHost3, {HashedDomain(2)}},
609 {kHost4, {HashedDomain(3)}},
610 {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}});
612 test_annotator_.UseModelInfo(
613 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
614 test_annotator_.UseAnnotations({
615 {kHost1, {1, 2, 103, 4, 5, 6}},
616 {kHost2, {2, 103, 4, 5, 6}},
617 {kHost3, {103, 4, 5, 6}},
623 task_environment_.AdvanceClock(base::Seconds(1));
625 EpochTopics result = CalculateTopics();
626 ExpectResultTopicsEqual(result.top_topics_and_observing_domains(),
633 EXPECT_EQ(result.padded_top_topics_start_index(), 0u);
637 BrowsingTopicsCalculatorTest,
638 HistoryHostsBetween7And21Days_IgnoredForTopTopicsDecision_ConsideredForObservingDomainsDecision) {
639 base::Time begin_time = base::Time::Now();
641 AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6},
642 begin_time - base::Days(20));
644 AddApiUsageContextEntries(
647 {kHost3, {HashedDomain(2)}},
648 {kHost4, {HashedDomain(3)}},
649 {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}});
651 test_annotator_.UseModelInfo(
652 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
653 test_annotator_.UseAnnotations({
654 {kHost1, {1, 2, 103, 4, 5, 6}},
655 {kHost2, {2, 103, 4, 5, 6}},
656 {kHost3, {103, 4, 5, 6}},
662 task_environment_.AdvanceClock(base::Seconds(1));
664 EpochTopics result = CalculateTopics();
665 ExpectResultTopicsEqual(result.top_topics_and_observing_domains(),
668 {Topic(103), {HashedDomain(2)}},
672 EXPECT_EQ(result.padded_top_topics_start_index(), 0u);
675 TEST_F(BrowsingTopicsCalculatorTest,
676 DataQueryBoundedByTopicsDataAccessibleSince) {
677 base::Time begin_time = base::Time::Now();
679 prefs_.SetTime(prefs::kPrivacySandboxTopicsDataAccessibleSince,
680 begin_time + base::Days(6));
682 AddHistoryEntries({kHost1, kHost2}, begin_time);
683 AddApiUsageContextEntries({{kHost1, {}}, {kHost2, {}}});
685 task_environment_.AdvanceClock(base::Days(6));
687 AddHistoryEntries({kHost3, kHost4, kHost5, kHost6},
688 begin_time + base::Days(6));
689 AddApiUsageContextEntries(
690 {{kHost3, {HashedDomain(2)}},
691 {kHost4, {HashedDomain(3)}},
692 {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}});
694 test_annotator_.UseModelInfo(
695 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
696 test_annotator_.UseAnnotations({
697 {kHost1, {1, 2, 3, 4, 5, 6}},
698 {kHost2, {2, 3, 4, 5, 6}},
699 {kHost3, {3, 4, 5, 6}},
705 task_environment_.AdvanceClock(base::Seconds(1));
707 EpochTopics result = CalculateTopics();
708 ExpectResultTopicsEqual(
709 result.top_topics_and_observing_domains(),
710 {{Topic(6), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
711 {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
712 {Topic(4), {HashedDomain(2), HashedDomain(3)}},
713 {Topic(3), {HashedDomain(2)}},
716 EXPECT_EQ(result.padded_top_topics_start_index(), 4u);
719 TEST_F(BrowsingTopicsCalculatorTest,
720 HistoryDataBoundedByLastEpochCalculationTime) {
721 base::Time begin_time = base::Time::Now();
722 AddHistoryEntries({kHost1, kHost2, kHost3}, begin_time);
723 AddApiUsageContextEntries({{kHost3, {HashedDomain(5)}}});
725 task_environment_.AdvanceClock(base::Days(4));
726 AddHistoryEntries({kHost2, kHost3}, begin_time + base::Days(4));
727 AddApiUsageContextEntries({{kHost3, {HashedDomain(2)}}});
729 task_environment_.AdvanceClock(base::Days(2));
730 AddHistoryEntries({kHost3, kHost4, kHost5, kHost6},
731 begin_time + base::Days(6));
732 AddApiUsageContextEntries(
733 {{kHost4, {HashedDomain(3)}},
734 {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}});
736 test_annotator_.UseModelInfo(
737 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
738 test_annotator_.UseAnnotations({
739 {kHost1, {1, 2, 3, 4, 5, 6}},
740 {kHost2, {2, 3, 4, 5, 6}},
741 {kHost3, {3, 4, 5, 6}},
747 task_environment_.AdvanceClock(base::Seconds(1));
749 base::circular_deque<EpochTopics> epochs;
750 epochs.push_back(EpochTopics(begin_time + base::Days(6)));
752 EpochTopics result = CalculateTopics(std::move(epochs));
754 // The topics are only from hosts since `begin_time + base::Days(6)`. The
755 // observing domains are from data since `begin_time`.
756 ExpectResultTopicsEqual(
757 result.top_topics_and_observing_domains(),
759 {HashedDomain(1), HashedDomain(2), HashedDomain(3), HashedDomain(5)}},
761 {HashedDomain(1), HashedDomain(2), HashedDomain(3), HashedDomain(5)}},
762 {Topic(4), {HashedDomain(2), HashedDomain(3), HashedDomain(5)}},
763 {Topic(3), {HashedDomain(2), HashedDomain(5)}},
766 EXPECT_EQ(result.padded_top_topics_start_index(), 4u);
769 TEST_F(BrowsingTopicsCalculatorTest,
770 HistoryDataAndApiUsageContextDataBoundedByPriorEpochsCalculationTime) {
771 base::Time begin_time = base::Time::Now();
772 AddHistoryEntries({kHost1, kHost2, kHost3}, begin_time);
773 AddApiUsageContextEntries({{kHost3, {HashedDomain(5)}}});
775 task_environment_.AdvanceClock(base::Days(4));
776 AddHistoryEntries({kHost2, kHost3}, begin_time + base::Days(4));
777 AddApiUsageContextEntries({{kHost3, {HashedDomain(2)}}});
779 task_environment_.AdvanceClock(base::Days(2));
780 AddHistoryEntries({kHost3, kHost4, kHost5, kHost6},
781 begin_time + base::Days(6));
782 AddApiUsageContextEntries(
783 {{kHost4, {HashedDomain(3)}},
784 {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}});
786 test_annotator_.UseModelInfo(
787 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
788 test_annotator_.UseAnnotations({
789 {kHost1, {1, 2, 3, 4, 5, 6}},
790 {kHost2, {2, 3, 4, 5, 6}},
791 {kHost3, {3, 4, 5, 6}},
797 task_environment_.AdvanceClock(base::Seconds(1));
799 base::circular_deque<EpochTopics> epochs;
800 epochs.push_back(EpochTopics(begin_time + base::Days(4)));
801 epochs.push_back(EpochTopics(begin_time + base::Days(5)));
802 epochs.push_back(EpochTopics(begin_time + base::Days(6)));
804 EpochTopics result = CalculateTopics(std::move(epochs));
806 // The topics are only from hosts since `begin_time + base::Days(6)`. The
807 // observing domains are from data since `begin_time + base::Days(4)`.
808 ExpectResultTopicsEqual(
809 result.top_topics_and_observing_domains(),
810 {{Topic(6), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
811 {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
812 {Topic(4), {HashedDomain(2), HashedDomain(3)}},
813 {Topic(3), {HashedDomain(2)}},
816 EXPECT_EQ(result.padded_top_topics_start_index(), 4u);
819 TEST_F(BrowsingTopicsCalculatorTest,
820 TopTopicsAndObservingDomains_DomainsSizeExceedsLimit) {
821 base::Time begin_time = base::Time::Now();
823 std::set<HashedDomain> large_size_domains;
824 for (int i = 1; i <= 1001; ++i) {
825 large_size_domains.insert(HashedDomain(i));
828 AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6},
831 AddApiUsageContextEntries({{kHost1, {}},
833 {kHost3, {HashedDomain(2)}},
834 {kHost4, {HashedDomain(3)}},
835 {kHost5, large_size_domains}});
837 test_annotator_.UseModelInfo(
838 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
839 test_annotator_.UseAnnotations({
840 {kHost1, {1, 2, 3, 4, 5, 6}},
841 {kHost2, {2, 3, 4, 5, 6}},
842 {kHost3, {3, 4, 5, 6}},
848 task_environment_.AdvanceClock(base::Seconds(1));
850 std::set<HashedDomain> expected_domains_after_capping = large_size_domains;
851 expected_domains_after_capping.erase(HashedDomain(1));
853 EpochTopics result = CalculateTopics();
854 ExpectResultTopicsEqual(result.top_topics_and_observing_domains(),
855 {{Topic(6), expected_domains_after_capping},
856 {Topic(5), expected_domains_after_capping},
857 {Topic(4), {HashedDomain(2), HashedDomain(3)}},
858 {Topic(3), {HashedDomain(2)}},
861 EXPECT_EQ(result.padded_top_topics_start_index(), 5u);
864 TEST_F(BrowsingTopicsCalculatorTest, TopicBlocked) {
865 base::Time begin_time = base::Time::Now();
867 AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6},
870 AddApiUsageContextEntries(
873 {kHost3, {HashedDomain(2)}},
874 {kHost4, {HashedDomain(3)}},
875 {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}});
877 test_annotator_.UseModelInfo(
878 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
879 test_annotator_.UseAnnotations({
880 {kHost1, {1, 2, 3, 4, 5, 6}},
881 {kHost2, {2, 3, 4, 5, 6}},
882 {kHost3, {3, 4, 5, 6}},
888 task_environment_.AdvanceClock(base::Seconds(1));
890 privacy_sandbox_settings_->SetTopicAllowed(
891 privacy_sandbox::CanonicalTopic(Topic(6), kTaxonomyVersion),
893 privacy_sandbox_settings_->SetTopicAllowed(
894 privacy_sandbox::CanonicalTopic(Topic(4), kTaxonomyVersion),
897 EpochTopics result = CalculateTopics();
898 ExpectResultTopicsEqual(
899 result.top_topics_and_observing_domains(),
901 {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
903 {Topic(3), {HashedDomain(2)}},
906 EXPECT_EQ(result.padded_top_topics_start_index(), 5u);
909 TEST_F(BrowsingTopicsCalculatorTest, TopicBlockedByFinch) {
910 base::Time begin_time = base::Time::Now();
912 AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6},
915 AddApiUsageContextEntries(
918 {kHost3, {HashedDomain(2)}},
919 {kHost4, {HashedDomain(3)}},
920 {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}});
922 test_annotator_.UseModelInfo(
923 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
924 test_annotator_.UseAnnotations({
925 {kHost1, {1, 2, 3, 4, 5, 6}},
926 {kHost2, {2, 3, 4, 5, 6}},
927 {kHost3, {3, 4, 5, 6}},
933 task_environment_.AdvanceClock(base::Seconds(1));
935 base::test::ScopedFeatureList feature_list;
936 feature_list.InitAndEnableFeatureWithParameters(
937 blink::features::kBrowsingTopicsParameters,
938 {{"disabled_topics_list", "6,4"}});
940 EpochTopics result = CalculateTopics();
941 ExpectResultTopicsEqual(
942 result.top_topics_and_observing_domains(),
944 {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
946 {Topic(3), {HashedDomain(2)}},
949 EXPECT_EQ(result.padded_top_topics_start_index(), 5u);
952 TEST_F(BrowsingTopicsCalculatorTest, TopicsPrioritizedByFinch) {
953 base::Time begin_time = base::Time::Now();
955 AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6},
958 AddApiUsageContextEntries(
961 {kHost3, {HashedDomain(2)}},
962 {kHost4, {HashedDomain(3)}},
963 {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}});
965 test_annotator_.UseModelInfo(
966 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
967 test_annotator_.UseAnnotations({
968 {kHost1, {74, 2, 3, 4, 5, 6}},
969 {kHost2, {2, 3, 4, 5, 6}},
970 {kHost3, {3, 4, 5, 6}},
976 task_environment_.AdvanceClock(base::Seconds(1));
978 base::test::ScopedFeatureList feature_list;
979 feature_list.InitAndEnableFeatureWithParameters(
980 blink::features::kBrowsingTopicsParameters,
981 {{"prioritized_topics_list", "4,57"}}); // 74 is descended from 57.
983 EpochTopics result = CalculateTopics();
984 ExpectResultTopicsEqual(
985 result.top_topics_and_observing_domains(),
986 {{Topic(4), {HashedDomain(2), HashedDomain(3)}},
988 {Topic(6), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
989 {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
990 {Topic(3), {HashedDomain(2)}}});
992 EXPECT_EQ(result.padded_top_topics_start_index(), 5u);
993 EXPECT_EQ(result.config_version(), 2);
996 TEST_F(BrowsingTopicsCalculatorTest, PaddedTopicsDoNotDuplicate) {
997 base::Time begin_time = base::Time::Now();
999 AddHistoryEntries({kHost4, kHost5, kHost6}, begin_time);
1001 AddApiUsageContextEntries(
1004 {kHost3, {HashedDomain(2)}},
1005 {kHost4, {HashedDomain(3)}},
1006 {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}});
1008 test_annotator_.UseModelInfo(
1009 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
1010 test_annotator_.UseAnnotations({
1011 {kHost1, {1, 2, 3, 4, 5, 102}},
1012 {kHost2, {2, 3, 4, 5, 102}},
1013 {kHost3, {3, 4, 5, 102}},
1014 {kHost4, {4, 5, 102}},
1019 task_environment_.AdvanceClock(base::Seconds(1));
1021 EpochTopics result = CalculateTopics();
1022 ExpectResultTopicsEqual(
1023 result.top_topics_and_observing_domains(),
1024 {{Topic(102), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
1025 {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
1026 {Topic(4), {HashedDomain(3)}},
1031 TEST_F(BrowsingTopicsCalculatorTest, Metrics_LessThan5HistoryTopics) {
1032 base::HistogramTester histograms;
1034 base::Time begin_time = base::Time::Now();
1036 AddHistoryEntries({kHost4, kHost5, kHost6}, begin_time);
1038 AddApiUsageContextEntries(
1041 {kHost3, {HashedDomain(2)}},
1042 {kHost4, {HashedDomain(3)}},
1043 {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}});
1045 test_annotator_.UseModelInfo(
1046 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
1047 test_annotator_.UseAnnotations({
1048 {kHost1, {1, 2, 3, 4, 5, 6}},
1049 {kHost2, {2, 3, 4, 5, 6}},
1050 {kHost3, {3, 4, 5, 6}},
1051 {kHost4, {4, 5, 6}},
1056 task_environment_.AdvanceClock(base::Seconds(1));
1058 EpochTopics result = CalculateTopics();
1059 ExpectResultTopicsEqual(
1060 result.top_topics_and_observing_domains(),
1061 {{Topic(6), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
1062 {Topic(5), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
1063 {Topic(4), {HashedDomain(3)}},
1067 EXPECT_EQ(result.padded_top_topics_start_index(), 3u);
1069 histograms.ExpectUniqueSample(
1070 "BrowsingTopics.EpochTopicsCalculation.EligibleDistinctHistoryHostsCount",
1072 /*expected_bucket_count=*/1);
1074 histograms.ExpectUniqueSample(
1075 "BrowsingTopics.EpochTopicsCalculation.HistoryTopicsCount",
1077 /*expected_bucket_count=*/1);
1079 histograms.ExpectUniqueSample(
1080 "BrowsingTopics.EpochTopicsCalculation.TopTopicsCountBeforePadding",
1082 /*expected_bucket_count=*/1);
1084 histograms.ExpectTotalCount(
1085 "BrowsingTopics.EpochTopicsCalculation."
1086 "ObservationContextDomainsCountPerTopTopic",
1088 histograms.ExpectBucketCount(
1089 "BrowsingTopics.EpochTopicsCalculation."
1090 "ObservationContextDomainsCountPerTopTopic",
1092 /*expected_count=*/2);
1093 histograms.ExpectBucketCount(
1094 "BrowsingTopics.EpochTopicsCalculation."
1095 "ObservationContextDomainsCountPerTopTopic",
1097 /*expected_count=*/1);
1098 histograms.ExpectBucketCount(
1099 "BrowsingTopics.EpochTopicsCalculation."
1100 "ObservationContextDomainsCountPerTopTopic",
1102 /*expected_count=*/2);
1105 TEST_F(BrowsingTopicsCalculatorTest, Metrics_MoreThan5HistoryTopics) {
1106 base::HistogramTester histograms;
1108 base::Time begin_time = base::Time::Now();
1110 AddHistoryEntries({kHost1, kHost2, kHost3, kHost4, kHost5, kHost6},
1113 AddApiUsageContextEntries(
1116 {kHost3, {HashedDomain(2)}},
1117 {kHost4, {HashedDomain(3)}},
1118 {kHost5, {HashedDomain(1), HashedDomain(2), HashedDomain(3)}}});
1120 test_annotator_.UseModelInfo(
1121 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
1122 test_annotator_.UseAnnotations({
1123 {kHost1, {1, 2, 3, 4, 5, 6}},
1124 {kHost2, {2, 3, 4, 5, 6}},
1125 {kHost3, {3, 4, 5, 6}},
1126 {kHost4, {4, 5, 6}},
1131 task_environment_.AdvanceClock(base::Seconds(1));
1133 EpochTopics result = CalculateTopics();
1135 EXPECT_EQ(result.padded_top_topics_start_index(), 5u);
1137 histograms.ExpectUniqueSample(
1138 "BrowsingTopics.EpochTopicsCalculation.HistoryTopicsCount",
1140 /*expected_bucket_count=*/1);
1142 histograms.ExpectUniqueSample(
1143 "BrowsingTopics.EpochTopicsCalculation.TopTopicsCountBeforePadding",
1145 /*expected_bucket_count=*/1);
1148 TEST_F(BrowsingTopicsCalculatorTest, NoDescendantTopics) {
1149 base::Time begin_time = base::Time::Now();
1160 AddApiUsageContextEntries({{kHost1, {HashedDomain(1)}},
1161 {kHost2, {HashedDomain(2)}},
1162 {kHost3, {HashedDomain(3)}},
1163 {kHost4, {HashedDomain(4)}},
1164 {kHost5, {HashedDomain(5)}}});
1166 test_annotator_.UseModelInfo(
1167 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
1168 test_annotator_.UseAnnotations({
1169 {kHost1, {2, 3, 4, 5, 6}},
1170 {kHost2, {3, 4, 5, 6}},
1171 {kHost3, {4, 5, 6}},
1176 task_environment_.AdvanceClock(base::Seconds(1));
1178 EpochTopics result = CalculateTopics();
1179 std::vector<std::pair<Topic, std::set<HashedDomain>>>
1180 expected_top_topics_and_observing_domains = {
1182 {HashedDomain(1), HashedDomain(2), HashedDomain(3), HashedDomain(4),
1185 {HashedDomain(1), HashedDomain(2), HashedDomain(3),
1187 {Topic(4), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
1188 {Topic(3), {HashedDomain(1), HashedDomain(2)}},
1189 {Topic(2), {HashedDomain(1)}}};
1190 ExpectResultTopicsEqual(result.top_topics_and_observing_domains(),
1191 expected_top_topics_and_observing_domains);
1194 TEST_F(BrowsingTopicsCalculatorTest, DescendantTopicIsBlocked) {
1195 base::Time begin_time = base::Time::Now();
1206 AddApiUsageContextEntries({{kHost1, {HashedDomain(1)}},
1207 {kHost2, {HashedDomain(2)}},
1208 {kHost3, {HashedDomain(3)}},
1209 {kHost4, {HashedDomain(4)}},
1210 {kHost5, {HashedDomain(5)}}});
1212 test_annotator_.UseModelInfo(
1213 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
1214 // 1 is the parent topic of 2-5.
1215 test_annotator_.UseAnnotations({
1216 {kHost1, {1, 2, 3, 4, 5}},
1217 {kHost2, {2, 3, 4, 5}},
1218 {kHost3, {3, 4, 5}},
1223 privacy_sandbox_settings_->SetTopicAllowed(
1224 privacy_sandbox::CanonicalTopic(Topic(5), kTaxonomyVersion),
1227 task_environment_.AdvanceClock(base::Seconds(1));
1229 EpochTopics result = CalculateTopics();
1230 // topic 5 is cleared but topic 1 can still see its observing domains
1231 std::vector<std::pair<Topic, std::set<HashedDomain>>>
1232 expected_top_topics_and_observing_domains = {
1235 {HashedDomain(1), HashedDomain(2), HashedDomain(3),
1237 {Topic(3), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
1238 {Topic(2), {HashedDomain(1), HashedDomain(2)}},
1240 {HashedDomain(1), HashedDomain(2), HashedDomain(3), HashedDomain(4),
1243 ExpectResultTopicsEqual(result.top_topics_and_observing_domains(),
1244 expected_top_topics_and_observing_domains);
1247 TEST_F(BrowsingTopicsCalculatorTest, TopicHasDistantDescendant) {
1248 base::Time begin_time = base::Time::Now();
1259 AddApiUsageContextEntries({{kHost1, {HashedDomain(1)}},
1260 {kHost2, {HashedDomain(2)}},
1261 {kHost3, {HashedDomain(3)}},
1262 {kHost4, {HashedDomain(4)}},
1263 {kHost5, {HashedDomain(5)}}});
1265 test_annotator_.UseModelInfo(
1266 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
1267 // 1 is the parent topic of 2-4, and grandparent of 21.
1268 test_annotator_.UseAnnotations({
1269 {kHost1, {1, 2, 3, 4, 21}},
1270 {kHost2, {2, 3, 4, 21}},
1271 {kHost3, {3, 4, 21}},
1276 task_environment_.AdvanceClock(base::Seconds(1));
1277 EpochTopics result = CalculateTopics();
1279 std::vector<std::pair<Topic, std::set<HashedDomain>>>
1280 expected_top_topics_and_observing_domains = {
1282 {HashedDomain(1), HashedDomain(2), HashedDomain(3), HashedDomain(4),
1285 {HashedDomain(1), HashedDomain(2), HashedDomain(3),
1287 {Topic(3), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
1288 {Topic(2), {HashedDomain(1), HashedDomain(2)}},
1290 {HashedDomain(1), HashedDomain(2), HashedDomain(3), HashedDomain(4),
1293 ExpectResultTopicsEqual(result.top_topics_and_observing_domains(),
1294 expected_top_topics_and_observing_domains);
1297 TEST_F(BrowsingTopicsCalculatorTest, MultipleTopTopicsHaveDescendants) {
1298 base::Time begin_time = base::Time::Now();
1309 AddApiUsageContextEntries({{kHost1, {HashedDomain(1)}},
1310 {kHost2, {HashedDomain(2)}},
1311 {kHost3, {HashedDomain(3)}},
1312 {kHost4, {HashedDomain(4)}},
1313 {kHost5, {HashedDomain(5)}}});
1315 test_annotator_.UseModelInfo(
1316 *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build());
1317 // 1 is the ancestor of 21, 57 is the ancestor of 63 and 64.
1318 test_annotator_.UseAnnotations({
1319 {kHost1, {1, 57, 63, 64, 21}},
1320 {kHost2, {57, 63, 64, 21}},
1321 {kHost3, {63, 64, 21}},
1326 task_environment_.AdvanceClock(base::Seconds(1));
1327 EpochTopics result = CalculateTopics();
1329 std::vector<std::pair<Topic, std::set<HashedDomain>>>
1330 expected_top_topics_and_observing_domains = {
1332 {HashedDomain(1), HashedDomain(2), HashedDomain(3), HashedDomain(4),
1335 {HashedDomain(1), HashedDomain(2), HashedDomain(3),
1337 {Topic(63), {HashedDomain(1), HashedDomain(2), HashedDomain(3)}},
1339 {HashedDomain(1), HashedDomain(2), HashedDomain(3),
1342 {HashedDomain(1), HashedDomain(2), HashedDomain(3), HashedDomain(4),
1345 ExpectResultTopicsEqual(result.top_topics_and_observing_domains(),
1346 expected_top_topics_and_observing_domains);
1349 } // namespace browsing_topics