| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/json/json_file_value_serializer.h" |
| #include "base/json/values_util.h" |
| #include "base/path_service.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/browsing_topics/browsing_topics_service_factory.h" |
| #include "chrome/browser/history/history_service_factory.h" |
| #include "chrome/browser/optimization_guide/browser_test_util.h" |
| #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h" |
| #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h" |
| #include "chrome/browser/optimization_guide/page_content_annotations_service_factory.h" |
| #include "chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/browsing_topics/browsing_topics_service.h" |
| #include "components/browsing_topics/browsing_topics_service_impl.h" |
| #include "components/browsing_topics/epoch_topics.h" |
| #include "components/browsing_topics/test_util.h" |
| #include "components/content_settings/browser/page_specific_content_settings.h" |
| #include "components/keyed_service/content/browser_context_dependency_manager.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_settings.h" |
| #include "components/ukm/test_ukm_recorder.h" |
| #include "content/public/browser/browsing_topics_site_data_manager.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/test/back_forward_cache_util.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browsing_topics_test_util.h" |
| #include "content/public/test/fenced_frame_test_util.h" |
| #include "content/public/test/prerender_test_util.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/request_handler_util.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" |
| #include "third_party/blink/public/common/features.h" |
| |
| namespace browsing_topics { |
| |
| namespace { |
| |
| constexpr browsing_topics::HmacKey kTestKey = {1}; |
| |
| constexpr base::Time kTime1 = |
| base::Time::FromDeltaSinceWindowsEpoch(base::Days(1)); |
| constexpr base::Time kTime2 = |
| base::Time::FromDeltaSinceWindowsEpoch(base::Days(2)); |
| |
| constexpr size_t kTaxonomySize = 349; |
| constexpr int kTaxonomyVersion = 1; |
| constexpr int64_t kModelVersion = 2; |
| constexpr size_t kPaddedTopTopicsStartIndex = 5; |
| constexpr Topic kExpectedTopic1 = Topic(1); |
| constexpr Topic kExpectedTopic2 = Topic(10); |
| constexpr char kExpectedResultOrder1[] = |
| "[{\"configVersion\":\"chrome.1\",\"modelVersion\":\"2\"," |
| "\"taxonomyVersion\":\"1\",\"topic\":1,\"version\":\"chrome.1:1:2\"};{" |
| "\"configVersion\":\"chrome.1\",\"modelVersion\":\"2\"," |
| "\"taxonomyVersion\":\"1\",\"topic\":10,\"version\":\"chrome.1:1:2\"};]"; |
| |
| constexpr char kExpectedResultOrder2[] = |
| "[{\"configVersion\":\"chrome.1\",\"modelVersion\":\"2\"," |
| "\"taxonomyVersion\":\"1\",\"topic\":10,\"version\":\"chrome.1:1:2\"};{" |
| "\"configVersion\":\"chrome.1\",\"modelVersion\":\"2\"," |
| "\"taxonomyVersion\":\"1\",\"topic\":1,\"version\":\"chrome.1:1:2\"};]"; |
| |
| EpochTopics CreateTestEpochTopics( |
| const std::vector<std::pair<Topic, std::set<HashedDomain>>>& topics, |
| base::Time calculation_time) { |
| DCHECK_EQ(topics.size(), 5u); |
| |
| std::vector<TopicAndDomains> top_topics_and_observing_domains; |
| for (size_t i = 0; i < 5; ++i) { |
| top_topics_and_observing_domains.emplace_back(topics[i].first, |
| topics[i].second); |
| } |
| |
| return EpochTopics(std::move(top_topics_and_observing_domains), |
| kPaddedTopTopicsStartIndex, kTaxonomySize, |
| kTaxonomyVersion, kModelVersion, calculation_time); |
| } |
| |
| class PortalActivationWaiter : public content::WebContentsObserver { |
| public: |
| explicit PortalActivationWaiter(content::WebContents* portal_contents) |
| : content::WebContentsObserver(portal_contents) {} |
| |
| void Wait() { |
| if (!web_contents()->IsPortal()) |
| return; |
| |
| base::RunLoop run_loop; |
| quit_closure_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| // content::WebContentsObserver: |
| void DidActivatePortal(content::WebContents* predecessor_contents, |
| base::TimeTicks activation_time) override { |
| if (quit_closure_) |
| std::move(quit_closure_).Run(); |
| } |
| |
| private: |
| base::OnceClosure quit_closure_; |
| }; |
| |
| } // namespace |
| |
| // A tester class that allows waiting for the first calculation to finish. |
| class TesterBrowsingTopicsService : public BrowsingTopicsServiceImpl { |
| public: |
| TesterBrowsingTopicsService( |
| const base::FilePath& profile_path, |
| privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings, |
| history::HistoryService* history_service, |
| content::BrowsingTopicsSiteDataManager* site_data_manager, |
| optimization_guide::PageContentAnnotationsService* annotations_service, |
| base::OnceClosure calculation_finish_callback) |
| : BrowsingTopicsServiceImpl(profile_path, |
| privacy_sandbox_settings, |
| history_service, |
| site_data_manager, |
| annotations_service), |
| calculation_finish_callback_(std::move(calculation_finish_callback)) {} |
| |
| ~TesterBrowsingTopicsService() override = default; |
| |
| TesterBrowsingTopicsService(const TesterBrowsingTopicsService&) = delete; |
| TesterBrowsingTopicsService& operator=(const TesterBrowsingTopicsService&) = |
| delete; |
| TesterBrowsingTopicsService(TesterBrowsingTopicsService&&) = delete; |
| TesterBrowsingTopicsService& operator=(TesterBrowsingTopicsService&&) = |
| delete; |
| |
| const BrowsingTopicsState& browsing_topics_state() override { |
| return BrowsingTopicsServiceImpl::browsing_topics_state(); |
| } |
| |
| void OnCalculateBrowsingTopicsCompleted(EpochTopics epoch_topics) override { |
| BrowsingTopicsServiceImpl::OnCalculateBrowsingTopicsCompleted( |
| std::move(epoch_topics)); |
| |
| if (calculation_finish_callback_) |
| std::move(calculation_finish_callback_).Run(); |
| } |
| |
| private: |
| base::OnceClosure calculation_finish_callback_; |
| }; |
| |
| class BrowsingTopicsBrowserTestBase : public InProcessBrowserTest { |
| public: |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); |
| https_server_.AddDefaultHandlers(GetChromeTestDataDir()); |
| |
| content::SetupCrossSiteRedirector(&https_server_); |
| ASSERT_TRUE(https_server_.Start()); |
| |
| content::SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| ~BrowsingTopicsBrowserTestBase() override = default; |
| |
| std::string InvokeTopicsAPI(const content::ToRenderFrameHost& adapter) { |
| return EvalJs(adapter, R"( |
| if (!(document.browsingTopics instanceof Function)) { |
| 'not a function'; |
| } else { |
| document.browsingTopics() |
| .then(topics => { |
| let result = "["; |
| for (const topic of topics) { |
| result += JSON.stringify(topic, Object.keys(topic).sort()) + ";" |
| } |
| result += "]"; |
| return result; |
| }) |
| .catch(error => error.message); |
| } |
| )") |
| .ExtractString(); |
| } |
| |
| content::WebContents* web_contents() { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| protected: |
| net::EmbeddedTestServer https_server_{ |
| net::test_server::EmbeddedTestServer::TYPE_HTTPS}; |
| }; |
| |
| class BrowsingTopicsDisabledBrowserTest : public BrowsingTopicsBrowserTestBase { |
| public: |
| BrowsingTopicsDisabledBrowserTest() { |
| scoped_feature_list_.InitWithFeatures( |
| /*enabled_features=*/{}, |
| /*disabled_features=*/{blink::features::kBrowsingTopics}); |
| } |
| |
| protected: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsDisabledBrowserTest, |
| NoBrowsingTopicsService) { |
| EXPECT_FALSE( |
| BrowsingTopicsServiceFactory::GetForProfile(browser()->profile())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsDisabledBrowserTest, NoTopicsAPI) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/empty_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| EXPECT_EQ("not a function", InvokeTopicsAPI(web_contents())); |
| } |
| |
| class BrowsingTopicsBrowserTest : public BrowsingTopicsBrowserTestBase { |
| public: |
| BrowsingTopicsBrowserTest() |
| : prerender_helper_( |
| base::BindRepeating(&BrowsingTopicsBrowserTest::web_contents, |
| base::Unretained(this))) { |
| scoped_feature_list_.InitWithFeatures( |
| /*enabled_features=*/ |
| {blink::features::kBrowsingTopics, |
| blink::features::kBrowsingTopicsBypassIPIsPubliclyRoutableCheck, |
| features::kPrivacySandboxAdsAPIsOverride, blink::features::kPortals}, |
| /*disabled_features=*/{}); |
| } |
| |
| ~BrowsingTopicsBrowserTest() override = default; |
| |
| void SetUpOnMainThread() override { |
| prerender_helper_.SetUp(&https_server_); |
| |
| BrowsingTopicsBrowserTestBase::SetUpOnMainThread(); |
| |
| for (auto& profile_and_calculation_finish_waiter : |
| calculation_finish_waiters_) { |
| profile_and_calculation_finish_waiter.second->Run(); |
| } |
| } |
| |
| // BrowserTestBase::SetUpInProcessBrowserTestFixture |
| void SetUpInProcessBrowserTestFixture() override { |
| subscription_ = |
| BrowserContextDependencyManager::GetInstance() |
| ->RegisterCreateServicesCallbackForTesting(base::BindRepeating( |
| &BrowsingTopicsBrowserTest::OnWillCreateBrowserContextServices, |
| base::Unretained(this))); |
| } |
| |
| protected: |
| void ExpectResultTopicsEqual( |
| const std::vector<TopicAndDomains>& result, |
| std::vector<std::pair<Topic, std::set<HashedDomain>>> 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); |
| } |
| } |
| |
| HashedDomain GetHashedDomain(const std::string& domain) { |
| return HashContextDomainForStorage(kTestKey, domain); |
| } |
| |
| void CreateBrowsingTopicsStateFile( |
| const base::FilePath& profile_path, |
| const std::vector<EpochTopics>& epochs, |
| base::Time next_scheduled_calculation_time) { |
| base::Value::List epochs_list; |
| for (const EpochTopics& epoch : epochs) { |
| epochs_list.Append(epoch.ToDictValue()); |
| } |
| |
| base::Value::Dict dict; |
| dict.Set("epochs", std::move(epochs_list)); |
| dict.Set("next_scheduled_calculation_time", |
| base::TimeToValue(next_scheduled_calculation_time)); |
| dict.Set("hex_encoded_hmac_key", base::HexEncode(kTestKey)); |
| dict.Set("config_version", 1); |
| |
| JSONFileValueSerializer( |
| profile_path.Append(FILE_PATH_LITERAL("BrowsingTopicsState"))) |
| .Serialize(dict); |
| } |
| |
| content::BrowsingTopicsSiteDataManager* browsing_topics_site_data_manager() { |
| return browser() |
| ->profile() |
| ->GetDefaultStoragePartition() |
| ->GetBrowsingTopicsSiteDataManager(); |
| } |
| |
| TesterBrowsingTopicsService* browsing_topics_service() { |
| return static_cast<TesterBrowsingTopicsService*>( |
| BrowsingTopicsServiceFactory::GetForProfile(browser()->profile())); |
| } |
| |
| const BrowsingTopicsState& browsing_topics_state() { |
| return browsing_topics_service()->browsing_topics_state(); |
| } |
| |
| content::test::PrerenderTestHelper& prerender_helper() { |
| return prerender_helper_; |
| } |
| |
| std::vector<optimization_guide::WeightedIdentifier> TopicsAndWeight( |
| const std::vector<int32_t>& topics, |
| double weight) { |
| std::vector<optimization_guide::WeightedIdentifier> result; |
| for (int32_t topic : topics) { |
| result.emplace_back(topic, weight); |
| } |
| |
| return result; |
| } |
| |
| void OnWillCreateBrowserContextServices(content::BrowserContext* context) { |
| PageContentAnnotationsServiceFactory::GetInstance()->SetTestingFactory( |
| context, |
| base::BindRepeating( |
| &BrowsingTopicsBrowserTest::CreatePageContentAnnotationsService, |
| base::Unretained(this))); |
| |
| browsing_topics::BrowsingTopicsServiceFactory::GetInstance() |
| ->SetTestingFactory( |
| context, |
| base::BindRepeating( |
| &BrowsingTopicsBrowserTest::CreateBrowsingTopicsService, |
| base::Unretained(this))); |
| } |
| |
| std::unique_ptr<KeyedService> CreatePageContentAnnotationsService( |
| content::BrowserContext* context) { |
| Profile* profile = Profile::FromBrowserContext(context); |
| |
| history::HistoryService* history_service = |
| HistoryServiceFactory::GetForProfile( |
| profile, ServiceAccessType::IMPLICIT_ACCESS); |
| |
| DCHECK(!base::Contains(optimization_guide_model_providers_, profile)); |
| optimization_guide_model_providers_.emplace( |
| profile, std::make_unique< |
| optimization_guide::TestOptimizationGuideModelProvider>()); |
| |
| auto page_content_annotations_service = |
| std::make_unique<optimization_guide::PageContentAnnotationsService>( |
| "en-US", optimization_guide_model_providers_.at(profile).get(), |
| history_service, nullptr, base::FilePath(), nullptr, nullptr); |
| |
| page_content_annotations_service->OverridePageContentAnnotatorForTesting( |
| &test_page_content_annotator_); |
| |
| return page_content_annotations_service; |
| } |
| |
| void InitializePreexistingState( |
| history::HistoryService* history_service, |
| content::BrowsingTopicsSiteDataManager* site_data_manager, |
| const base::FilePath& profile_path) { |
| // Configure the (mock) model. |
| test_page_content_annotator_.UsePageTopics( |
| *optimization_guide::TestModelInfoBuilder().SetVersion(1).Build(), |
| {{"foo6.com", TopicsAndWeight({1, 2, 3, 4, 5, 6}, 0.1)}, |
| {"foo5.com", TopicsAndWeight({2, 3, 4, 5, 6}, 0.1)}, |
| {"foo4.com", TopicsAndWeight({3, 4, 5, 6}, 0.1)}, |
| {"foo3.com", TopicsAndWeight({4, 5, 6}, 0.1)}, |
| {"foo2.com", TopicsAndWeight({5, 6}, 0.1)}, |
| {"foo1.com", TopicsAndWeight({6}, 0.1)}}); |
| |
| // Add some initial history. |
| history::HistoryAddPageArgs add_page_args; |
| add_page_args.time = base::Time::Now(); |
| add_page_args.context_id = reinterpret_cast<history::ContextID>(1); |
| add_page_args.nav_entry_id = 1; |
| |
| // Note: foo6.com isn't in the initial history. |
| for (int i = 1; i <= 5; ++i) { |
| add_page_args.url = |
| GURL(base::StrCat({"https://foo", base::NumberToString(i), ".com"})); |
| history_service->AddPage(add_page_args); |
| history_service->SetBrowsingTopicsAllowed(add_page_args.context_id, |
| add_page_args.nav_entry_id, |
| add_page_args.url); |
| } |
| |
| // Add some API usage contexts data. |
| site_data_manager->OnBrowsingTopicsApiUsed( |
| HashMainFrameHostForStorage("foo1.com"), {HashedDomain(1)}, |
| base::Time::Now()); |
| |
| // Initialize the `BrowsingTopicsState`. |
| std::vector<EpochTopics> preexisting_epochs; |
| preexisting_epochs.push_back( |
| CreateTestEpochTopics({{Topic(1), {GetHashedDomain("a.test")}}, |
| {Topic(2), {GetHashedDomain("a.test")}}, |
| {Topic(3), {GetHashedDomain("a.test")}}, |
| {Topic(4), {GetHashedDomain("a.test")}}, |
| {Topic(5), {GetHashedDomain("a.test")}}}, |
| kTime1)); |
| preexisting_epochs.push_back( |
| CreateTestEpochTopics({{Topic(6), {GetHashedDomain("a.test")}}, |
| {Topic(7), {GetHashedDomain("a.test")}}, |
| {Topic(8), {GetHashedDomain("a.test")}}, |
| {Topic(9), {GetHashedDomain("a.test")}}, |
| {Topic(10), {GetHashedDomain("a.test")}}}, |
| kTime2)); |
| |
| CreateBrowsingTopicsStateFile( |
| profile_path, std::move(preexisting_epochs), |
| /*next_scheduled_calculation_time=*/base::Time::Now() - base::Days(1)); |
| } |
| |
| std::unique_ptr<KeyedService> CreateBrowsingTopicsService( |
| content::BrowserContext* context) { |
| Profile* profile = Profile::FromBrowserContext(context); |
| |
| privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings = |
| PrivacySandboxSettingsFactory::GetForProfile(profile); |
| |
| history::HistoryService* history_service = |
| HistoryServiceFactory::GetForProfile( |
| profile, ServiceAccessType::IMPLICIT_ACCESS); |
| |
| content::BrowsingTopicsSiteDataManager* site_data_manager = |
| context->GetDefaultStoragePartition() |
| ->GetBrowsingTopicsSiteDataManager(); |
| |
| optimization_guide::PageContentAnnotationsService* annotations_service = |
| PageContentAnnotationsServiceFactory::GetForProfile(profile); |
| |
| InitializePreexistingState(history_service, site_data_manager, |
| profile->GetPath()); |
| |
| DCHECK(!base::Contains(calculation_finish_waiters_, profile)); |
| calculation_finish_waiters_.emplace(profile, |
| std::make_unique<base::RunLoop>()); |
| |
| if (!ukm_recorder_) |
| ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>(); |
| |
| return std::make_unique<TesterBrowsingTopicsService>( |
| profile->GetPath(), privacy_sandbox_settings, history_service, |
| site_data_manager, annotations_service, |
| calculation_finish_waiters_.at(profile)->QuitClosure()); |
| } |
| |
| content::test::FencedFrameTestHelper fenced_frame_test_helper_; |
| |
| content::test::PrerenderTestHelper prerender_helper_; |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| std::map< |
| Profile*, |
| std::unique_ptr<optimization_guide::TestOptimizationGuideModelProvider>> |
| optimization_guide_model_providers_; |
| |
| std::map<Profile*, std::unique_ptr<base::RunLoop>> |
| calculation_finish_waiters_; |
| |
| optimization_guide::TestPageContentAnnotator test_page_content_annotator_; |
| |
| std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_; |
| |
| base::CallbackListSubscription subscription_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, HasBrowsingTopicsService) { |
| EXPECT_TRUE(browsing_topics_service()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, NoServiceInIncognitoMode) { |
| CreateIncognitoBrowser(browser()->profile()); |
| |
| EXPECT_TRUE(browser()->profile()->HasPrimaryOTRProfile()); |
| |
| Profile* incognito_profile = |
| browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/false); |
| EXPECT_TRUE(incognito_profile); |
| |
| BrowsingTopicsService* incognito_browsing_topics_service = |
| BrowsingTopicsServiceFactory::GetForProfile(incognito_profile); |
| EXPECT_FALSE(incognito_browsing_topics_service); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, BrowsingTopicsStateOnStart) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/empty_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| base::Time now = base::Time::Now(); |
| |
| EXPECT_EQ(browsing_topics_state().epochs().size(), 3u); |
| EXPECT_EQ(browsing_topics_state().epochs()[0].calculation_time(), kTime1); |
| EXPECT_EQ(browsing_topics_state().epochs()[1].calculation_time(), kTime2); |
| EXPECT_GT(browsing_topics_state().epochs()[2].calculation_time(), |
| now - base::Minutes(1)); |
| EXPECT_LT(browsing_topics_state().epochs()[2].calculation_time(), now); |
| |
| ExpectResultTopicsEqual( |
| browsing_topics_state().epochs()[0].top_topics_and_observing_domains(), |
| {{Topic(1), {GetHashedDomain("a.test")}}, |
| {Topic(2), {GetHashedDomain("a.test")}}, |
| {Topic(3), {GetHashedDomain("a.test")}}, |
| {Topic(4), {GetHashedDomain("a.test")}}, |
| {Topic(5), {GetHashedDomain("a.test")}}}); |
| |
| ExpectResultTopicsEqual( |
| browsing_topics_state().epochs()[1].top_topics_and_observing_domains(), |
| {{Topic(6), {GetHashedDomain("a.test")}}, |
| {Topic(7), {GetHashedDomain("a.test")}}, |
| {Topic(8), {GetHashedDomain("a.test")}}, |
| {Topic(9), {GetHashedDomain("a.test")}}, |
| {Topic(10), {GetHashedDomain("a.test")}}}); |
| |
| ExpectResultTopicsEqual( |
| browsing_topics_state().epochs()[2].top_topics_and_observing_domains(), |
| {{Topic(6), {HashedDomain(1)}}, |
| {Topic(5), {}}, |
| {Topic(4), {}}, |
| {Topic(3), {}}, |
| {Topic(2), {}}}); |
| |
| EXPECT_GT(browsing_topics_state().next_scheduled_calculation_time(), |
| now + base::Days(7) - base::Minutes(1)); |
| EXPECT_LT(browsing_topics_state().next_scheduled_calculation_time(), |
| now + base::Days(7)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, CalculationResultUkm) { |
| auto entries = ukm_recorder_->GetEntriesByName( |
| ukm::builders::BrowsingTopics_EpochTopicsCalculationResult::kEntryName); |
| |
| // The number of entries should equal the number of profiles, which could be |
| // greater than 1 on some platform. |
| EXPECT_EQ(optimization_guide_model_providers_.size(), entries.size()); |
| |
| for (auto* entry : entries) { |
| ukm_recorder_->ExpectEntryMetric( |
| entry, |
| ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: |
| kTopTopic0Name, |
| 6); |
| ukm_recorder_->ExpectEntryMetric( |
| entry, |
| ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: |
| kTopTopic1Name, |
| 5); |
| ukm_recorder_->ExpectEntryMetric( |
| entry, |
| ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: |
| kTopTopic2Name, |
| 4); |
| ukm_recorder_->ExpectEntryMetric( |
| entry, |
| ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: |
| kTopTopic3Name, |
| 3); |
| ukm_recorder_->ExpectEntryMetric( |
| entry, |
| ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: |
| kTopTopic4Name, |
| 2); |
| ukm_recorder_->ExpectEntryMetric( |
| entry, |
| ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: |
| kTaxonomyVersionName, |
| 1); |
| ukm_recorder_->ExpectEntryMetric( |
| entry, |
| ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: |
| kModelVersionName, |
| 1); |
| ukm_recorder_->ExpectEntryMetric( |
| entry, |
| ukm::builders::BrowsingTopics_EpochTopicsCalculationResult:: |
| kPaddedTopicsStartIndexName, |
| 5); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, ApiResultUkm) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/one_iframe_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| InvokeTopicsAPI(web_contents()); |
| |
| auto entries = ukm_recorder_->GetEntriesByName( |
| ukm::builders::BrowsingTopics_DocumentBrowsingTopicsApiResult:: |
| kEntryName); |
| EXPECT_EQ(1u, entries.size()); |
| |
| ukm_recorder_->ExpectEntrySourceHasUrl(entries.back(), main_frame_url); |
| |
| const int64_t* topic0_metric = ukm_recorder_->GetEntryMetric( |
| entries.back(), |
| ukm::builders::BrowsingTopics_DocumentBrowsingTopicsApiResult:: |
| kReturnedTopic0Name); |
| const int64_t* topic1_metric = ukm_recorder_->GetEntryMetric( |
| entries.back(), |
| ukm::builders::BrowsingTopics_DocumentBrowsingTopicsApiResult:: |
| kReturnedTopic1Name); |
| |
| EXPECT_TRUE(topic0_metric); |
| EXPECT_TRUE(topic1_metric); |
| |
| EXPECT_TRUE((*topic0_metric == kExpectedTopic1.value() && |
| *topic1_metric == kExpectedTopic2.value()) || |
| (*topic0_metric == kExpectedTopic2.value() && |
| *topic1_metric == kExpectedTopic1.value())); |
| |
| ukm_recorder_->ExpectEntryMetric( |
| entries.back(), |
| ukm::builders::BrowsingTopics_DocumentBrowsingTopicsApiResult:: |
| kReturnedTopic0IsTrueTopTopicName, |
| true); |
| ukm_recorder_->ExpectEntryMetric( |
| entries.back(), |
| ukm::builders::BrowsingTopics_DocumentBrowsingTopicsApiResult:: |
| kReturnedTopic0ModelVersionName, |
| 2); |
| ukm_recorder_->ExpectEntryMetric( |
| entries.back(), |
| ukm::builders::BrowsingTopics_DocumentBrowsingTopicsApiResult:: |
| kReturnedTopic0TaxonomyVersionName, |
| 1); |
| |
| ukm_recorder_->ExpectEntryMetric( |
| entries.back(), |
| ukm::builders::BrowsingTopics_DocumentBrowsingTopicsApiResult:: |
| kReturnedTopic1IsTrueTopTopicName, |
| true); |
| ukm_recorder_->ExpectEntryMetric( |
| entries.back(), |
| ukm::builders::BrowsingTopics_DocumentBrowsingTopicsApiResult:: |
| kReturnedTopic1ModelVersionName, |
| 2); |
| ukm_recorder_->ExpectEntryMetric( |
| entries.back(), |
| ukm::builders::BrowsingTopics_DocumentBrowsingTopicsApiResult:: |
| kReturnedTopic1TaxonomyVersionName, |
| 1); |
| |
| EXPECT_FALSE(ukm_recorder_->GetEntryMetric( |
| entries.back(), |
| ukm::builders::BrowsingTopics_DocumentBrowsingTopicsApiResult:: |
| kReturnedTopic2Name)); |
| EXPECT_FALSE(ukm_recorder_->GetEntryMetric( |
| entries.back(), |
| ukm::builders::BrowsingTopics_DocumentBrowsingTopicsApiResult:: |
| kEmptyReasonName)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, PageLoadUkm) { |
| // The test assumes pages gets deleted after navigation, triggering metrics |
| // recording. Disable back/forward cache to ensure that pages don't get |
| // preserved in the cache. |
| content::DisableBackForwardCacheForTesting( |
| web_contents(), content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/one_iframe_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| InvokeTopicsAPI(web_contents()); |
| |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL))); |
| |
| auto entries = ukm_recorder_->GetEntriesByName( |
| ukm::builders::BrowsingTopics_PageLoad::kEntryName); |
| EXPECT_EQ(1u, entries.size()); |
| |
| ukm_recorder_->ExpectEntrySourceHasUrl(entries.back(), main_frame_url); |
| |
| ukm_recorder_->ExpectEntryMetric(entries.back(), |
| ukm::builders::BrowsingTopics_PageLoad:: |
| kTopicsRequestingContextDomainsCountName, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, GetTopicsForSiteForDisplay) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/empty_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| std::vector<privacy_sandbox::CanonicalTopic> result = |
| browsing_topics_service()->GetTopicsForSiteForDisplay( |
| web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin()); |
| |
| // Epoch switch time has not arrived. So expect one topic from each of the |
| // first two epochs. |
| EXPECT_EQ(result.size(), 2u); |
| EXPECT_EQ(result[0].topic_id(), Topic(1)); |
| EXPECT_EQ(result[0].taxonomy_version(), 1); |
| EXPECT_EQ(result[1].topic_id(), Topic(10)); |
| EXPECT_EQ(result[1].taxonomy_version(), 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, GetTopTopicsForDisplay) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/empty_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| std::vector<privacy_sandbox::CanonicalTopic> result = |
| browsing_topics_service()->GetTopTopicsForDisplay(); |
| |
| EXPECT_EQ(result.size(), 15u); |
| EXPECT_EQ(result[0].topic_id(), Topic(1)); |
| EXPECT_EQ(result[1].topic_id(), Topic(2)); |
| EXPECT_EQ(result[2].topic_id(), Topic(3)); |
| EXPECT_EQ(result[3].topic_id(), Topic(4)); |
| EXPECT_EQ(result[4].topic_id(), Topic(5)); |
| EXPECT_EQ(result[5].topic_id(), Topic(6)); |
| EXPECT_EQ(result[6].topic_id(), Topic(7)); |
| EXPECT_EQ(result[7].topic_id(), Topic(8)); |
| EXPECT_EQ(result[8].topic_id(), Topic(9)); |
| EXPECT_EQ(result[9].topic_id(), Topic(10)); |
| EXPECT_EQ(result[10].topic_id(), Topic(6)); |
| EXPECT_EQ(result[11].topic_id(), Topic(5)); |
| EXPECT_EQ(result[12].topic_id(), Topic(4)); |
| EXPECT_EQ(result[13].topic_id(), Topic(3)); |
| EXPECT_EQ(result[14].topic_id(), Topic(2)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, |
| TopicsAPI_ContextDomainNotFiltered_FromMainFrame) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/one_iframe_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| std::string result = InvokeTopicsAPI(web_contents()); |
| |
| EXPECT_TRUE(result == kExpectedResultOrder1 || |
| result == kExpectedResultOrder2); |
| |
| // Ensure access has been reported to the Page Specific Content Settings. |
| auto* pscs = content_settings::PageSpecificContentSettings::GetForPage( |
| web_contents()->GetPrimaryPage()); |
| EXPECT_TRUE(pscs->HasAccessedTopics()); |
| auto topics = pscs->GetAccessedTopics(); |
| ASSERT_EQ(2u, topics.size()); |
| |
| // No ordering is enforced by the PSCS. |
| ASSERT_NE(topics[0].topic_id(), topics[1].topic_id()); |
| ASSERT_TRUE(topics[0].topic_id() == kExpectedTopic1 || |
| topics[0].topic_id() == kExpectedTopic2); |
| ASSERT_TRUE(topics[1].topic_id() == kExpectedTopic1 || |
| topics[1].topic_id() == kExpectedTopic2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, |
| TopicsAPI_ContextDomainNotFiltered_FromSubframe) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/one_iframe_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| GURL subframe_url = |
| https_server_.GetURL("a.test", "/browsing_topics/empty_page.html"); |
| |
| ASSERT_TRUE(content::NavigateIframeToURL(web_contents(), |
| /*iframe_id=*/"frame", |
| subframe_url)); |
| |
| std::string result = InvokeTopicsAPI( |
| content::ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)); |
| |
| EXPECT_TRUE(result == kExpectedResultOrder1 || |
| result == kExpectedResultOrder2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, |
| TopicsAPI_ContextDomainFiltered) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/one_iframe_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| GURL subframe_url = |
| https_server_.GetURL("b.test", "/browsing_topics/empty_page.html"); |
| |
| ASSERT_TRUE(content::NavigateIframeToURL(web_contents(), |
| /*iframe_id=*/"frame", |
| subframe_url)); |
| |
| // b.test has yet to call the API so it shouldn't receive a topic. |
| EXPECT_EQ("[]", InvokeTopicsAPI(content::ChildFrameAt( |
| web_contents()->GetPrimaryMainFrame(), 0))); |
| auto* pscs = content_settings::PageSpecificContentSettings::GetForPage( |
| web_contents()->GetPrimaryPage()); |
| EXPECT_FALSE(pscs->HasAccessedTopics()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, |
| TopicsAPI_ContextDomainTracked) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/one_iframe_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| GURL subframe_url = |
| https_server_.GetURL("b.test", "/browsing_topics/empty_page.html"); |
| |
| ASSERT_TRUE(content::NavigateIframeToURL(web_contents(), |
| /*iframe_id=*/"frame", |
| subframe_url)); |
| |
| // The usage is not tracked before the API call. The returned entry was from |
| // the pre-existing storage. |
| std::vector<ApiUsageContext> api_usage_contexts = |
| content::GetBrowsingTopicsApiUsage(browsing_topics_site_data_manager()); |
| EXPECT_EQ(api_usage_contexts.size(), 1u); |
| |
| EXPECT_EQ("[]", InvokeTopicsAPI(content::ChildFrameAt( |
| web_contents()->GetPrimaryMainFrame(), 0))); |
| |
| api_usage_contexts = |
| content::GetBrowsingTopicsApiUsage(browsing_topics_site_data_manager()); |
| |
| // The usage is tracked after the API call. |
| EXPECT_EQ(api_usage_contexts.size(), 2u); |
| EXPECT_EQ(api_usage_contexts[0].hashed_main_frame_host, |
| HashMainFrameHostForStorage("foo1.com")); |
| EXPECT_EQ(api_usage_contexts[0].hashed_context_domain, HashedDomain(1)); |
| |
| EXPECT_EQ( |
| api_usage_contexts[1].hashed_main_frame_host, |
| HashMainFrameHostForStorage(https_server_.GetURL("a.test", "/").host())); |
| EXPECT_EQ(api_usage_contexts[1].hashed_context_domain, |
| GetHashedDomain("b.test")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BrowsingTopicsBrowserTest, |
| EmptyPage_PermissionsPolicyBrowsingTopicsNone_TopicsAPI) { |
| GURL main_frame_url = https_server_.GetURL( |
| "a.test", "/browsing_topics/empty_page_browsing_topics_none.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| EXPECT_EQ( |
| "The \"browsing-topics\" Permissions Policy denied the use of " |
| "document.browsingTopics().", |
| InvokeTopicsAPI(web_contents())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BrowsingTopicsBrowserTest, |
| EmptyPage_PermissionsPolicyInterestCohortNone_TopicsAPI) { |
| GURL main_frame_url = https_server_.GetURL( |
| "a.test", "/browsing_topics/empty_page_interest_cohort_none.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| EXPECT_EQ( |
| "The \"interest-cohort\" Permissions Policy denied the use of " |
| "document.browsingTopics().", |
| InvokeTopicsAPI(web_contents())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BrowsingTopicsBrowserTest, |
| OneIframePage_SubframePermissionsPolicyBrowsingTopicsNone_TopicsAPI) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/one_iframe_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| GURL subframe_url = https_server_.GetURL( |
| "a.test", "/browsing_topics/empty_page_browsing_topics_none.html"); |
| |
| ASSERT_TRUE(content::NavigateIframeToURL(web_contents(), |
| /*iframe_id=*/"frame", |
| subframe_url)); |
| |
| std::string result = InvokeTopicsAPI(web_contents()); |
| EXPECT_TRUE(result == kExpectedResultOrder1 || |
| result == kExpectedResultOrder2); |
| |
| EXPECT_EQ( |
| "The \"browsing-topics\" Permissions Policy denied the use of " |
| "document.browsingTopics().", |
| InvokeTopicsAPI( |
| content::ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, |
| PermissionsPolicyAllowCertainOrigin_TopicsAPI) { |
| base::StringPairs port_replacement; |
| port_replacement.push_back( |
| std::make_pair("{{PORT}}", base::NumberToString(https_server_.port()))); |
| |
| GURL main_frame_url = https_server_.GetURL( |
| "a.test", net::test_server::GetFilePathWithReplacements( |
| "/browsing_topics/" |
| "one_iframe_page_browsing_topics_allow_certain_origin.html", |
| port_replacement)); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| std::string result = InvokeTopicsAPI(web_contents()); |
| EXPECT_TRUE(result == kExpectedResultOrder1 || |
| result == kExpectedResultOrder2); |
| |
| GURL subframe_url = |
| https_server_.GetURL("c.test", "/browsing_topics/empty_page.html"); |
| |
| ASSERT_TRUE(content::NavigateIframeToURL(web_contents(), |
| /*iframe_id=*/"frame", |
| subframe_url)); |
| EXPECT_EQ("[]", InvokeTopicsAPI(content::ChildFrameAt( |
| web_contents()->GetPrimaryMainFrame(), 0))); |
| |
| subframe_url = |
| https_server_.GetURL("b.test", "/browsing_topics/empty_page.html"); |
| |
| ASSERT_TRUE(content::NavigateIframeToURL(web_contents(), |
| /*iframe_id=*/"frame", |
| subframe_url)); |
| |
| EXPECT_EQ( |
| "The \"browsing-topics\" Permissions Policy denied the use of " |
| "document.browsingTopics().", |
| InvokeTopicsAPI( |
| content::ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, |
| TopicsAPINotAllowedInInsecureContext) { |
| GURL main_frame_url = embedded_test_server()->GetURL( |
| "a.test", "/browsing_topics/one_iframe_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| // Navigate the iframe to a https site. |
| GURL subframe_url = https_server_.GetURL("b.test", "/empty_page.html"); |
| content::NavigateIframeToURL(web_contents(), |
| /*iframe_id=*/"frame", subframe_url); |
| |
| // Both the main frame and the subframe are insecure context because the main |
| // frame is loaded over HTTP. Expect that the API isn't available in either |
| // frame. |
| EXPECT_EQ("not a function", InvokeTopicsAPI(web_contents())); |
| EXPECT_EQ("not a function", InvokeTopicsAPI(content::ChildFrameAt( |
| web_contents()->GetPrimaryMainFrame(), 0))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, |
| TopicsAPINotAllowedInDetachedDocument) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/one_iframe_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| EXPECT_EQ( |
| "Failed to execute 'browsingTopics' on 'Document': A browsing " |
| "context is required when calling document.browsingTopics().", |
| EvalJs(web_contents(), R"( |
| const iframe = document.getElementById('frame'); |
| const childDocument = iframe.contentWindow.document; |
| iframe.remove(); |
| |
| childDocument.browsingTopics() |
| .then(topics => "success") |
| .catch(error => error.message); |
| )")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, |
| TopicsAPINotAllowedInOpaqueOriginDocument) { |
| GURL main_frame_url = https_server_.GetURL( |
| "a.test", "/browsing_topics/one_sandboxed_iframe_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| EXPECT_EQ( |
| "document.browsingTopics() is not allowed in an opaque origin context.", |
| InvokeTopicsAPI( |
| content::ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, |
| TopicsAPINotAllowedInFencedFrame) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/one_iframe_page.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| GURL fenced_frame_url = |
| https_server_.GetURL("b.test", "/fenced_frames/title1.html"); |
| |
| content::RenderFrameHostWrapper fenced_frame_rfh_wrapper( |
| fenced_frame_test_helper_.CreateFencedFrame( |
| web_contents()->GetPrimaryMainFrame(), fenced_frame_url)); |
| |
| EXPECT_EQ("document.browsingTopics() is not allowed in a fenced frame.", |
| InvokeTopicsAPI(fenced_frame_rfh_wrapper.get())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, |
| TopicsAPINotAllowedInPrerenderedPage) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/one_iframe_page.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| GURL prerender_url = |
| https_server_.GetURL("a.test", "/browsing_topics/empty_page.html"); |
| |
| int host_id = prerender_helper().AddPrerender(prerender_url); |
| |
| content::RenderFrameHost* prerender_host = |
| prerender_helper().GetPrerenderedMainFrameHost(host_id); |
| EXPECT_EQ( |
| "document.browsingTopics() is not allowed when the page is being " |
| "prerendered.", |
| InvokeTopicsAPI(prerender_host)); |
| |
| // Activate the prerendered page. The API call should succeed. |
| content::test::PrerenderHostObserver prerender_observer(*web_contents(), |
| prerender_url); |
| prerender_helper().NavigatePrimaryPage(prerender_url); |
| prerender_observer.WaitForActivation(); |
| |
| std::string result = InvokeTopicsAPI(web_contents()); |
| |
| EXPECT_TRUE(result == kExpectedResultOrder1 || |
| result == kExpectedResultOrder2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, TopicsAPINotAllowedInPortal) { |
| GURL main_frame_url = |
| https_server_.GetURL("a.test", "/browsing_topics/one_iframe_page.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| GURL portal_url = |
| https_server_.GetURL("a.test", "/browsing_topics/empty_page.html"); |
| |
| ASSERT_EQ(true, content::EvalJs(web_contents()->GetPrimaryMainFrame(), |
| content::JsReplace(R"( |
| new Promise((resolve) => { |
| let portal = document.createElement('portal'); |
| portal.src = $1; |
| portal.onload = () => { resolve(true); } |
| document.body.appendChild(portal); |
| }); |
| )", |
| portal_url))); |
| |
| std::vector<content::WebContents*> inner_web_contents = |
| web_contents()->GetInnerWebContents(); |
| EXPECT_EQ(1u, inner_web_contents.size()); |
| content::WebContents* portal_contents = inner_web_contents[0]; |
| |
| EXPECT_EQ( |
| "document.browsingTopics() is only allowed in the outermost page and " |
| "when the page is active.", |
| InvokeTopicsAPI(portal_contents)); |
| |
| // Activate the portal. The API call should succeed. |
| PortalActivationWaiter activation_waiter(portal_contents); |
| content::ExecuteScriptAsync(web_contents()->GetPrimaryMainFrame(), |
| "document.querySelector('portal').activate();"); |
| activation_waiter.Wait(); |
| |
| EXPECT_EQ(portal_contents, web_contents()); |
| |
| std::string result = InvokeTopicsAPI(web_contents()); |
| |
| EXPECT_TRUE(result == kExpectedResultOrder1 || |
| result == kExpectedResultOrder2); |
| } |
| |
| // Regression test for crbug/1339735. |
| IN_PROC_BROWSER_TEST_F( |
| BrowsingTopicsBrowserTest, |
| TopicsAPIInvokedInMainFrameUnloadHandler_NoRendererCrash) { |
| GURL main_frame_url = https_server_.GetURL( |
| "a.test", "/browsing_topics/get_topics_during_unload.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url)); |
| |
| // A renderer crash won't always be captured if the renderer is also shutting |
| // down naturally around the same time. Thus, we create a new page in the same |
| // renderer process to keep the renderer process alive when the page navigates |
| // away later. |
| content::TestNavigationObserver popup_observer(main_frame_url); |
| popup_observer.StartWatchingNewWebContents(); |
| EXPECT_TRUE( |
| ExecuteScript(web_contents()->GetPrimaryMainFrame(), |
| content::JsReplace("window.open($1)", main_frame_url))); |
| popup_observer.Wait(); |
| |
| GURL new_url = |
| https_server_.GetURL("b.test", "/browsing_topics/empty_page.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), new_url)); |
| } |
| |
| } // namespace browsing_topics |