| // 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 "content/browser/browsing_topics/header_util.h" |
| |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/public/test/web_contents_tester.h" |
| #include "content/test/test_render_view_host.h" |
| #include "services/network/public/mojom/parsed_headers.mojom.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| blink::mojom::EpochTopicPtr CreateMojomTopic(int topic, |
| const std::string& model_version) { |
| auto mojom_topic = blink::mojom::EpochTopic::New(); |
| mojom_topic->topic = topic; |
| mojom_topic->config_version = "chrome.1"; |
| mojom_topic->taxonomy_version = "1"; |
| mojom_topic->model_version = model_version; |
| mojom_topic->version = base::StrCat({mojom_topic->config_version, ":", |
| mojom_topic->taxonomy_version, ":", |
| mojom_topic->model_version}); |
| return mojom_topic; |
| } |
| |
| class TopicsInterceptingContentBrowserClient : public ContentBrowserClient { |
| public: |
| bool HandleTopicsWebApi( |
| const url::Origin& context_origin, |
| content::RenderFrameHost* main_frame, |
| browsing_topics::ApiCallerSource caller_source, |
| bool get_topics, |
| bool observe, |
| std::vector<blink::mojom::EpochTopicPtr>& topics) override { |
| handle_topics_web_api_called_ = true; |
| last_get_topics_param_ = get_topics; |
| last_observe_param_ = observe; |
| return true; |
| } |
| |
| int NumVersionsInTopicsEpochs( |
| content::RenderFrameHost* main_frame) const override { |
| return 1; |
| } |
| |
| bool handle_topics_web_api_called() const { |
| return handle_topics_web_api_called_; |
| } |
| |
| bool last_get_topics_param() const { return last_get_topics_param_; } |
| |
| bool last_observe_param() const { return last_observe_param_; } |
| |
| private: |
| bool handle_topics_web_api_called_ = false; |
| bool last_get_topics_param_ = false; |
| bool last_observe_param_ = false; |
| }; |
| |
| } // namespace |
| |
| class BrowsingTopicsUtilTest : public RenderViewHostTestHarness { |
| public: |
| void SetUp() override { |
| content::RenderViewHostTestHarness::SetUp(); |
| |
| original_client_ = content::SetBrowserClientForTesting(&browser_client_); |
| |
| GURL url("https://foo.com"); |
| auto simulator = |
| NavigationSimulator::CreateBrowserInitiated(url, web_contents()); |
| simulator->Commit(); |
| } |
| |
| void TearDown() override { |
| SetBrowserClientForTesting(original_client_); |
| |
| content::RenderViewHostTestHarness::TearDown(); |
| } |
| |
| const TopicsInterceptingContentBrowserClient& browser_client() const { |
| return browser_client_; |
| } |
| |
| private: |
| TopicsInterceptingContentBrowserClient browser_client_; |
| raw_ptr<ContentBrowserClient> original_client_ = nullptr; |
| }; |
| |
| TEST_F(BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_EmptyTopics_ZeroVersionInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/0); |
| EXPECT_EQ(header_value, "();p=P0000000000000000000000000000000"); |
| } |
| |
| TEST_F(BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_EmptyTopics_OneVersionInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/1); |
| EXPECT_EQ(header_value, "();p=P0000000000000000000000000000000"); |
| } |
| |
| TEST_F(BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_EmptyTopics_TwoVersionsInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/2); |
| EXPECT_EQ(header_value, |
| "();p=P00000000000000000000000000000000000000000000000000"); |
| } |
| |
| TEST_F(BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_EmptyTopics_ThreeVersionsInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/3); |
| EXPECT_EQ( |
| header_value, |
| "();p=" |
| "P000000000000000000000000000000000000000000000000000000000000000000000"); |
| } |
| |
| TEST_F(BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_OneTopic_OneVersionInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(1, /*model_version=*/"2")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/1); |
| |
| EXPECT_EQ(header_value, "(1);v=chrome.1:1:2, ();p=P00000000000"); |
| } |
| |
| TEST_F(BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_OneTopic_TwoVersionsInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(1, /*model_version=*/"2")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/2); |
| |
| EXPECT_EQ(header_value, |
| "(1);v=chrome.1:1:2, ();p=P000000000000000000000000000000"); |
| } |
| |
| TEST_F(BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_OneTopic_ThreeVersionsInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(1, /*model_version=*/"2")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/3); |
| |
| EXPECT_EQ(header_value, |
| "(1);v=chrome.1:1:2, " |
| "();p=P0000000000000000000000000000000000000000000000000"); |
| } |
| |
| TEST_F(BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_OneThreeDigitTopic_OneVersionInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(123, |
| /*model_version=*/"2")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/1); |
| |
| EXPECT_EQ(header_value, "(123);v=chrome.1:1:2, ();p=P000000000"); |
| } |
| |
| TEST_F(BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_TwoTopics_SameTopicVersions_OneVersionInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(1, /*model_version=*/"2")); |
| topics.push_back(CreateMojomTopic(2, /*model_version=*/"2")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/1); |
| |
| EXPECT_EQ(header_value, "(1 2);v=chrome.1:1:2, ();p=P000000000"); |
| } |
| |
| TEST_F( |
| BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_TwoMixedDigitsTopics_SameTopicVersions_OneVersionInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(123, /*model_version=*/"2")); |
| topics.push_back(CreateMojomTopic(45, /*model_version=*/"2")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/1); |
| |
| EXPECT_EQ(header_value, "(123 45);v=chrome.1:1:2, ();p=P000000"); |
| } |
| |
| TEST_F( |
| BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_TwoTopics_SameTopicVersions_TwoVersionsInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(1, /*model_version=*/"2")); |
| topics.push_back(CreateMojomTopic(2, /*model_version=*/"2")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/2); |
| |
| EXPECT_EQ(header_value, |
| "(1 2);v=chrome.1:1:2, ();p=P0000000000000000000000000000"); |
| } |
| |
| TEST_F( |
| BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_TwoTopics_DifferentTopicVersions_TwoVersionsInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(1, /*model_version=*/"2")); |
| topics.push_back(CreateMojomTopic(1, /*model_version=*/"4")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/2); |
| |
| EXPECT_EQ(header_value, |
| "(1);v=chrome.1:1:2, (1);v=chrome.1:1:4, ();p=P0000000000"); |
| } |
| |
| TEST_F( |
| BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_TwoTopics_DifferentTopicVersions_ThreeVersionsInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(1, /*model_version=*/"2")); |
| topics.push_back(CreateMojomTopic(1, /*model_version=*/"4")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/3); |
| EXPECT_EQ(header_value, |
| "(1);v=chrome.1:1:2, (1);v=chrome.1:1:4, " |
| "();p=P00000000000000000000000000000"); |
| } |
| |
| TEST_F( |
| BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_ThreeTopics_SameTopicVersions_OneVersionInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(1, /*model_version=*/"2")); |
| topics.push_back(CreateMojomTopic(2, /*model_version=*/"2")); |
| topics.push_back(CreateMojomTopic(3, /*model_version=*/"2")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/1); |
| |
| EXPECT_EQ(header_value, "(1 2 3);v=chrome.1:1:2, ();p=P0000000"); |
| } |
| |
| TEST_F( |
| BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_ThreeThreeDigitsTopics_SameTopicVersions_OneVersionInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(100, /*model_version=*/"20")); |
| topics.push_back(CreateMojomTopic(200, /*model_version=*/"20")); |
| topics.push_back(CreateMojomTopic(300, /*model_version=*/"20")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/1); |
| |
| EXPECT_EQ(header_value, "(100 200 300);v=chrome.1:1:20, ();p=P"); |
| } |
| |
| TEST_F( |
| BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_ThreeThreeDigitsTopics_FirstTwoTopicVersionsSame_TwoVersionsInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(100, /*model_version=*/"2")); |
| topics.push_back(CreateMojomTopic(200, /*model_version=*/"2")); |
| topics.push_back(CreateMojomTopic(300, /*model_version=*/"4")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/2); |
| |
| EXPECT_EQ(header_value, |
| "(100 200);v=chrome.1:1:2, (300);v=chrome.1:1:4, ();p=P00"); |
| } |
| |
| TEST_F( |
| BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_ThreeThreeDigitsTopics_LastTwoTopicVersionsSame_TwoVersionsInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(100, /*model_version=*/"2")); |
| topics.push_back(CreateMojomTopic(200, /*model_version=*/"4")); |
| topics.push_back(CreateMojomTopic(300, /*model_version=*/"4")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/2); |
| |
| EXPECT_EQ(header_value, |
| "(100);v=chrome.1:1:2, (200 300);v=chrome.1:1:4, ();p=P00"); |
| } |
| |
| TEST_F(BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_ThreeThreeDigitsTopics_ThreeTopicVersions) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(100, /*model_version=*/"20")); |
| topics.push_back(CreateMojomTopic(200, /*model_version=*/"40")); |
| topics.push_back(CreateMojomTopic(300, /*model_version=*/"60")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/3); |
| |
| EXPECT_EQ(header_value, |
| "(100);v=chrome.1:1:20, (200);v=chrome.1:1:40, " |
| "(300);v=chrome.1:1:60, ();p=P"); |
| } |
| |
| TEST_F( |
| BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_InconsistentNumTopicsVersionsAndNumVersionsInEpochs) { |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(100, /*model_version=*/"20")); |
| topics.push_back(CreateMojomTopic(200, /*model_version=*/"40")); |
| topics.push_back(CreateMojomTopic(300, /*model_version=*/"60")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/2); |
| |
| EXPECT_EQ(header_value, |
| "(100);v=chrome.1:1:20, (200);v=chrome.1:1:40, " |
| "(300);v=chrome.1:1:60, ();p=P"); |
| } |
| |
| TEST_F(BrowsingTopicsUtilTest, |
| DeriveTopicsHeaderValue_LengthExceedsDefaultMax_NoPadding) { |
| std::string config_version = base::StrCat( |
| {"chrome.", |
| base::NumberToString(browsing_topics::ConfigVersion::kMaxValue)}); |
| std::string taxonomy_version = base::NumberToString( |
| blink::features::kBrowsingTopicsTaxonomyVersion.Get()); |
| |
| std::vector<blink::mojom::EpochTopicPtr> topics; |
| topics.push_back(CreateMojomTopic(100, /*model_version=*/"20")); |
| topics.push_back(CreateMojomTopic(200, /*model_version=*/"40")); |
| topics.push_back(CreateMojomTopic(300, /*model_version=*/"600")); |
| |
| std::string header_value = |
| DeriveTopicsHeaderValue(topics, /*num_versions_in_epochs=*/3); |
| |
| EXPECT_EQ(header_value, |
| "(100);v=chrome.1:1:20, (200);v=chrome.1:1:40, " |
| "(300);v=chrome.1:1:600, ();p=P"); |
| } |
| |
| TEST_F(BrowsingTopicsUtilTest, |
| HandleTopicsEligibleResponse_TrueValueObserveTopicsHeader) { |
| network::mojom::ParsedHeadersPtr parsed_headers = |
| network::mojom::ParsedHeaders::New(); |
| parsed_headers->observe_browsing_topics = true; |
| HandleTopicsEligibleResponse( |
| parsed_headers, |
| /*caller_origin=*/url::Origin::Create(GURL("https://bar.com")), |
| *web_contents()->GetPrimaryMainFrame(), |
| browsing_topics::ApiCallerSource::kFetch); |
| |
| EXPECT_TRUE(browser_client().handle_topics_web_api_called()); |
| EXPECT_FALSE(browser_client().last_get_topics_param()); |
| EXPECT_TRUE(browser_client().last_observe_param()); |
| } |
| |
| TEST_F(BrowsingTopicsUtilTest, |
| HandleTopicsEligibleResponse_FalseValueObserveTopicsHeader) { |
| network::mojom::ParsedHeadersPtr parsed_headers = |
| network::mojom::ParsedHeaders::New(); |
| parsed_headers->observe_browsing_topics = false; |
| HandleTopicsEligibleResponse( |
| parsed_headers, |
| /*caller_origin=*/url::Origin::Create(GURL("https://bar.com")), |
| *web_contents()->GetPrimaryMainFrame(), |
| browsing_topics::ApiCallerSource::kFetch); |
| |
| EXPECT_FALSE(browser_client().handle_topics_web_api_called()); |
| } |
| |
| TEST_F(BrowsingTopicsUtilTest, HandleTopicsEligibleResponse_InactiveFrame) { |
| network::mojom::ParsedHeadersPtr parsed_headers = |
| network::mojom::ParsedHeaders::New(); |
| parsed_headers->observe_browsing_topics = true; |
| RenderFrameHostImpl& rfh = |
| static_cast<RenderFrameHostImpl&>(*web_contents()->GetPrimaryMainFrame()); |
| rfh.SetLifecycleState( |
| RenderFrameHostImpl::LifecycleStateImpl::kReadyToBeDeleted); |
| |
| HandleTopicsEligibleResponse( |
| parsed_headers, |
| /*caller_origin=*/url::Origin::Create(GURL("https://bar.com")), rfh, |
| browsing_topics::ApiCallerSource::kFetch); |
| |
| EXPECT_FALSE(browser_client().handle_topics_web_api_called()); |
| } |
| |
| } // namespace content |