910e62b5创建于 1月15日历史提交
// 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