// 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 <algorithm>
#include <optional>
#include <string_view>

#include "base/containers/fixed_flat_set.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/statistics_recorder.h"
#include "base/no_destructor.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/policy/policy_test_utils.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/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/embedder_support/switches.h"
#include "components/language/core/browser/language_prefs.h"
#include "components/language/core/browser/pref_names.h"
#include "components/language/core/common/language_experiments.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/origin_trials_controller_delegate.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "services/network/public/cpp/features.h"
#include "third_party/blink/public/common/features.h"
#include "ui/base/resource/resource_bundle.h"
#include "url/origin.h"

namespace {

using ::content::URLLoaderInterceptor;
using ::net::test_server::EmbeddedTestServer;

enum class FeatureEnableType { FeatureFlagEnable, OriginTrialEnable };

struct ReduceAcceptLanguageTestOptions {
  std::optional<std::string> content_language_in_parent;
  std::optional<std::string> avail_language_in_parent;
  std::optional<std::string> vary_in_parent;
  std::optional<std::string> content_language_in_child;
  std::optional<std::string> avail_language_in_child;
  std::optional<std::string> vary_in_child;
  bool is_fenced_frame = false;
  bool is_critical_origin_trial = false;
};

struct ServerPortAndValidOriginToken {
  int port;
  std::string token;
};

const char kLargeLanguages[] =
    "zh,zh-CN,en-US,en,af,sq,am,ar,an,hy,ast,az,bn,bs,be,eu,br,bg,nl,da,cs,hr,"
    "co,en-AU,en-CA,en-IN,en-NZ,en-ZA,en-GB-oxendict,en-GB,eo,et,fo,fil,fi,fr,"
    "fr-FR,fr-CA,fr-CH,gl,ka,de,gu,gn,el,de-CH,de-LI,de-DE,ht,is,hu,hmn,hi,he,"
    "haw,ig,ja,it-CH,it-IT,it,ga,jv,kn,kk,km,rw,ko,ku,ky,lo,mk,lb,lt,ln,lv,mg,"
    "ms,no,ne,mn,mr,mi,mt,nb,or,oc,ny,nn,pl,fa,ps,om,pt,pt-BR,my,ca,ckb,chr,"
    "ceb,zh-HK,zh-TW,la,ia,id,ha,de-AT,ml,pt-PT,sd,sn,sh,sr,gd,sm,ru,rm,mo,ro,"
    "qu,pa,es-VE,es-UY,es-US,es-ES,es-419,es-MX,es-PE,es-HN,es-CR,es-AR,es,st,"
    "so,sl,sk,si,wa,vi,uz,ug,uk,ur,yi,xh,wo,fy,cy,yo,zu,es-CL,es-CO,su,ta,sv,"
    "sw,tg,tn,to,ti,th,te,tt,tr,tk,tw";

const size_t kLargeLanguagesCount = base::SplitString(kLargeLanguages,
                                                      ",",
                                                      base::TRIM_WHITESPACE,
                                                      base::SPLIT_WANT_ALL)
                                        .size();

static constexpr const char kFirstPartyOriginUrl[] = "https://127.0.0.1:44444";
static constexpr char kThirdPartyOriginUrl[] = "https://my-site.com:44444";

// Notes: Only use to test origin trial feature with URLLoaderInterceptor.
// generate_token.py https://127.0.0.1:44444 DisableReduceAcceptLanguage
// --expire-timestamp=2000000000
static constexpr const char kValidFirstPartyToken[] =
    "A8iumEkw+XVtNR0dFIBuu6jDlDRPOxG4z9lVnq8bunWBNoV//lHIIrHkpQlzZ5Xr9sEW/"
    "0KZibE/"
    "Nrt+"
    "pC3qUQwAAABleyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0NDQiLCAiZmVhdHVyZS"
    "I6ICJEaXNhYmxlUmVkdWNlQWNjZXB0TGFuZ3VhZ2UiLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0"
    "=";

// Notes: Only use to test origin trial feature with URLLoaderInterceptor.
// generate_token.py https://my-site.com:44444 DisableReduceAcceptLanguage
// --is-third-party --expire-timestamp=2000000000
static constexpr const char kValidThirdPartyToken[] =
    "A8zLsSI/JcHxa+c6CvX2asTG2Uh62FUsb9jTZVszTHGyert8A22L/"
    "XgCdGVFjujmRtDHeAd7ctVgUr7IWWgVgAwAAAB9eyJvcmlnaW4iOiAiaHR0cHM6Ly9teS1zaXR"
    "lLmNvbTo0NDQ0NCIsICJmZWF0dXJlIjogIkRpc2FibGVSZWR1Y2VBY2NlcHRMYW5ndWFnZSIsI"
    "CJleHBpcnkiOiAyMDAwMDAwMDAwLCAiaXNUaGlyZFBhcnR5IjogdHJ1ZX0=";

// Notes: Only use to test origin trial feature with URLLoaderInterceptor.
// generate_token.py https://my-site.com:44444 DisableReduceAcceptLanguage
// --expire-timestamp=2000000000
static constexpr const char kValidMySiteFirstPartyToken[] =
    "A5z0r3ggtGZbmJt+zZWHzeLJeeXdkzmi38nNssSJet5TbRS+"
    "gdKQy9f8b5YCJvK478XVHd6fCKXOSHgxNQTV2ggAAABneyJvcmlnaW4iOiAiaHR0cHM6Ly9teS"
    "1zaXRlLmNvbTo0NDQ0NCIsICJmZWF0dXJlIjogIkRpc2FibGVSZWR1Y2VBY2NlcHRMYW5ndWFn"
    "ZSIsICJleHBpcnkiOiAyMDAwMDAwMDAwfQ==";

static constexpr const char kInvalidOriginToken[] =
    "AjfC47H1q8/Ho5ALFkjkwf9CBK6oUUeRTlFc50Dj+eZEyGGKFIY2WTxMBfy8cLc3"
    "E0nmFroDA3OmABmO5jMCFgkAAABXeyJvcmlnaW4iOiAiaHR0cDovL3ZhbGlkLmV4"
    "YW1wbGUuY29tOjgwIiwgImZlYXR1cmUiOiAiRnJvYnVsYXRlIiwgImV4cGlyeSI6"
    "IDIwMDAwMDAwMDB9";

static constexpr const char kDeprecationTrialName[] =
    "DisableReduceAcceptLanguage";

}  // namespace

class ReduceAcceptLanguageBrowserTest : public policy::PolicyTest {
 public:
  ReduceAcceptLanguageBrowserTest() = default;

  void SetUp() override {
    EnabledFeatures();
    InProcessBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    // We use a URLLoaderInterceptor, we also can use the EmbeddedTestServer.
    url_loader_interceptor_ = std::make_unique<URLLoaderInterceptor>(
        base::BindRepeating(&ReduceAcceptLanguageBrowserTest::InterceptRequest,
                            base::Unretained(this)));

    InProcessBrowserTest::SetUpOnMainThread();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // The public key for the default private key used by the
    // tools/origin_trials/generate_token.py tool.
    static constexpr char kOriginTrialTestPublicKey[] =
        "dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNA=";
    command_line->AppendSwitchASCII(embedder_support::kOriginTrialPublicKey,
                                    kOriginTrialTestPublicKey);
  }

  void TearDownOnMainThread() override {
    // Clean up any saved settings after test run.
    browser()
        ->GetProfile()
        ->GetOriginTrialsControllerDelegate()
        ->ClearPersistedTokens();

    url_loader_interceptor_.reset();
    intercepted_load_urls_.clear();
    InProcessBrowserTest::TearDownOnMainThread();
  }

  void SetTestOptions(const ReduceAcceptLanguageTestOptions& test_options,
                      const std::set<GURL>& expected_request_urls) {
    test_options_ = test_options;
    expected_request_urls_ = expected_request_urls;
  }

  GURL CreateServiceWorkerRequestUrl() const {
    return GURL(
        base::StrCat({kFirstPartyOriginUrl, "/create_service_worker.html"}));
  }

  GURL NavigationPreloadWorkerRequestUrl() const {
    return GURL(
        base::StrCat({kFirstPartyOriginUrl, "/navigation_preload_worker.js"}));
  }

  GURL SameOriginRequestUrl() const {
    return GURL(
        base::StrCat({kFirstPartyOriginUrl, "/same_origin_request.html"}));
  }

  GURL SameOriginIframeUrl() const {
    return GURL(
        base::StrCat({kFirstPartyOriginUrl, "/same_origin_iframe.html"}));
  }

  GURL SameOriginImgUrl() const {
    return GURL(base::StrCat({kFirstPartyOriginUrl, "/same_origin_img.html"}));
  }

  GURL SimpleImgUrl() const {
    return GURL(
        base::StrCat({kFirstPartyOriginUrl, "/subresource_simple.jpg"}));
  }

  GURL SimpleRequestUrl() const {
    return GURL(base::StrCat({kFirstPartyOriginUrl, "/subframe_simple.html"}));
  }

  GURL LastRequestUrl() const {
    return url_loader_interceptor_->GetLastRequestURL();
  }

  GURL CrossOriginSubresourceUrl() const {
    return GURL(
        base::StrCat({kFirstPartyOriginUrl, "/cross_origin_subresource.html"}));
  }

  GURL CrossOriginMetaTagInjectingJavascriptUrl() const {
    return GURL(base::StrCat({kThirdPartyOriginUrl, "/meta.js"}));
  }

  GURL CrossOriginCssRequestUrl() const {
    return GURL(base::StrCat(
        {kThirdPartyOriginUrl, "/subresource_redirect_style.css"}));
  }

  GURL CrossOriginSimpleImgUrl() const {
    return GURL(
        base::StrCat({kThirdPartyOriginUrl, "/subresource_simple.jpg"}));
  }

  // Navigate `url` and wait for NavigateToURL to complete, including all
  // subframes and verify whether the Accept-Language header value of last
  // request in `expected_request_urls_` is `expect_accept_language`.
  void NavigateAndVerifyAcceptLanguageOfLastRequest(
      const GURL& url,
      const std::optional<std::string>& expect_accept_language) {
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    const std::optional<std::string>& accept_language_header_value =
        GetLastAcceptLanguageHeaderValue();
    if (!expect_accept_language) {
      EXPECT_FALSE(accept_language_header_value.has_value());
    } else {
      EXPECT_TRUE(accept_language_header_value.has_value());
      EXPECT_EQ(expect_accept_language.value(),
                accept_language_header_value.value());
    }
  }

  void VerifyNavigatorLanguages(
      const std::vector<std::string>& expect_languages) {
    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    base::ListValue languages_list =
        content::EvalJs(web_contents, "navigator.languages")
            .TakeValue()
            .TakeList();
    std::vector<std::string> actual_languages;
    for (const auto& result : languages_list) {
      actual_languages.push_back(result.GetString());
    }

    EXPECT_EQ(expect_languages, actual_languages);
  }

  void SetPrefsAcceptLanguage(
      const std::vector<std::string>& accept_languages) {
    auto language_prefs = std::make_unique<language::LanguagePrefs>(
        browser()->GetProfile()->GetPrefs());
    language_prefs->SetUserSelectedLanguagesList(accept_languages);
  }

  // Mock the site set content-language behavior. If site supports the language
  // in the accept-language request header, set the content-language the same as
  // accept-language, otherwise set as the first available language.
  std::string GetResponseContentLanguage(
      const std::string& accept_language,
      const std::vector<std::string>& avail_languages) {
    auto iter = std::ranges::find(avail_languages, accept_language);
    return iter != avail_languages.end() ? *iter : avail_languages[0];
  }

 protected:
  // Return the feature list for the tests.
  virtual void EnabledFeatures() = 0;
  base::test::ScopedFeatureList scoped_feature_list_;

  // Returns whether a given |header| has been received for the last request.
  bool HasReceivedHeader(const std::string& header) const {
    return url_loader_interceptor_->GetLastRequestHeaders().HasHeader(header);
  }

  void ResetURLAndAcceptLanguageSequence() {
    actual_url_accept_language_.clear();
  }

  void VerifyURLAndAcceptLanguageSequence(
      const std::vector<std::vector<std::string>>& expect_url_accept_language,
      const std::string& message = "") {
    EXPECT_EQ(actual_url_accept_language_, expect_url_accept_language)
        << message;
  }

  // As origin trial needs to start a service in a specific port instead of
  // random port, sometime the specific port is not ready, this can cause tests
  // are flaky. Allow test server to retry on provided ports and set the origin
  // trial token if server starts succeed.
  void StartTestServerAndSetToken(
      net::EmbeddedTestServer* http_server,
      std::vector<ServerPortAndValidOriginToken> port_tokens,
      bool third_party_origin = false) {
    // Try start server in random ports.
    if (port_tokens.empty()) {
      EXPECT_TRUE(http_server->Start());
      return;
    }

    // Try different ports and assign the origin token.
    bool started = false;
    for (size_t i = 0; i < port_tokens.size(); i++) {
      LOG(INFO) << "Start server on port " << port_tokens[i].port
                << " in attempt " << i << ".";
      started = http_server->Start(port_tokens[i].port);

      if (started) {
        third_party_origin ? SetValidThirdPartyToken(port_tokens[i].token)
                           : SetValidFirstPartyToken(port_tokens[i].token);
        break;
      }
    }
    EXPECT_TRUE(started);
  }

  void SetValidFirstPartyToken(const std::string& token) {
    valid_first_party_token_ = token;
  }

  void SetValidThirdPartyToken(const std::string& token) {
    valid_third_party_token_ = token;
  }

  void SetOriginTrialFirstPartyToken(const std::string& token) {
    origin_trial_first_party_token_ = token;
  }

  void SetOriginTrialThirdPartyToken(const std::string& token) {
    origin_trial_third_party_token_ = token;
  }

  std::string GetFirstLanguage(std::string_view language_list) {
    auto end = language_list.find(",");
    return std::string(language_list.substr(0, end));
  }

  std::vector<std::vector<std::string>> actual_url_accept_language_;
  std::string origin_trial_first_party_token_;
  std::string origin_trial_third_party_token_;
  std::string valid_first_party_token_;
  std::string valid_third_party_token_;
  std::set<GURL> intercepted_load_urls_;

 private:
  // Returns the value of the Accept-Language request header from the last sent
  // request, or nullopt if the header could not be read.
  const std::optional<std::string>& GetLastAcceptLanguageHeaderValue() {
    last_accept_language_value_ =
        url_loader_interceptor_->GetLastRequestHeaders().GetHeader(
            "accept-language");
    return last_accept_language_value_;
  }

  // URLLoaderInterceptor callback
  bool InterceptRequest(URLLoaderInterceptor::RequestParams* params) {
    if (expected_request_urls_.find(params->url_request.url) ==
        expected_request_urls_.end())
      return false;

    intercepted_load_urls_.insert(params->url_request.url);

    if (params->url_request.url == CrossOriginSubresourceUrl()) {
      return RespondForCrossOriginSubResourceOriginTrialUrl(params);
    }
    if (params->url_request.url == CrossOriginMetaTagInjectingJavascriptUrl()) {
      return RespondCrossOriginMetaTagInjectingScriptUrl(params);
    }

    std::string headers = "HTTP/1.1 200 OK\r\n";
    if (params->url_request.url == NavigationPreloadWorkerRequestUrl()) {
      base::StrAppend(&headers, {"Content-Type: text/javascript\r\n"});
    } else {
      base::StrAppend(&headers, {"Content-Type: text/html\r\n"});
    }

    if (test_options_.is_fenced_frame) {
      base::StrAppend(&headers, {"Supports-Loading-Mode: fenced-frame\r\n"});
    }
    static constexpr auto kSubresourcePaths =
        base::MakeFixedFlatSet<std::string_view>({
            "/subframe_iframe_basic.html",
            "/subframe_iframe_3p.html",
            "/subframe_redirect.html",
            "/subframe_simple.html",
            "/subframe_simple_3p.html",
            "/subresource_simple.jpg",
            "/subresource_redirect_style.css",
        });
    const std::string path = params->url_request.url.GetPath();
    if (kSubresourcePaths.contains(path)) {
      base::StrAppend(&headers, {BuildSubresourceResponseHeader()});
    } else {
      base::StrAppend(&headers, {BuildResponseHeader()});
    }

    // Build mock header for the first party origin if the token is not empty.
    if (!origin_trial_first_party_token_.empty()) {
      base::StrAppend(
          &headers,
          {"Origin-Trial: ", origin_trial_first_party_token_, "\r\n"});
      if (test_options_.is_critical_origin_trial) {
        base::StrAppend(
            &headers,
            {"Critical-Origin-Trial: ", kDeprecationTrialName, "\r\n"});
      }
    }

    // Only build mock header with third party origin trial tokens for the third
    // party requests.
    const GURL origin = params->url_request.url.DeprecatedGetOriginAsURL();
    if (!origin_trial_third_party_token_.empty() &&
        origin == GURL(kThirdPartyOriginUrl)) {
      base::StrAppend(
          &headers,
          {"Origin-Trial: ", origin_trial_third_party_token_, "\r\n"});
      if (test_options_.is_critical_origin_trial) {
        base::StrAppend(
            &headers,
            {"Critical-Origin-Trial: ", kDeprecationTrialName, "\r\n"});
      }
    }

    static constexpr auto kServiceWorkerPaths =
        base::MakeFixedFlatSet<std::string_view>({
            "/create_service_worker.html",
            "/navigation_preload_worker.js",
        });

    std::string resource_path;
    if (kServiceWorkerPaths.contains(path)) {
      resource_path = "chrome/test/data/service_worker";
    } else {
      resource_path = "chrome/test/data/reduce_accept_language";
    }
    resource_path.append(
        static_cast<std::string>(params->url_request.url.path()));

    URLLoaderInterceptor::WriteResponse(resource_path, params->client.get(),
                                        &headers, std::nullopt,
                                        /*url=*/params->url_request.url);
    return true;
  }

  std::string BuildResponseHeader() {
    std::string headers;
    if (test_options_.content_language_in_parent) {
      base::StrAppend(
          &headers, {"Content-Language: ",
                     test_options_.content_language_in_parent.value(), "\r\n"});
    }
    if (test_options_.avail_language_in_parent) {
      base::StrAppend(&headers,
                      {"Avail-Language: ",
                       test_options_.avail_language_in_parent.value(), "\r\n"});
    }
    if (test_options_.vary_in_parent) {
      base::StrAppend(&headers,
                      {"Vary: ", test_options_.vary_in_parent.value(), "\n"});
    }
    return headers;
  }

  std::string BuildSubresourceResponseHeader() {
    std::string headers;
    if (test_options_.content_language_in_child) {
      base::StrAppend(
          &headers, {"Content-Language: ",
                     test_options_.content_language_in_child.value(), "\r\n"});
    }
    if (test_options_.avail_language_in_child) {
      base::StrAppend(&headers,
                      {"Avail-Language: ",
                       test_options_.avail_language_in_child.value(), "\r\n"});
    }
    if (test_options_.vary_in_child) {
      base::StrAppend(&headers,
                      {"Vary: ", test_options_.vary_in_child.value(), "\r\n"});
    }
    return headers;
  }

  bool RespondForCrossOriginSubResourceOriginTrialUrl(
      URLLoaderInterceptor::RequestParams* params) {
    if (origin_trial_third_party_token_.empty()) {
      return false;
    }
    // Construct the origin trial header response.
    std::string headers = "HTTP/1.1 200 OK\nContent-Type: text/html\n";
    std::string body = base::StrCat(
        {"<html><head><script src=\"",
         CrossOriginMetaTagInjectingJavascriptUrl().spec(), "\"></script>",
         "<link rel=\"stylesheet\" href=\"", CrossOriginCssRequestUrl().spec(),
         "\">", "</head> <body> <img src=\"", CrossOriginSimpleImgUrl().spec(),
         "\"></img> This page has no title.</body></html>"});
    URLLoaderInterceptor::WriteResponse(headers, body, params->client.get());
    return true;
  }

  bool RespondCrossOriginMetaTagInjectingScriptUrl(
      URLLoaderInterceptor::RequestParams* params) {
    if (origin_trial_third_party_token_.empty()) {
      return false;
    }
    // Construct the origin trial header response.
    std::string headers =
        "HTTP/1.1 200 OK\nContent-Type: application/javascript\n";
    std::string body =
        base::StrCat({"const otMeta = document.createElement('meta'); "
                      "otMeta.httpEquiv = 'origin-trial'; "
                      "otMeta.content = '",
                      origin_trial_third_party_token_,
                      "'; "
                      "document.head.append(otMeta); "});
    URLLoaderInterceptor::WriteResponse(headers, body, params->client.get());
    return true;
  }

  std::unique_ptr<URLLoaderInterceptor> url_loader_interceptor_;
  std::set<GURL> expected_request_urls_;
  ReduceAcceptLanguageTestOptions test_options_;
  std::optional<std::string> last_accept_language_value_;
};

// Browser tests that consider ReduceAcceptLanguage feature disabled.
class DisableFeatureReduceAcceptLanguageBrowserTest
    : public ReduceAcceptLanguageBrowserTest {
  void EnabledFeatures() override {
    scoped_feature_list_.InitWithFeatures(
        {}, {network::features::kReduceAcceptLanguage,
             network::features::kReduceAcceptLanguageHTTP});
  }
};

IN_PROC_BROWSER_TEST_F(DisableFeatureReduceAcceptLanguageBrowserTest,
                       NoAcceptLanguageHeader) {
  SetTestOptions({.content_language_in_parent = "en",
                  .avail_language_in_parent = "en, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});
  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Expect no Accept-Language header added because browser_tests can only check
  // headers in navigation layer, browser_tests can't see headers added by
  // network stack.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                               std::nullopt);
  VerifyNavigatorLanguages({"zh", "en-US"});
}

IN_PROC_BROWSER_TEST_F(DisableFeatureReduceAcceptLanguageBrowserTest,
                       IframeNoAcceptLanguageHeader) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "es",
                  .avail_language_in_child = "es, en-US",
                  .vary_in_child = "accept-language"},
                 {SameOriginIframeUrl(), SimpleRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Expect no Accept-Language header added because browser_tests can only check
  // headers in navigation layer, browser_tests can't see headers added by
  // network stack.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginIframeUrl(),
                                               std::nullopt);
  EXPECT_EQ(LastRequestUrl().GetPath(), "/subframe_simple.html");
}

// Browser tests that using Enterprise policy to control ReduceAcceptLanguage
// feature.
class ReduceAcceptLanguageEnterprisePolicyBrowserTest
    : public ReduceAcceptLanguageBrowserTest,
      public ::testing::WithParamInterface<policy::PolicyTest::BooleanPolicy> {
 public:
  static std::string DescribeParams(
      const ::testing::TestParamInfo<ParamType>& info) {
    switch (info.param) {
      case policy::PolicyTest::BooleanPolicy::kNotConfigured:
        return "NotConfigured";
      case policy::PolicyTest::BooleanPolicy::kTrue:
        return "True";
      case policy::PolicyTest::BooleanPolicy::kFalse:
        return "False";
    }
  }

  void SetUpInProcessBrowserTestFixture() override {
    policy::PolicyTest::SetUpInProcessBrowserTestFixture();

    if (GetParam() == policy::PolicyTest::BooleanPolicy::kNotConfigured) {
      return;
    }

    policy::PolicyMap policies;
    SetPolicy(
        &policies, policy::key::kReduceAcceptLanguageEnabled,
        base::Value(GetParam() == policy::PolicyTest::BooleanPolicy::kTrue));
    UpdateProviderPolicy(policies);
  }

  void EnabledFeatures() override {
    scoped_feature_list_.InitWithFeatures(
        {network::features::kReduceAcceptLanguage}, {});
  }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    ReduceAcceptLanguageEnterprisePolicyBrowserTest,
    ::testing::Values(policy::PolicyTest::BooleanPolicy::kNotConfigured,
                      policy::PolicyTest::BooleanPolicy::kFalse,
                      policy::PolicyTest::BooleanPolicy::kTrue),
    &ReduceAcceptLanguageEnterprisePolicyBrowserTest::DescribeParams);

IN_PROC_BROWSER_TEST_P(ReduceAcceptLanguageEnterprisePolicyBrowserTest,
                       PolicyIsFollowed) {
  SetTestOptions({.content_language_in_parent = "en",
                  .avail_language_in_parent = "en, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});
  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Both true and the default (no parameter) should be enabled.
  const bool expect_feature_disabled =
      GetParam() == policy::PolicyTest::BooleanPolicy::kFalse;
  if (expect_feature_disabled) {
    // Expect no Accept-Language header added because browser_tests can only
    // check headers in navigation layer, browser_tests can't see headers added
    // by network stack.
    NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                                 std::nullopt);
    VerifyNavigatorLanguages({"zh", "en-US"});
  } else {
    NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                                 "en-US,en;q=0.9");
    VerifyNavigatorLanguages({"zh"});
  }
}

IN_PROC_BROWSER_TEST_P(ReduceAcceptLanguageEnterprisePolicyBrowserTest,
                       PolicyIsFollowedIframe) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "es",
                  .avail_language_in_child = "es, en-US",
                  .vary_in_child = "accept-language"},
                 {SameOriginIframeUrl(), SimpleRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Both true and the default (no parameter) should be enabled.
  const bool expect_feature_disabled =
      GetParam() == policy::PolicyTest::BooleanPolicy::kFalse;
  if (expect_feature_disabled) {
    NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginIframeUrl(),
                                                 std::nullopt);
    VerifyNavigatorLanguages({"zh", "en-US"});
  } else {
    NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginIframeUrl(),
                                                 "en-US,en;q=0.9");
    VerifyNavigatorLanguages({"zh"});
  }
  EXPECT_EQ(LastRequestUrl().GetPath(), "/subframe_simple.html");
}

IN_PROC_BROWSER_TEST_P(ReduceAcceptLanguageEnterprisePolicyBrowserTest,
                       PolicyIsFollowedImgSubresource) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "es",
                  .avail_language_in_child = "es, en-US",
                  .vary_in_child = "accept-language"},
                 {SameOriginImgUrl(), SimpleImgUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Both true and the default (no parameter) should be enabled.
  const bool expect_feature_disabled =
      GetParam() == policy::PolicyTest::BooleanPolicy::kFalse;
  if (expect_feature_disabled) {
    NavigateAndVerifyAcceptLanguageOfLastRequest(SimpleImgUrl(), std::nullopt);
    VerifyNavigatorLanguages({"zh", "en-US"});
  } else {
    NavigateAndVerifyAcceptLanguageOfLastRequest(SimpleImgUrl(),
                                                 "en-US,en;q=0.9");
    VerifyNavigatorLanguages({"zh"});
  }
  EXPECT_EQ(LastRequestUrl().GetPath(), "/subresource_simple.jpg");
}

// Tests same origin requests with the ReduceAcceptLanguage feature enabled.
class SameOriginReduceAcceptLanguageBrowserTest
    : public ReduceAcceptLanguageBrowserTest,
      public testing::WithParamInterface<bool> {
 protected:
  void EnabledFeatures() override {
    // True: Enable the general feature for Reduce Accept-Language.
    // False: Only enable reduction for HTTP header.
    if (GetParam()) {
      scoped_feature_list_.InitWithFeatures(
          {network::features::kReduceAcceptLanguage}, {});
    } else {
      scoped_feature_list_.InitWithFeatures(
          {network::features::kReduceAcceptLanguageHTTP},
          {network::features::kReduceAcceptLanguage});
    }
  }
};

INSTANTIATE_TEST_SUITE_P(All,
                         SameOriginReduceAcceptLanguageBrowserTest,
                         testing::Bool());

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       LargeLanguageListAndScriptDisable) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});

  SetPrefsAcceptLanguage(base::SplitString(
      kLargeLanguages, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL));
  // Expect accept-language set as the negotiation language.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                               "en-US,en;q=0.9");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // same_origin_request_url request has two fetch Prefs requests: one fetch
  // for initially adding header and another one for restart fetch.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 2);
  // One store for same_origin_request_url main frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  // Disable script for first party origin.
  HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile())
      ->SetContentSettingCustomScope(
          ContentSettingsPattern::FromURL(GURL(kFirstPartyOriginUrl)),
          ContentSettingsPattern::Wildcard(), ContentSettingsType::JAVASCRIPT,
          CONTENT_SETTING_BLOCK);

  // Even Script disabled, it still expects reduced accept-language. The second
  // navigation should use the language after negotiation which is en-US.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                               "en-US,en;q=0.9");
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       EmptyUserAcceptLanguage) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});

  SetPrefsAcceptLanguage({});
  // Expect no reduced Accept-Language header set on navigation request.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                               std::nullopt);

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguagePrefValueIsEmpty", true, 1);

  // No prefs read and write operations.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 0);
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 0);
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       NoAvailLanguageHeader) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = std::nullopt,
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en"});
  // Expect accept-language set as the first user's accept-language
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(), "zh");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 1);
  // Persist won't happen.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 0);

  // Verify that navigator.languages only returns an array of length 1 if
  // ReduceAcceptLanguage is enabled. For the HTTP-only feature, it should
  // be no change and return the full list of languages.
  if (GetParam()) {
    VerifyNavigatorLanguages({"zh"});
  } else {
    VerifyNavigatorLanguages({"zh", "en"});
  }
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       NoContentLanguageHeader) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = std::nullopt,
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en"});
  // Expect accept-language set as the first user's accept-language
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(), "zh");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure metrics report correctly.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kAvailLanguageAndContentLanguageHeaderPresent=*/2, 0);
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 1);
  // Persist won't happen.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 0);
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       EmptyAvailLanguageAcceptLanguages) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en"});
  // Expect accept-language set as the first user's accept-language
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(), "zh");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // One request, one prefs fetch when initially adding header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 1);
  // Persist won't happen.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 0);
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       AvailLanguageAcceptLanguagesWhiteSpace) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "   ",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en"});
  // Expect accept-language set as the first user's accept-language
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(), "zh");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure no restart happens.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 0);
  // One request, one Prefs fetch request when initially adding header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 1);
  // Persist won't happen.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 0);
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       SiteLanguageMatchNonPrimaryLanguage) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Expect accept-language set as negotiated language: en-US.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                               "en-US,en;q=0.9");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure only restart once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);
  // One request same_origin_request_url: one Prefs fetch request when initial
  // add header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 2);
  // One store for same_origin_request_url main frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  base::HistogramTester histograms_after;
  SetTestOptions({.content_language_in_parent = "en-US",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});

  // The second request should send out with the first matched negotiation
  // language en-US instead of ja.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                               "en-US,en;q=0.9");
  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure no restart happen.
  histograms_after.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 0);
  // One request same_origin_request_url: one fetch for initially adding header
  // and no restart fetch.
  histograms_after.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 1);
  // One store for same_origin_request_url main frame.
  histograms_after.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);
}

// Verify no endless resend requests for the service worker navigation preload
// requests.
IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       ServiceWorkerNavigationPreload) {
  SetTestOptions(
      {.content_language_in_parent = "es",
       .avail_language_in_parent = "es, en-US",
       .vary_in_parent = "accept-language"},
      {CreateServiceWorkerRequestUrl(), NavigationPreloadWorkerRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  base::HistogramTester histograms;
  // Expect accept-language set as negotiated language: en-US.
  NavigateAndVerifyAcceptLanguageOfLastRequest(CreateServiceWorkerRequestUrl(),
                                               "en-US,en;q=0.9");
  // Register a service worker that uses navigation preload.
  EXPECT_EQ("DONE", EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
                           "register('/navigation_preload_worker.js', '/');"));

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Total two Prefs fetch requests: one for initially adding header and another
  // one for the restart request adding header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 2);
  // One store for create_service_worker_request_url main frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  // Verify "Service-Worker-Navigation-Preload" is present and no future resend
  // requests when site responses with expected content-language 'en-US'.
  base::HistogramTester histograms2;
  SetTestOptions(
      {.content_language_in_parent = "en-US",
       .avail_language_in_parent = "es, en-US",
       .vary_in_parent = "accept-language"},
      {CreateServiceWorkerRequestUrl(), NavigationPreloadWorkerRequestUrl()});

  NavigateAndVerifyAcceptLanguageOfLastRequest(CreateServiceWorkerRequestUrl(),
                                               "en-US,en;q=0.9");
  EXPECT_TRUE(HasReceivedHeader("Service-Worker-Navigation-Preload"));

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // One Prefs fetch request when initially adding header. No restart.
  histograms2.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 1);
  histograms2.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kServiceWorkerPreloadRequest=*/2, 1);
  // Ensure no restart happen.
  histograms2.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 0);
  histograms2.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 0);

  // Verify "Service-Worker-Navigation-Preload" is present and no future resend
  // requests even when site made mistake responding with unexpected
  // content-language 'es'.
  base::HistogramTester histograms3;
  SetTestOptions(
      {.content_language_in_parent = "es",
       .avail_language_in_parent = "es, en-US",
       .vary_in_parent = "accept-language"},
      {CreateServiceWorkerRequestUrl(), NavigationPreloadWorkerRequestUrl()});

  NavigateAndVerifyAcceptLanguageOfLastRequest(CreateServiceWorkerRequestUrl(),
                                               "en-US,en;q=0.9");
  EXPECT_TRUE(HasReceivedHeader("Service-Worker-Navigation-Preload"));

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // One Prefs fetch request when initially adding header.
  histograms3.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 1);
  histograms3.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kServiceWorkerPreloadRequest=*/2, 1);
  // Ensure no restart happen.
  histograms3.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 0);
  histograms3.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 0);
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       SiteLanguageMatchPrimaryLanguage) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});

  SetPrefsAcceptLanguage({"es", "en-US"});

  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(), "es");
  // Ensure no restart happen.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 0);

  // The second request should send out with the same preferred language.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(), "es");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // For above two same_origin_request_url requests, both only have one Prefs
  // fetch when initially adding header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 2);
  // Expect no perf storage updates.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 0);
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       SubresourceRequestNoRestart) {
  base::HistogramTester histograms;
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginImgUrl(), SimpleImgUrl()});
  SetPrefsAcceptLanguage({"es", "en-US"});

  // Initial request.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginImgUrl(), "es");
  EXPECT_EQ(LastRequestUrl().GetPath(), "/subresource_simple.jpg");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure no restart happens.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 0);
  // Total two different url requests:
  // * same_origin_img.html: one fetch for initially adding header.
  // * subresource_simple.jpg: no prefs read, it directly reads from the
  // navigation commit language.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 1);
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       SiteLanguageMatchMultipleLanguage) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US, ja",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US", "ja"});

  // Expect accept-language set as negotiated language: en-US.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                               "en-US,en;q=0.9");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure only restart once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);
  // One request same_origin_request_url: one fetch for initially adding header
  // and another one for restart fetch.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 2);
  // One store for same_origin_request_url main frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  base::HistogramTester histograms_after;
  SetTestOptions({.content_language_in_parent = "en-US",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});

  // The second request should send out with the first matched negotiation
  // language en-US instead of ja.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                               "en-US,en;q=0.9");
  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure no restart happen.
  histograms_after.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 0);
  // One request same_origin_request_url: one fetch for initially adding header
  // and no restart fetch.
  histograms_after.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 1);
  // One store for same_origin_request_url main frame.
  histograms_after.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       SiteLanguageDontMatchAnyPreferredLanguage) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "ja"});

  // Expect accept-language set as the first user's accept-language.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(), "zh");
  // Ensure no restart happen.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 0);

  // The second request should send out with the same first preferred language.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(), "zh");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // For above two same_origin_request_url requests: each has one Prefs fetch
  // request when initially adding header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 2);
  // Expect no perf storage updates.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 0);
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       PersistedAcceptLanguageNotAvailable) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, ja, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "ja", "en-US"});
  // The first request should send out with the negotiated language which is ja.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(), "ja");

  SetPrefsAcceptLanguage({"zh", "en-US"});
  // The second request should send out with the new negotiated language en-US.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                               "en-US,en;q=0.9");

  base::HistogramTester histograms;
  SetPrefsAcceptLanguage({"zh"});
  // The third request should send out with the first accept-language since the
  // persisted language not available in latest user's accept-language list.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(), "zh");
  // The previous persisted language `en-US` is not in the user's preference
  // list. Verify that a language clear operation occurred.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.ClearLatency", 1);
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       IframeReduceAcceptLanguage) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "es",
                  .avail_language_in_child = "es, en-US",
                  .vary_in_child = "accept-language"},
                 {SameOriginIframeUrl(), SimpleRequestUrl()});

  SetPrefsAcceptLanguage(base::SplitString(
      kLargeLanguages, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL));

  // Iframe request expect to be the language after language negotiation.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginIframeUrl(),
                                               "en-US,en;q=0.9");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure restart happen once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);

  // Total two different url requests:
  // * same_origin_iframe_url: one fetch for initially adding header and another
  // one for the restart request adding header.
  // * simple_request_url: one fetch for initially adding header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 3);

  EXPECT_EQ(LastRequestUrl().GetPath(), "/subframe_simple.html");

  // Disable script for first party origin.
  HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile())
      ->SetContentSettingCustomScope(
          ContentSettingsPattern::FromURL(GURL(kFirstPartyOriginUrl)),
          ContentSettingsPattern::Wildcard(), ContentSettingsType::JAVASCRIPT,
          CONTENT_SETTING_BLOCK);

  // Even Script disabled, it still expects reduced accept-language. The second
  // navigation should use the language after negotiation which is en-US.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginIframeUrl(),
                                               "en-US,en;q=0.9");
  EXPECT_EQ(LastRequestUrl().GetPath(), "/subframe_simple.html");
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       ImgSubresourceReduceAcceptLanguage) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "es",
                  .avail_language_in_child = "es, en-US",
                  .vary_in_child = "accept-language"},
                 {SameOriginImgUrl(), SimpleImgUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Subresource img request expect to be the language after language
  // negotiation.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginImgUrl(),
                                               "en-US,en;q=0.9");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure restart happen once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);
  // Total two different URL requests, only same_origin_img_url request has two
  // fetch Prefs requests: one fetch for initially adding header and another one
  // for the restart request adding header. For image request, it will directly
  // read the persisted from the navigation commit reduced accept language.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 2);
  // One store for same_origin_img_url main frame.

  EXPECT_EQ(LastRequestUrl().GetPath(), "/subresource_simple.jpg");
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       IframeNoContentLanguageInChild) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = std::nullopt,
                  .avail_language_in_child = "es, en-US",
                  .vary_in_child = "accept-language"},
                 {SameOriginIframeUrl(), SimpleRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Iframe request expect to be the language after language negotiation.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginIframeUrl(),
                                               "en-US,en;q=0.9");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure restart happen once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);
  // Total two different URL requests:
  // * same_origin_iframe_url: one fetch for initially adding header and another
  // one for the restart request adding header.
  // * simple_request_url: one fetch for initially adding header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 3);
  // One store for same_origin_iframe_url main frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  EXPECT_EQ(LastRequestUrl().GetPath(), "/subframe_simple.html");
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       IframeNoAvailLanguageAcceptLanguageInChild) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "es",
                  .avail_language_in_child = std::nullopt,
                  .vary_in_child = "accept-language"},
                 {SameOriginIframeUrl(), SimpleRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Iframe request expect to be the language after language negotiation.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginIframeUrl(),
                                               "en-US,en;q=0.9");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure restart happen once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);
  // Total two different URL requests:
  // * same_origin_iframe_url: one fetch for initially adding header and another
  // one for the restart request adding header.
  // * simple_request_url: one fetch for initially adding header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 3);
  // One store for same_origin_iframe_url main frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  EXPECT_EQ(LastRequestUrl().GetPath(), "/subframe_simple.html");
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       IframeSameContentLanguage) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "es",
                  .avail_language_in_child = "es, en-US",
                  .vary_in_child = "accept-language"},
                 {SameOriginIframeUrl(), SimpleRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Iframe request expect to be the language after language negotiation.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginIframeUrl(),
                                               "en-US,en;q=0.9");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure restart happen once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);
  // Total two different URL requests:
  // * same_origin_iframe_url: one fetch for initially adding header and another
  // one for the restart request adding header.
  // * simple_request_url: one fetch for initially adding header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 3);
  // One store for same_origin_iframe_url main frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  EXPECT_EQ(LastRequestUrl().GetPath(), "/subframe_simple.html");
}

IN_PROC_BROWSER_TEST_P(SameOriginReduceAcceptLanguageBrowserTest,
                       IframeDifferentContentLanguage) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "zh",
                  .avail_language_in_child = "zh",
                  .vary_in_child = "accept-language"},
                 {SameOriginIframeUrl(), SimpleRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Iframe request expect to be the language after language negotiation.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginIframeUrl(),
                                               "en-US,en;q=0.9");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure restart happen once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);
  // Total two different URL requests:
  // * same_origin_iframe_url: one fetch for initially adding header and another
  // one for the restart request adding header.
  // * simple_request_url: one fetch for initially adding header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 3);
  // One store for same_origin_iframe_url main frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  EXPECT_EQ(LastRequestUrl().GetPath(), "/subframe_simple.html");
}

class ThirdPartyReduceAcceptLanguageBrowserTest
    : public ReduceAcceptLanguageBrowserTest,
      public testing::WithParamInterface<bool> {
 public:
  static constexpr char kOtherSiteOriginUrl[] = "https://other-site.com:44445";
  static constexpr char kOtherSiteBOriginUrl[] =
      "https://other-site-b.com:44445";

  GURL CrossOriginIframeUrl() const {
    return GURL(
        base::StrCat({kFirstPartyOriginUrl, "/cross_origin_iframe.html"}));
  }

  GURL TopLevelWithIframeRedirectUrl() const {
    return GURL(base::StrCat(
        {kFirstPartyOriginUrl, "/top_level_with_iframe_redirect.html"}));
  }

  GURL CrossOriginIframeWithSubresourceUrl() const {
    return GURL(base::StrCat(
        {kFirstPartyOriginUrl, "/cross_origin_iframe_with_subrequests.html"}));
  }

  GURL SubframeThirdPartyRequestUrl() const {
    return GURL(
        base::StrCat({kThirdPartyOriginUrl, "/subframe_redirect_3p.html"}));
  }

  GURL SimpleThirdPartyRequestUrl() const {
    return GURL(
        base::StrCat({kThirdPartyOriginUrl, "/subframe_simple_3p.html"}));
  }

  GURL IframeThirdPartyRequestUrl() const {
    return GURL(
        base::StrCat({kThirdPartyOriginUrl, "/subframe_iframe_3p.html"}));
  }

  GURL OtherSiteCssRequestUrl() const {
    return GURL(
        base::StrCat({kOtherSiteOriginUrl, "/subresource_redirect_style.css"}));
  }

  GURL OtherSiteBasicRequestUrl() const {
    return GURL(
        base::StrCat({kOtherSiteBOriginUrl, "/subframe_iframe_basic.html"}));
  }

 protected:
  void EnabledFeatures() override {
    // True: Enable the general feature for Reduce Accept-Language.
    // False: Only enable reduction for HTTP header.
    if (GetParam()) {
      scoped_feature_list_.InitWithFeatures(
          {network::features::kReduceAcceptLanguage}, {});
    } else {
      scoped_feature_list_.InitWithFeatures(
          {network::features::kReduceAcceptLanguageHTTP},
          {network::features::kReduceAcceptLanguage});
    }
  }
};

INSTANTIATE_TEST_SUITE_P(All,
                         ThirdPartyReduceAcceptLanguageBrowserTest,
                         testing::Bool());

IN_PROC_BROWSER_TEST_P(ThirdPartyReduceAcceptLanguageBrowserTest,
                       IframeDifferentContentLanguage) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "zh",
                  .avail_language_in_child = "zh",
                  .vary_in_child = "accept-language"},
                 {CrossOriginIframeUrl(), SimpleThirdPartyRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Third party iframe subrequest expect to be the language of the main frame
  // after language negotiation.
  NavigateAndVerifyAcceptLanguageOfLastRequest(CrossOriginIframeUrl(),
                                               "en-US,en;q=0.9");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure restart happen once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);
  // Total two different URL requests:
  // * cross_origin_iframe_url: one fetch for initially adding header and
  // another one for the restart request adding header.
  // * simple_3p_request_url: one fetch for initially adding header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 3);
  // One store for same_origin_iframe_url main frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  EXPECT_EQ(LastRequestUrl().GetPath(), "/subframe_simple_3p.html");
}

IN_PROC_BROWSER_TEST_P(ThirdPartyReduceAcceptLanguageBrowserTest,
                       ThirdPartyIframeWithSubresourceRequests) {
  base::HistogramTester histograms;

  SetTestOptions(
      {.content_language_in_parent = "es",
       .avail_language_in_parent = "es, en-US",
       .vary_in_parent = "accept-language",
       .content_language_in_child = "zh",
       .avail_language_in_child = "zh",
       .vary_in_child = "accept-language"},
      {CrossOriginIframeWithSubresourceUrl(), IframeThirdPartyRequestUrl(),
       OtherSiteCssRequestUrl(), OtherSiteBasicRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Third party iframe subrequest expect to be the language of the main frame
  // after language negotiation.
  NavigateAndVerifyAcceptLanguageOfLastRequest(
      CrossOriginIframeWithSubresourceUrl(), "en-US,en;q=0.9");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure restart happen once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);
  // Fetch reduce accept-language when visiting the following three URLs, for
  // css request, it won't pass to navigation layer:
  // * cross_origin_iframe_with_subrequests_url(2):one fetch for initially
  // adding header and another one for the restart request adding header.
  // * iframe_3p_request_url(1): one fetch for initially adding header.
  // * other_site_b_basic_request_url(1): one fetch for initially adding header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 4);
  // One store for cross_region_iframe_url main frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  // All subresources should have been loaded,
  EXPECT_THAT(intercepted_load_urls_,
              testing::IsSupersetOf({IframeThirdPartyRequestUrl(),
                                     OtherSiteCssRequestUrl(),
                                     OtherSiteBasicRequestUrl()}));
}

IN_PROC_BROWSER_TEST_P(ThirdPartyReduceAcceptLanguageBrowserTest,
                       ThirdPartyIframeWithSubresourceRedirectRequests) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "zh",
                  .avail_language_in_child = "zh",
                  .vary_in_child = "accept-language"},
                 {TopLevelWithIframeRedirectUrl(),
                  SubframeThirdPartyRequestUrl(), OtherSiteCssRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // It still expected an accept-language header has the reduced value even the
  // final url is a css style document,
  NavigateAndVerifyAcceptLanguageOfLastRequest(TopLevelWithIframeRedirectUrl(),
                                               "en-US,en;q=0.9");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure restart happen once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);
  // Fetch reduce accept-language when visiting the following three URLs, for
  // css request, it won't pass to navigation layer:
  // * top_level_with_iframe_redirect_url(2):one fetch for initially adding
  // header and another one for the restart request adding header.
  // * subframe_3p_request_url(1): one fetch for initially adding header.
  // * other_site_css_request_url(0): directly read from commit parameter.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 3);
  // One store for top_level_with_iframe_redirect_url main frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  // All subresources should have been loaded,
  EXPECT_THAT(intercepted_load_urls_,
              testing::IsSupersetOf(
                  {SubframeThirdPartyRequestUrl(), OtherSiteCssRequestUrl()}));
}

class FencedFrameReduceAcceptLanguageBrowserTest
    : public ReduceAcceptLanguageBrowserTest {
 public:
  static constexpr char kFirstPartyOriginUrl[] = "https://127.0.0.1:44444";
  static constexpr char kThirdPartyOriginUrl[] = "https://my-site.com:44444";

  GURL SameOriginFencedFrameUrl() const {
    return GURL(
        base::StrCat({kFirstPartyOriginUrl, "/same_origin_fenced_frame.html"}));
  }

  GURL CrossOriginFencedFrameUrl() const {
    return GURL(base::StrCat(
        {kFirstPartyOriginUrl, "/cross_origin_fenced_frame.html"}));
  }

  GURL SimpleRequestUrl() const {
    return GURL(base::StrCat({kFirstPartyOriginUrl, "/subframe_simple.html"}));
  }

  GURL SimpleThirdPartyRequestUrl() const {
    return GURL(
        base::StrCat({kThirdPartyOriginUrl, "/subframe_simple_3p.html"}));
  }

 protected:
  void EnabledFeatures() override {
    scoped_feature_list_.InitWithFeaturesAndParameters(
        {{blink::features::kFencedFrames, {}},
         {blink::features::kFencedFramesAPIChanges, {}},
         {blink::features::kFencedFramesDefaultMode, {}},
         {features::kPrivacySandboxAdsAPIsOverride, {}},
         {network::features::kReduceAcceptLanguage, {}}},
        {/* disabled_features */});
  }
};

IN_PROC_BROWSER_TEST_F(FencedFrameReduceAcceptLanguageBrowserTest,
                       CrossOriginFencedFrame) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "zh",
                  .avail_language_in_child = "zh",
                  .vary_in_child = "accept-language",
                  .is_fenced_frame = true},
                 {CrossOriginFencedFrameUrl(), SimpleThirdPartyRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // The result of the main frame's language negotiation should not be shared
  // with requests made from fenced frames, since fenced frames restrict
  // communication with their outer page. After language negotiation, the
  // persisted language is en-US. The third party fenced frame requests should
  // use the first accept-language zh instead of en-US.
  NavigateAndVerifyAcceptLanguageOfLastRequest(CrossOriginFencedFrameUrl(),
                                               "zh");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure restart happen once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);
  // Total two different URL requests:
  // * cross_region_fenced_frame_url(2):one fetch for initially adding
  // header and another one for the restart request adding header.
  // * simple_3p_request_url: no fetch for initially adding header since a
  // fenced frame but not a main frame will result in a nullopt origin value
  // when getting top-level main frame origin. In this case, we set the
  // Accept-Language header with the first user’s accept-language.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 2);
  // One store for cross_region_fenced_frame_url main frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  EXPECT_EQ(LastRequestUrl().GetPath(), "/subframe_simple_3p.html");
}

IN_PROC_BROWSER_TEST_F(FencedFrameReduceAcceptLanguageBrowserTest,
                       SameOriginFencedFrame) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "zh",
                  .avail_language_in_child = "zh",
                  .vary_in_child = "accept-language",
                  .is_fenced_frame = true},
                 {SameOriginFencedFrameUrl(), SimpleRequestUrl()});

  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Main frame after language negotiation should not shared to fenced frame
  // subrequest since restricts communication.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginFencedFrameUrl(),
                                               "zh");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure restart happen once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);
  // Total two different URL requests:
  // * same_origin_fenced_frame_url(2):one fetch for initially adding
  // header and another one for the restart request adding header.
  // * simple_request_url: no fetch for initially adding header since a fenced
  // frame but not a main frame will result in a nullopt origin value when
  // getting top-level main frame origin. In this case, we set the
  // Accept-Language header with the first user’s accept-language.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 2);
  // One store for cross_region_fenced_frame_url main frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  EXPECT_EQ("/subframe_simple.html", LastRequestUrl().GetPath());
}

// Browser tests verify redirect same origin with different cases.
class SameOriginRedirectReduceAcceptLanguageBrowserTest
    : public ReduceAcceptLanguageBrowserTest {
 public:
  explicit SameOriginRedirectReduceAcceptLanguageBrowserTest(
      const std::vector<ServerPortAndValidOriginToken>& port_tokens = {})
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
    https_server_.ServeFilesFromSourceDirectory(
        "chrome/test/data/reduce_accept_language");

    https_server_.RegisterRequestMonitor(
        base::BindRepeating(&SameOriginRedirectReduceAcceptLanguageBrowserTest::
                                MonitorResourceRequest,
                            base::Unretained(this)));

    https_server_.RegisterRequestHandler(
        base::BindRepeating(&SameOriginRedirectReduceAcceptLanguageBrowserTest::
                                RequestHandlerRedirect,
                            base::Unretained(this)));

    // Using a specified port for origin trial to generate token instead of
    // always using an auto selected one.
    StartTestServerAndSetToken(&https_server_, port_tokens);

    same_origin_redirect_ = https_server_.GetURL("/same_origin_redirect.html");
    same_origin_redirect_a_ =
        https_server_.GetURL("/same_origin_redirect_a.html");
    same_origin_redirect_b_ =
        https_server_.GetURL("/same_origin_redirect_b.html");
  }

  static constexpr const char kAcceptLanguage[] = "accept-language";
  static constexpr auto kValidPaths = base::MakeFixedFlatSet<std::string_view>({
      "/same_origin_redirect.html",
      "/same_origin_redirect_a.html",
      "/same_origin_redirect_b.html",
  });

  GURL same_origin_redirect() const { return same_origin_redirect_; }

  GURL same_origin_redirect_a() const { return same_origin_redirect_a_; }

  GURL same_origin_redirect_b() const { return same_origin_redirect_b_; }

  void SetOptions(const std::string& content_language_a,
                  const std::string& content_language_b) {
    content_language_a_ = content_language_a;
    content_language_b_ = content_language_b;
  }

 protected:
  void EnabledFeatures() override {
    // Explicit enable feature ReduceAcceptLanguage.
    scoped_feature_list_.InitAndEnableFeature(
        {network::features::kReduceAcceptLanguage});
  }

 private:
  // Intercepts only the requests that for same origin redirect tests.
  std::unique_ptr<net::test_server::HttpResponse> RequestHandlerRedirect(
      const net::test_server::HttpRequest& request) {
    if (!kValidPaths.contains(request.relative_url)) {
      return nullptr;
    }

    std::string accept_language;
    if (request.headers.find(kAcceptLanguage) != request.headers.end()) {
      accept_language =
          GetFirstLanguage(request.headers.find(kAcceptLanguage)->second);
    }

    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
    if (request.relative_url == "/same_origin_redirect.html") {
      response->set_code(net::HTTP_FOUND);
      // Assume site supports content_language_a_ and content_language_b_. If
      // accept-language matches content_language_b_ then returns
      // content_language_b_, otherwise returns content_language_a_.
      if (accept_language == content_language_b_) {
        response->AddCustomHeader("Content-Language", content_language_b_);
        response->AddCustomHeader("Location", same_origin_redirect_b().spec());
      } else {
        response->AddCustomHeader("Content-Language", content_language_a_);
        response->AddCustomHeader("Location", same_origin_redirect_a().spec());
      }
    } else if (request.relative_url == "/same_origin_redirect_a.html") {
      response->set_code(net::HTTP_OK);
      response->AddCustomHeader("Content-Language", content_language_a_);
    } else if (request.relative_url == "/same_origin_redirect_b.html") {
      response->set_code(net::HTTP_OK);
      response->AddCustomHeader("Content-Language", content_language_b_);
    }

    response->AddCustomHeader(
        "Avail-Language",
        base::StrCat({content_language_a_, ", ", content_language_b_}));

    if (!origin_trial_first_party_token_.empty()) {
      response->AddCustomHeader("Origin-Trial",
                                origin_trial_first_party_token_);
    }

    return std::move(response);
  }

  // Called by `https_server_`.
  void MonitorResourceRequest(const net::test_server::HttpRequest& request) {
    if (!kValidPaths.contains(request.relative_url)) {
      return;
    }

    if (request.headers.find(kAcceptLanguage) != request.headers.end()) {
      actual_url_accept_language_.push_back(
          {request.GetURL().spec(),
           request.headers.find(kAcceptLanguage)->second});
    }
  }

  GURL same_origin_redirect_;
  GURL same_origin_redirect_a_;
  GURL same_origin_redirect_b_;
  net::EmbeddedTestServer https_server_;
  std::string content_language_a_;
  std::string content_language_b_;
};

IN_PROC_BROWSER_TEST_F(SameOriginRedirectReduceAcceptLanguageBrowserTest,
                       MatchFirstLanguage) {
  SetPrefsAcceptLanguage({"en", "ja"});
  SetOptions(/*content_language_a=*/"en", /*content_language_b=*/"ja");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), same_origin_redirect()));

  // 1. initial request to main request(/) with first user accept-language en.
  // 2. initial request to A(/en) with the language matches the expected
  // accept-language.
  VerifyURLAndAcceptLanguageSequence({{same_origin_redirect().spec(), "en"},
                                      {same_origin_redirect_a().spec(), "en"}});
}

IN_PROC_BROWSER_TEST_F(SameOriginRedirectReduceAcceptLanguageBrowserTest,
                       MatchSecondaryLanguage) {
  SetPrefsAcceptLanguage({"zh-CN", "ja"});
  SetOptions(/*content_language_a=*/"en", /*content_language_b=*/"ja");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), same_origin_redirect()));

  // 1. initial request to main request(/) with first user accept-language
  // zh-CN.
  // 2. restart request to main request(/) with the persisted language ja after
  // language negotiation.
  // 3. initial request to B(/ja) with the language matches the expected
  // accept-language.
  VerifyURLAndAcceptLanguageSequence(
      {{same_origin_redirect().spec(), "zh-CN,zh;q=0.9"},
       {same_origin_redirect().spec(), "ja"},
       {same_origin_redirect_b().spec(), "ja"}});
}

// Browser tests verify redirect cross origin A to B with different cases.
class CrossOriginRedirectReduceAcceptLanguageBrowserTest
    : public ReduceAcceptLanguageBrowserTest {
 public:
  explicit CrossOriginRedirectReduceAcceptLanguageBrowserTest(
      const std::vector<ServerPortAndValidOriginToken>& port_tokens_a = {},
      const std::vector<ServerPortAndValidOriginToken>& port_tokens_b = {})
      : https_server_a_(net::EmbeddedTestServer::TYPE_HTTPS),
        https_server_b_(net::EmbeddedTestServer::TYPE_HTTPS) {
    https_server_a_.ServeFilesFromSourceDirectory(
        "chrome/test/data/reduce_accept_language");
    https_server_b_.ServeFilesFromSourceDirectory(
        "chrome/test/data/reduce_accept_language");

    https_server_a_.RegisterRequestMonitor(base::BindRepeating(
        &CrossOriginRedirectReduceAcceptLanguageBrowserTest::
            MonitorResourceRequest,
        base::Unretained(this)));

    https_server_a_.RegisterRequestHandler(base::BindRepeating(
        &CrossOriginRedirectReduceAcceptLanguageBrowserTest::
            RequestHandlerRedirect,
        base::Unretained(this)));

    https_server_b_.RegisterRequestMonitor(base::BindRepeating(
        &CrossOriginRedirectReduceAcceptLanguageBrowserTest::
            MonitorResourceRequest,
        base::Unretained(this)));
    https_server_b_.RegisterRequestHandler(base::BindRepeating(
        &CrossOriginRedirectReduceAcceptLanguageBrowserTest::
            RequestHandlerRedirect,
        base::Unretained(this)));

    // Using a specified port for origin trial to generate token instead of
    // always using an auto selected one.
    StartTestServerAndSetToken(&https_server_a_, port_tokens_a);
    StartTestServerAndSetToken(&https_server_b_, port_tokens_b, true);

    // Make sure two origins are different.
    EXPECT_NE(https_server_a_.base_url(), https_server_b_.base_url());
    cross_origin_redirect_a_ =
        https_server_a_.GetURL("/cross_origin_redirect_a.html");
    cross_origin_redirect_b_ =
        https_server_b_.GetURL("/cross_origin_redirect_b.html");
  }

  static constexpr const char kAcceptLanguage[] = "accept-language";
  static constexpr auto kValidPaths = base::MakeFixedFlatSet<std::string_view>({
      "/cross_origin_redirect_a.html",
      "/cross_origin_redirect_b.html",
  });

  GURL cross_origin_redirect_a() const { return cross_origin_redirect_a_; }

  GURL cross_origin_redirect_b() const { return cross_origin_redirect_b_; }

  void SetOptions(const std::vector<std::string> avail_language_a,
                  const std::vector<std::string> avail_language_b) {
    avail_language_a_ = avail_language_a;
    avail_language_b_ = avail_language_b;
  }

  void SetOriginTrialFirstPartyToken(const std::string& origin_trial_token_a,
                                     const std::string& origin_trial_token_b) {
    origin_trial_token_a_ = origin_trial_token_a;
    origin_trial_token_b_ = origin_trial_token_b;
  }

 protected:
  void EnabledFeatures() override {
    std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
    feature_list->InitFromCommandLine("ReduceAcceptLanguage", "");
    scoped_feature_list_.InitWithFeatureList(std::move(feature_list));
  }

 private:
  // Intercepts only the requests that for cross origin redirect tests.
  std::unique_ptr<net::test_server::HttpResponse> RequestHandlerRedirect(
      const net::test_server::HttpRequest& request) {
    if (!kValidPaths.contains(request.relative_url)) {
      return nullptr;
    }

    std::string accept_language;
    if (request.headers.find(kAcceptLanguage) != request.headers.end())
      accept_language = request.headers.find(kAcceptLanguage)->second;

    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
    if (request.relative_url == "/cross_origin_redirect_a.html") {
      response->set_code(net::HTTP_FOUND);
      response->AddCustomHeader(
          "Content-Language",
          GetResponseContentLanguage(accept_language, avail_language_a_));

      // Stop sending Avail-Language header as well if tests set an invalid
      // origin token.
      response->AddCustomHeader("Avail-Language",
                                base::JoinString(avail_language_a_, ", "));

      response->AddCustomHeader("Location", cross_origin_redirect_b().spec());
      if (!origin_trial_token_a_.empty()) {
        response->AddCustomHeader("Origin-Trial", origin_trial_token_a_);
      }
    } else if (request.relative_url == "/cross_origin_redirect_b.html") {
      response->set_code(net::HTTP_OK);
      response->AddCustomHeader(
          "Content-Language",
          GetResponseContentLanguage(accept_language, avail_language_b_));
      response->AddCustomHeader("Avail-Language",
                                base::JoinString(avail_language_b_, ", "));
      if (!origin_trial_token_b_.empty()) {
        response->AddCustomHeader("Origin-Trial", origin_trial_token_b_);
      }
    }
    return std::move(response);
  }

  // Called by `https_server_`.
  void MonitorResourceRequest(const net::test_server::HttpRequest& request) {
    if (!kValidPaths.contains(request.relative_url)) {
      return;
    }

    if (request.headers.find(kAcceptLanguage) != request.headers.end()) {
      actual_url_accept_language_.push_back(
          {request.GetURL().spec(),
           request.headers.find(kAcceptLanguage)->second});
    }
  }

  GURL cross_origin_redirect_a_;
  GURL cross_origin_redirect_b_;
  net::EmbeddedTestServer https_server_a_;
  net::EmbeddedTestServer https_server_b_;
  std::vector<std::string> avail_language_a_;
  std::vector<std::string> avail_language_b_;
  std::string origin_trial_token_a_;
  std::string origin_trial_token_b_;
};

IN_PROC_BROWSER_TEST_F(CrossOriginRedirectReduceAcceptLanguageBrowserTest,
                       RestartOnA) {
  SetPrefsAcceptLanguage({"en-US", "zh"});
  SetOptions(/*avail_language_a=*/{"ja", "zh"},
             /*avail_language_b=*/{"en-US"});

  // initial redirect request.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));

  // 1. initial request to A with first user accept-language en-US.
  // 2. restart request to A with the persisted language zh.
  // 3. initial request to B with the first user accept-language en-US.
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9"}});

  ResetURLAndAcceptLanguageSequence();

  // Secondary redirect request expects no restarts.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9"}});
}

IN_PROC_BROWSER_TEST_F(CrossOriginRedirectReduceAcceptLanguageBrowserTest,
                       RestartOnB) {
  SetPrefsAcceptLanguage({"en-US", "zh"});
  SetOptions(/*avail_language_a=*/{"en-US", "zh"},
             /*avail_language_b=*/{"de", "zh"});

  // initial redirect request.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));

  // 1. initial request to A with first user accept-language en-US.
  // 2. initial request to B with the first user accept-language en-US.
  // 3. restart request to A with first user accept-language en-US.
  // 4. restart request to B with the persisted language zh.
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_a().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_b().spec(), "zh"}});

  ResetURLAndAcceptLanguageSequence();

  // Secondary redirect request expects no restarts.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_b().spec(), "zh"}});
}

IN_PROC_BROWSER_TEST_F(CrossOriginRedirectReduceAcceptLanguageBrowserTest,
                       RestartBothAB) {
  SetPrefsAcceptLanguage({"en-US", "zh"});
  SetOptions(/*avail_language_a=*/{"ja", "zh"},
             /*avail_language_b=*/{"de", "zh"});

  // initial redirect request.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));

  // 1. initial request to A with first user accept-language en-US.
  // 2. restart request to A with the persisted language zh.
  // 3. initial request to B with the first user accept-language en-US.
  // 4. restart request to A since redirect the original URL with persisted
  // language zh.
  // 5. restart request to B with the persisted language zh.
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "zh"}});

  ResetURLAndAcceptLanguageSequence();

  // Secondary redirect request expects no restarts.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "zh"}});
}

// Browser tests verify same origin redirect when DisableReduceAcceptLanguage
// deprecation origin trial enable.
// NOTES: As URLLoaderInterceptor doesn't support redirect, testing redirects
// with origin trial requires EmbeddedTestServer to start on specific ports, we
// can only add a single test in this test class in case the different tests run
// parallel to cause server can't starts on specific ports. It will cause tests
// flakiness. Also, we need to make sure it doesn't share port with any other
// browser_tests.
class SameOriginRedirectReduceAcceptLanguageOTBrowserTest
    : public SameOriginRedirectReduceAcceptLanguageBrowserTest {
 public:
  SameOriginRedirectReduceAcceptLanguageOTBrowserTest()
      : SameOriginRedirectReduceAcceptLanguageBrowserTest(
            GetValidPortsAndTokens()) {
    // Initialize with valid origin trial token.
    SetOriginTrialFirstPartyToken(GetValidFirstPartyToken());
  }

  // Work around solution to test redirect using EmbeddedTestServer. Make a list
  // port and corresponding OT token for test server to retry if port in use.
  // generate_token.py https://127.0.0.1:44455 DisableReduceAcceptLanguage
  // --expire-timestamp=2000000000
  const std::vector<ServerPortAndValidOriginToken>& GetValidPortsAndTokens() {
    static const base::NoDestructor<std::vector<ServerPortAndValidOriginToken>>
        vec({
            {44455,
             "A1IOD8fXBTZ01WHMaBk9MqqvmiLPtIioHYEpcPn7kLtRHqJNL4pwZguJErdl+"
             "hIXpDYbR+"
             "7VnhXtv7YtEyaJzgoAAABleyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ"
             "0NTUiLCAiZmVhdHVyZSI6ICJEaXNhYmxlUmVkdWNlQWNjZXB0TGFuZ3VhZ2UiLCAi"
             "ZXhwaXJ5IjogMjAwMDAwMDAwMH0="},
            {44456,
             "A+QbN+WXtJCFPhyFkS2uW0VU3DdceOtQvO/"
             "8ZYL9CgicyLVyZQngYWZeahtT2Hy3978TOwrCD7D+"
             "AJGo1eseqAcAAABleyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0NTYiL"
             "CAiZmVhdHVyZSI6ICJEaXNhYmxlUmVkdWNlQWNjZXB0TGFuZ3VhZ2UiLCAiZXhwaX"
             "J5IjogMjAwMDAwMDAwMH0="},
            {44457,
             "A75oT3Ki0N9WCQNOlzmB8+1s3pJMdNIT1DqeXkjF1LF8Xg6rfK65Z/"
             "bDKuBzDNMhTgoD5fN+"
             "RBRcVG8jLLWhSQ0AAABleyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0N"
             "TciLCAiZmVhdHVyZSI6ICJEaXNhYmxlUmVkdWNlQWNjZXB0TGFuZ3VhZ2UiLCAiZX"
             "hwaXJ5IjogMjAwMDAwMDAwMH0="},
            {44458,
             "A3tN+D5Qyma6ozNdZPQIyu32bgG1Nwb3rQzwP8Su+"
             "57FbFQTGSXu3Wpr0HHOhkdKk50FVs849XJEv1pMjwDb7QQAAABleyJvcmlnaW4iOi"
             "AiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0NTgiLCAiZmVhdHVyZSI6ICJEaXNhYmxlUmV"
             "kdWNlQWNjZXB0TGFuZ3VhZ2UiLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0="},
            {44459,
             "A2bAXhaCHO5WNgU4xkHLG7UeYzD4lVlXBJxAO0/7U/"
             "Rie3s82v4AfWaOCZOd0YvOgxoTw8WVQZObpgIGkyQrQgYAAABleyJvcmlnaW4iOiA"
             "iaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0NTkiLCAiZmVhdHVyZSI6ICJEaXNhYmxlUmVk"
             "dWNlQWNjZXB0TGFuZ3VhZ2UiLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0="},
        });
    return *vec;
  }

  std::string GetValidFirstPartyToken() { return valid_first_party_token_; }

 protected:
  void EnabledFeatures() override {
    // Explicit enable feature ReduceAcceptLanguage.
    scoped_feature_list_.InitAndEnableFeature(
        {network::features::kReduceAcceptLanguage});
  }
};

IN_PROC_BROWSER_TEST_F(SameOriginRedirectReduceAcceptLanguageOTBrowserTest,
                       MatchFirstLanguage) {
  // Match the first language
  SetPrefsAcceptLanguage({"en", "ja"});
  SetOptions(/*content_language_a=*/"en", /*content_language_b=*/"ja");
  SetOriginTrialFirstPartyToken(GetValidFirstPartyToken());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), same_origin_redirect()));

  // First Request, opt-in the deprecation origin trial.
  // 1. initial request to main request(/) with reduced accept-language since
  // we can't validate deprecation origin trial token before sending requests.
  // 2. initial request to A(/en) with the reduced language en which persisted
  // when process request to main request(/).
  VerifyURLAndAcceptLanguageSequence(
      {{same_origin_redirect().spec(), "en"},
       {same_origin_redirect_a().spec(), "en"}},
      "Verifying the first request sequence failed in matching first "
      "language.");

  // Second request.
  ResetURLAndAcceptLanguageSequence();
  // 1. Second request to main request(/) with the unreduced accept-language.
  // 2. Second request to A(/en) with the unreduced accept-language en.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), same_origin_redirect()));
  VerifyURLAndAcceptLanguageSequence(
      {{same_origin_redirect().spec(), "en,ja;q=0.9"},
       {same_origin_redirect_a().spec(), "en,ja;q=0.9"}},
      "Verifying the second request sequence failed in matching first "
      "language.");

  // Third Request: reset deprecation origin trial token to be invalid, opt-out
  // deprecation trial.
  SetOriginTrialFirstPartyToken(kInvalidOriginToken);
  ResetURLAndAcceptLanguageSequence();
  // 1. Third request to main request(/) with the unreduced accept-language.
  // 2. Third request to A(/en) with the unreduced accept-language.
  // All persisted languages for the givin origin should be cleaned in this
  // request, all subsequent requests should start sending unreduced
  // accept-language.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), same_origin_redirect()));
  VerifyURLAndAcceptLanguageSequence(
      {{same_origin_redirect().spec(), "en,ja;q=0.9"},
       {same_origin_redirect_a().spec(), "en,ja;q=0.9"}},
      "Verifying the third request sequence failed in matching first "
      "language.");

  // Fourth request.
  ResetURLAndAcceptLanguageSequence();
  // 1. Fourth request to main request(/) with the reduced accept-language en.
  // 2. Fourth request to A(/en) with the reduced accept-language en.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), same_origin_redirect()));
  VerifyURLAndAcceptLanguageSequence(
      {{same_origin_redirect().spec(), "en"},
       {same_origin_redirect_a().spec(), "en"}},
      "Verifying the fourth request sequence failed in matching first "
      "language.");
}

IN_PROC_BROWSER_TEST_F(SameOriginRedirectReduceAcceptLanguageOTBrowserTest,
                       MatchNonPrimaryLanguage) {
  // Match non primary language
  SetPrefsAcceptLanguage({"zh-CN", "ja"});
  SetOptions(/*content_language_a=*/"en", /*content_language_b=*/"ja");
  SetOriginTrialFirstPartyToken(GetValidFirstPartyToken());

  ResetURLAndAcceptLanguageSequence();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), same_origin_redirect()));

  // First Request, opt-in the deprecation origin trial.
  // 1. initial request to main request(/) with reduced accept-language zh-CN
  // since we can't validate deprecation origin trial token before sending
  // requests.
  // 2. restart request to main request(/) with the persisted language ja after
  // language negotiation.
  // 3. initial request to B(/ja) with the language matches the expected
  // accept-language.
  VerifyURLAndAcceptLanguageSequence(
      {{same_origin_redirect().spec(), "zh-CN,zh;q=0.9"},
       {same_origin_redirect().spec(), "ja"},
       {same_origin_redirect_b().spec(), "ja"}},
      "Verifying the first request sequence failed in matching non-primary "
      "language.");

  // Second request.
  ResetURLAndAcceptLanguageSequence();
  // 1. Second request to main request(/) with unreduced accept-language.
  // 2. Second request to B(/ja) with unreduced accept-language.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), same_origin_redirect()));
  VerifyURLAndAcceptLanguageSequence(
      {{same_origin_redirect().spec(), "zh-CN,zh;q=0.9,ja;q=0.8"},
       {same_origin_redirect_a().spec(), "zh-CN,zh;q=0.9,ja;q=0.8"}},
      "Verifying the second request sequence failed in matching non-primary "
      "language.");

  // Third Request: reset deprecation origin trial token to be invalid, this is
  // also the first request opt-out deprecation trial.
  SetOriginTrialFirstPartyToken(kInvalidOriginToken);
  ResetURLAndAcceptLanguageSequence();
  // 1. Third request to main request(/) with unreduced accept-language.
  // 2. Third request to A(/en) with unreduced accept-language.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), same_origin_redirect()));
  VerifyURLAndAcceptLanguageSequence(
      {{same_origin_redirect().spec(), "zh-CN,zh;q=0.9,ja;q=0.8"},
       {same_origin_redirect_a().spec(), "zh-CN,zh;q=0.9,ja;q=0.8"}},
      "Verifying the third request sequence failed in matching non-primary "
      "language.");

  // Fourth request.
  ResetURLAndAcceptLanguageSequence();
  // 1. Fourth request to main request(/) with reduced accept-language zh-CN
  // since site opt-out the deprecation origin trial.
  // 2. restart request to main request(/) with the persisted language ja after
  // language negotiation.
  // 3. Fourth request to B(/ja) with the language matches the expected
  // accept-language.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), same_origin_redirect()));
  VerifyURLAndAcceptLanguageSequence(
      {{same_origin_redirect().spec(), "zh-CN,zh;q=0.9"},
       {same_origin_redirect().spec(), "ja"},
       {same_origin_redirect_b().spec(), "ja"}},
      "Verifying the fourth request sequence failed in matching non-primary "
      "language.");
}

// Browser tests verify cross origin redirect when DisableReduceAcceptLanguage
// origin trial enable.
// NOTES: As URLLoaderInterceptor doesn't support redirect, testing redirects
// with origin trial requires EmbeddedTestServer to start on specific ports, we
// can only add a single test in this test class in case the different tests run
// parallel to cause server can't starts on specific ports. It will cause tests
// flakiness. Also, we need to make sure it doesn't share port with any other
// browser_tests.
class CrossOriginRedirectReduceAcceptLanguageOTBrowserTest
    : public CrossOriginRedirectReduceAcceptLanguageBrowserTest {
 public:
  CrossOriginRedirectReduceAcceptLanguageOTBrowserTest()
      : CrossOriginRedirectReduceAcceptLanguageBrowserTest(
            GetValidPortsAndTokensA(),
            GetValidPortsAndTokensB()) {}

  // generate_token.py https://127.0.0.1:44466 DisableReduceAcceptLanguage
  // --expire-timestamp=2000000000
  const std::vector<ServerPortAndValidOriginToken>& GetValidPortsAndTokensA() {
    static const base::NoDestructor<std::vector<ServerPortAndValidOriginToken>>
        vec({
            {44466,
             "A3yx7PpTSg5saq9Ni4by9/"
             "8lCmxy9kTkz9l2qcVCWTxE7m1hF5JNBJQnrnva5dEI0iozq08H+TsS3bFRptQ+"
             "vg0AAABleyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0NjYiLCAiZmVhd"
             "HVyZSI6ICJEaXNhYmxlUmVkdWNlQWNjZXB0TGFuZ3VhZ2UiLCAiZXhwaXJ5IjogMj"
             "AwMDAwMDAwMH0="},
            {44467,
             "A8uLuFYjCwhU1tNYQBC6JsK7QtZTrYe1QOeSU/irQMdmOaU/"
             "dXv6n7JWxS7vsQgEApOWyc58HVlIxr3TT8rT3A0AAABleyJvcmlnaW4iOiAiaHR0c"
             "HM6Ly8xMjcuMC4wLjE6NDQ0NjciLCAiZmVhdHVyZSI6ICJEaXNhYmxlUmVkdWNlQW"
             "NjZXB0TGFuZ3VhZ2UiLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0="},
            {44468,
             "AyRZGI32JZcK6DYat+TahnkxJ+nrT/"
             "G9vw9DkmVMmMF7IZLFQ9PC3NKZ9Votiik9sGY+"
             "PSV8odfsLrqZtpHQzAwAAABleyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6N"
             "DQ0NjgiLCAiZmVhdHVyZSI6ICJEaXNhYmxlUmVkdWNlQWNjZXB0TGFuZ3VhZ2UiLC"
             "AiZXhwaXJ5IjogMjAwMDAwMDAwMH0="},
            {44469,
             "AxViEPVpoX+1KbTBnBv0HZoI5dukMCi0Ib4FQYPzadSdg3XWaFv+"
             "CnvdtWl7IyjQjLuO0v3cnFs797PjUrcDoQwAAABleyJvcmlnaW4iOiAiaHR0cHM6L"
             "y8xMjcuMC4wLjE6NDQ0NjkiLCAiZmVhdHVyZSI6ICJEaXNhYmxlUmVkdWNlQWNjZX"
             "B0TGFuZ3VhZ2UiLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0="},
            {44470,
             "A657dEV4mXzjMLET+T6Z/"
             "iHO9WbXi+i7aC7g42WzY8I96tDKGGM6On4vKhqB6VlntM/Ec0iIw2DJ3/"
             "VrNMOpKg4AAABleyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0NzAiLCA"
             "iZmVhdHVyZSI6ICJEaXNhYmxlUmVkdWNlQWNjZXB0TGFuZ3VhZ2UiLCAiZXhwaXJ5"
             "IjogMjAwMDAwMDAwMH0="},
        });
    return *vec;
  }

  // generate_token.py https://127.0.0.1:44477 DisableReduceAcceptLanguage
  // --expire-timestamp=2000000000
  const std::vector<ServerPortAndValidOriginToken>& GetValidPortsAndTokensB() {
    static const base::NoDestructor<std::vector<ServerPortAndValidOriginToken>>
        vec({
            {44477,
             "A6P0N8LB9c/"
             "ZmFPpIyoPk7cSL3wv5adWfrmFI0FNfvY672xO96N9e5tLK2MtHm89YH2QIp4ROfow"
             "krIlh2OzxgIAAABleyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0NzciL"
             "CAiZmVhdHVyZSI6ICJEaXNhYmxlUmVkdWNlQWNjZXB0TGFuZ3VhZ2UiLCAiZXhwaX"
             "J5IjogMjAwMDAwMDAwMH0="},
            {44478,
             "A1hljUHw96l+l4zSiLZjGwCdEJ8jMdZp0GJnBJIHO9nCvP6KiXJi/"
             "Ow5yRLy+"
             "8RtrWMyyTJVDaX3fbJCFpcavg0AAABleyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC"
             "4wLjE6NDQ0NzgiLCAiZmVhdHVyZSI6ICJEaXNhYmxlUmVkdWNlQWNjZXB0TGFuZ3V"
             "hZ2UiLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0="},
            {44479,
             "A2mynuvz4OL0gAkgDO1SNfLrAL7Mpb1aKJBzZ7TMby/"
             "nZQNEXed1Cr9mDAWoG1Kj6sD3ygKPgm68dnTo+"
             "7ujDQUAAABleyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0NzkiLCAiZm"
             "VhdHVyZSI6ICJEaXNhYmxlUmVkdWNlQWNjZXB0TGFuZ3VhZ2UiLCAiZXhwaXJ5Ijo"
             "gMjAwMDAwMDAwMH0="},
            {44480,
             "A0qwpXBuq0NOJjSypFvI2O59dKzJSGZKLXSgPYDS2N7IjX7nBHBtISWbGRXxm24QL"
             "puxWAUxyQ/"
             "RTdB4uGdy3AMAAABleyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0ODAi"
             "LCAiZmVhdHVyZSI6ICJEaXNhYmxlUmVkdWNlQWNjZXB0TGFuZ3VhZ2UiLCAiZXhwa"
             "XJ5IjogMjAwMDAwMDAwMH0="},
            {44481,
             "A/aKNOr1iw10YYvsrJPF4TMpMFiyGUk/qGw3uUk4ZD/"
             "t0TFjxVZa7NsdLr5jFAVDT+"
             "aWyWTDu41hyBqkjWIrlQgAAABleyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE"
             "6NDQ0ODEiLCAiZmVhdHVyZSI6ICJEaXNhYmxlUmVkdWNlQWNjZXB0TGFuZ3VhZ2Ui"
             "LCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0="},
        });
    return *vec;
  }

  std::string GetValidTokenA() { return valid_first_party_token_; }

  std::string GetValidTokenB() { return valid_third_party_token_; }

 protected:
  void EnabledFeatures() override {
    // Explicit enable feature ReduceAcceptLanguage.
    scoped_feature_list_.InitAndEnableFeature(
        {network::features::kReduceAcceptLanguage});
  }
};

// Persistent origin trial doesn't works when a.com redirects to b.com and only
// a.com opt-in the deprecation origin trial, because persistent origin trial
// only parse and persist token for commit origin, in this redirect case b.com
// is always the commit origin.
IN_PROC_BROWSER_TEST_F(CrossOriginRedirectReduceAcceptLanguageOTBrowserTest,
                       RestartOnA) {
  // Restart only happens on A, and only A opt-in the deprecation origin trial,
  // then invalidate only A's token.
  SetPrefsAcceptLanguage({"en-US", "zh"});
  SetOptions(/*avail_language_a=*/{"ja", "zh"},
             /*avail_language_b=*/{"en-US"});

  // Set A opt-in and B opt-out the origin trial.
  SetOriginTrialFirstPartyToken(
      /*origin_trial_token_a=*/GetValidTokenA(),
      /*origin_trial_token_b=*/kInvalidOriginToken);

  ResetURLAndAcceptLanguageSequence();
  // initial redirect request.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  // 1. initial request to A with with reduced accept-language en-US since
  // we can't validate deprecation origin trial token before sending requests.
  // 2. restart request to A with the persisted language zh.
  // 3. initial request to B with reduced user accept-language en-US.
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9"}},
      "Verifying RestartOnA the first request sequence failed.");

  ResetURLAndAcceptLanguageSequence();
  // Secondary redirect request expects no restarts, but it won't send unreduced
  // accept-language. Persistent origin trial won't persist origin A's token in
  // case a.com redirects to b.com since it only persist the token for the
  // commit origin, in the redirect case, b.com is the commit origin.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9"}},
      "Verifying RestartOnA the second request sequence failed.");

  // Set A opt-out the deprecation origin trial.
  SetOriginTrialFirstPartyToken(/*origin_trial_token_a=*/kInvalidOriginToken,
                                /*origin_trial_token_b=*/kInvalidOriginToken);

  base::HistogramTester histograms;
  ResetURLAndAcceptLanguageSequence();
  // Accept-Language in the third request header is the same as the second one,
  // it won't clear the persisted language since we can't validate a.com
  // deprecation trial token.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9"}},
      "Verifying RestartOnA the third request sequence failed.");
  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  histograms.ExpectTotalCount("ReduceAcceptLanguage.ClearLatency", 0);

  ResetURLAndAcceptLanguageSequence();
  // Fourth request will be the same as the third one.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9"}},
      "Verifying RestartOnA the fourth request sequence failed.");
}

IN_PROC_BROWSER_TEST_F(CrossOriginRedirectReduceAcceptLanguageOTBrowserTest,
                       RestartOnB) {
  // Restart only happens on B, and only B opt-in the deprecation origin trial,
  // then invalidate only B's token.
  SetPrefsAcceptLanguage({"en-US", "zh"});
  SetOptions(/*avail_language_a=*/{"en-US", "zh"},
             /*avail_language_b=*/{"de", "zh"});

  // Set B opt-in and A opt-out the origin trial.
  SetOriginTrialFirstPartyToken(/*origin_trial_token_a=*/kInvalidOriginToken,
                                /*origin_trial_token_b=*/GetValidTokenB());

  ResetURLAndAcceptLanguageSequence();
  // Initial request.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  // 1. initial request to A with the reduced accept-language en-US since A
  // hasn't participated in the deprecation origin trial.
  // 2. initial request to B with reduced accept-language en-US since we
  // can't validate B's deprecation origin trial token before sending requests.
  // 3. restart request to A still sends the reduced accept-language.
  // 4. restart request to B with the persisted language zh.
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_a().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_b().spec(), "zh"}},
      "Verifying RestartOnB the first request sequence failed.");

  ResetURLAndAcceptLanguageSequence();
  // Secondary redirect request expects no restarts, A sends reduced
  // Accept-Language and B sends unreduced Accept-Language header.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9,zh;q=0.8"}},
      "Verifying RestartOnB the second request sequence failed.");

  // Set B opt-out the origin trial.
  SetOriginTrialFirstPartyToken(/*origin_trial_token_a=*/kInvalidOriginToken,
                                /*origin_trial_token_b=*/kInvalidOriginToken);

  ResetURLAndAcceptLanguageSequence();
  // Accept-Language in the third request header is the same as the second one.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9,zh;q=0.8"}},
      "Verifying RestartOnB the third request sequence failed.");

  ResetURLAndAcceptLanguageSequence();
  // Fourth request will start to send the reduced Accept-Language header for B
  // and it will do the language negotiation once the given origin opt-out
  // the deprecation origin trial.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_a().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_b().spec(), "zh"}},
      "Verifying RestartOnB the fourth request sequence failed.");
}

IN_PROC_BROWSER_TEST_F(CrossOriginRedirectReduceAcceptLanguageOTBrowserTest,
                       RestartOnAB) {
  // Restart on both A and B, and both origin opt-in the deprecation origin
  // trial, then invalidate A's and B's token. Verify Accept-Language header in
  // both A and B for the first two requests.
  SetPrefsAcceptLanguage({"en-US", "zh"});
  SetOptions(/*avail_language_a=*/{"ja", "zh"},
             /*avail_language_b=*/{"de", "zh"});

  // Set A opt-in and B opt-in the deprecation origin trial.
  SetOriginTrialFirstPartyToken(
      /*origin_trial_token_a=*/GetValidTokenA(),
      /*origin_trial_token_b=*/GetValidTokenB());

  ResetURLAndAcceptLanguageSequence();
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  // 1. initial request to A with reduced accept-language of first language.
  // 2. restart request to A with the persisted language zh.
  // 3. initial request to B with reduced accept-language of first language.
  // 4. restart request to A since redirect the original URL with persisted
  // language zh.
  // 5. restart request to B with the persisted language zh.
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "zh"}},
      "Verifying the first request sequence failed.");

  ResetURLAndAcceptLanguageSequence();
  // Secondary redirect request expects no restarts and B starts to send
  // unreduced accept-language.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9,zh;q=0.8"}},
      "Verifying the second request sequence failed.");

  // Set A opt-out the deprecation origin trial.
  SetOriginTrialFirstPartyToken(kInvalidOriginToken, GetValidTokenB());

  ResetURLAndAcceptLanguageSequence();
  // For the first request after A opting-out the deprecation trial: there is no
  // changes on both A and B.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9,zh;q=0.8"}},
      "Verifying RestartOnAB the third request sequence failed.");

  ResetURLAndAcceptLanguageSequence();
  // For the second request after A opting-out the deprecation trial, it's the
  // same as the above request.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9,zh;q=0.8"}},
      "Verifying RestartOnAB the fourth request sequence failed.");

  // Set A and B both opt-out the deprecation origin trial.
  SetOriginTrialFirstPartyToken(kInvalidOriginToken, kInvalidOriginToken);

  ResetURLAndAcceptLanguageSequence();
  // There is no change for the first request after B opting-out the
  // deprecation trial.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9,zh;q=0.8"}},
      "Verifying RestartOnAB the fifth request sequence failed.");

  ResetURLAndAcceptLanguageSequence();
  // For the second request after B opting-out the deprecation trial, there is
  // no change on A, but B will do the language negotiation and send the new
  // reduced Accept-Language.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), cross_origin_redirect_a()));
  VerifyURLAndAcceptLanguageSequence(
      {{cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "en-US,en;q=0.9"},
       {cross_origin_redirect_a().spec(), "zh"},
       {cross_origin_redirect_b().spec(), "zh"}},
      "Verifying RestartOnA the fourth request sequence failed.");
}

// Browser tests verify same origin deprecation origin trial.
class SameOriginReduceAcceptLanguageDeprecationOTBrowserTest
    : public ReduceAcceptLanguageBrowserTest {
 public:
  void VerifySubrequest(
      const GURL& url,
      const std::string& last_request_path,
      const std::vector<std::string>& user_accept_languages,
      int expect_restart_count,
      int expect_fetch_count,
      const std::optional<std::string>& expect_opt_in_fq_language,
      const std::optional<std::string>& expect_opt_out_fq_language,
      const std::string& expect_reduced_accept_language) {
    base::HistogramTester histograms;

    // 1. Test opt-in deprecation origin trial.
    SetOriginTrialFirstPartyToken(kValidFirstPartyToken);
    // Verify the first request opt-in deprecation origin trial.
    NavigateAndVerifyAcceptLanguageOfLastRequest(url,
                                                 expect_opt_in_fq_language);
    EXPECT_EQ(LastRequestUrl().GetPath(), last_request_path);

    metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
    // Ensure restart happen once.
    histograms.ExpectBucketCount(
        "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
        /*=kNavigationRestarted=*/3, expect_restart_count);
    histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs",
                                expect_fetch_count);

    // Verify navigator.languages returns full list of accept-languages if
    // has valid deprecation origin trial token.
    VerifyNavigatorLanguages(user_accept_languages);

    // Verify the send request after opt-in deprecation origin trial.
    NavigateAndVerifyAcceptLanguageOfLastRequest(url, std::nullopt);

    // 2. Test opt-out deprecation origin trial.
    // Verify the first request has invalid deprecation origin trial token.
    SetOriginTrialFirstPartyToken(kInvalidOriginToken);
    NavigateAndVerifyAcceptLanguageOfLastRequest(url,
                                                 expect_opt_out_fq_language);
    EXPECT_EQ(LastRequestUrl().GetPath(), last_request_path);
    VerifyNavigatorLanguages({user_accept_languages[0]});

    // Verify the second request has invalid deprecation origin trial token, it
    // should continue to reduce the Accept-Language.
    NavigateAndVerifyAcceptLanguageOfLastRequest(
        url, expect_reduced_accept_language);
    EXPECT_EQ(LastRequestUrl().GetPath(), last_request_path);
  }

  void VerifySameOriginRequestNoRestart(
      const std::optional<std::string>& expect_accept_language,
      int expect_fetch_count,
      int expect_store_count) {
    base::HistogramTester histograms;
    NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                                 expect_accept_language);
    metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
    // Ensure no restart happen.
    histograms.ExpectBucketCount(
        "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
        /*=kNavigationRestarted=*/3, 0);
    histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs",
                                expect_fetch_count);
    // Expect one storage update when response has a valid origin token.
    histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency",
                                expect_store_count);
  }

  void VerifySameOriginTwoRequestsAfterTokenInvalid(
      const std::optional<std::string>& expect_accept_language) {
    SetOriginTrialFirstPartyToken(kInvalidOriginToken);
    base::HistogramTester histograms;
    // First request after token invalid will continue not sending reduced
    // header since we can't verify the response header before preparing the
    // request headers.
    NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                                 std::nullopt);
    metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();

    // For opt-out deprecation origin trial, there is no clear cache operation.
    histograms.ExpectTotalCount("ReduceAcceptLanguage.ClearLatency", 0);

    // The second request with invalid token should continue to reduced
    // Accept-Language header.
    NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                                 expect_accept_language);
  }

 protected:
  void EnabledFeatures() override {
    // Explicit enable feature ReduceAcceptLanguage.
    scoped_feature_list_.InitAndEnableFeature(
        {network::features::kReduceAcceptLanguage});
  }
};

IN_PROC_BROWSER_TEST_F(SameOriginReduceAcceptLanguageDeprecationOTBrowserTest,
                       FirstRequestMatchPrimaryLanguage) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});
  SetOriginTrialFirstPartyToken(kValidFirstPartyToken);
  SetPrefsAcceptLanguage({"es", "zh"});

  // The first request will add the reduced Accept-Language in navigation
  // request since we enabled the ReduceAcceptLanguage.
  // One fetch for initially checking whether need to add reduce Accept-Language
  // header. No call to store the language since we don't store the reduced
  // language which is the first primary language.
  VerifySameOriginRequestNoRestart(/*expect_accept_language=*/"es",
                                   /*expect_fetch_count=*/1,
                                   /*expect_store_count=*/0);
  // The second request should not send out reduced Accept-Language, the network
  // layer will add the full Accept-Language list.
  VerifySameOriginRequestNoRestart(/*expect_accept_language=*/std::nullopt,
                                   /*expect_fetch_count=*/0,
                                   /*expect_store_count=*/0);
  // Verify requests after invalid token will continue send the reduced
  // accept-language.
  VerifySameOriginTwoRequestsAfterTokenInvalid("es");
}

IN_PROC_BROWSER_TEST_F(SameOriginReduceAcceptLanguageDeprecationOTBrowserTest,
                       FirstRequestMatchNonPrimaryLanguage) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "en-US, es;d",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});
  SetOriginTrialFirstPartyToken(kValidFirstPartyToken);
  SetPrefsAcceptLanguage({"zh", "en-US"});

  // First request restarts and send Accept-Language with negotiated language:
  // en-us, the deprecation origin trial doesn't apply to the first request.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                               "en-US,en;q=0.9");
  // Ensure only restart once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);

  // Two fetches for initially adding header and restart fetch.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 2);
  // Expect no perf storage updates.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  // The second request shouldn't send out any reduced accept-language due to
  // opted-in the deprecation origin trial.
  VerifySameOriginRequestNoRestart(/*expect_accept_language=*/std::nullopt,
                                   /*expect_fetch_count=*/0,
                                   /*expect_store_count=*/0);
  // Verify requests after invalid token will continue send the reduced
  // accept-language.
  VerifySameOriginTwoRequestsAfterTokenInvalid("en-US,en;q=0.9");
}

IN_PROC_BROWSER_TEST_F(SameOriginReduceAcceptLanguageDeprecationOTBrowserTest,
                       FirstRequestMatchNonPrimaryLanguageWithCriticalTrial) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "en-US, es;d",
                  .vary_in_parent = "accept-language",
                  .is_critical_origin_trial = true},
                 {SameOriginRequestUrl()});
  SetOriginTrialFirstPartyToken(kValidFirstPartyToken);
  SetPrefsAcceptLanguage({"zh", "en-US"});

  // First request restarts and won't send reduced Accept-Language header due to
  // critical deprecation origin trial apply to the first request.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                               std::nullopt);
  // Ensure only restart once.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);

  // The second request shouldn't send out any reduced accept-language due to
  // opted-in the deprecation origin trial.
  VerifySameOriginRequestNoRestart(/*expect_accept_language=*/std::nullopt,
                                   /*expect_fetch_count=*/0,
                                   /*expect_store_count=*/0);
  // Verify requests after invalid token will continue to send the reduced
  // accept-language.
  VerifySameOriginTwoRequestsAfterTokenInvalid("en-US,en;q=0.9");
}

IN_PROC_BROWSER_TEST_F(SameOriginReduceAcceptLanguageDeprecationOTBrowserTest,
                       FirstRequestNoMatchLanguage) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});
  SetOriginTrialFirstPartyToken(kValidFirstPartyToken);
  SetPrefsAcceptLanguage({"zh", "ja"});

  // The first request adds the reduced Accept-Language in navigation request
  // since we enabled ReduceAcceptLanguage feature.
  VerifySameOriginRequestNoRestart(/*expect_accept_language=*/"zh",
                                   /*expect_fetch_count=*/1,
                                   /*expect_store_count=*/0);
  // The second request should not send out reduced Accept-Language, the network
  // layer will add the full Accept-Language list.
  VerifySameOriginRequestNoRestart(/*expect_accept_language=*/std::nullopt,
                                   /*expect_fetch_count=*/0,
                                   /*expect_store_count=*/0);
  // Verify requests after invalid token will continue send the reduced
  // accept-language.
  VerifySameOriginTwoRequestsAfterTokenInvalid("zh");
}

IN_PROC_BROWSER_TEST_F(SameOriginReduceAcceptLanguageDeprecationOTBrowserTest,
                       IframeRequestRestart) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "es",
                  .avail_language_in_child = "es, en-US",
                  .vary_in_child = "accept-language",
                  .is_critical_origin_trial = false},
                 {SameOriginIframeUrl(), SimpleRequestUrl()});
  std::vector<std::string> user_accept_languages = {"zh", "en-US"};
  SetPrefsAcceptLanguage(user_accept_languages);

  // Total two different url requests:
  // * same_origin_iframe_url: one fetch for initially adding header and another
  // one for the restart request adding header.
  // * simple_request_url: no fetch due to opted-in deprecation origin trial.
  //
  // For the first iframe request after site opt-in the deprecation OT, we
  // won't add the Accept-Language since language negotiation restart the
  // request and find a valid deprecation origin trial token. Network layer
  // will add the full list of user's Accept-Language.
  // For the first iframe request after site opt-out (or invalid token) the
  // deprecation OT, we resume to reduce the Accept-Language, however, the
  // reduced accept-language is the first user accept-language because the
  // negotiated language won't take effect for the first request after opt-out
  // the deprecation trial without the critical trial.
  VerifySubrequest(
      /*url=*/SameOriginIframeUrl(),
      /*last_request_path=*/"/subframe_simple.html",
      /*user_accept_languages=*/user_accept_languages,
      /*expect_restart_count=*/1,
      /*expect_fetch_count=*/2,
      /*expect_opt_in_fq_language=*/std::nullopt,
      /*expect_opt_out_fq_language=*/"zh",
      /*expect_reduced_accept_language=*/"en-US,en;q=0.9");
}

IN_PROC_BROWSER_TEST_F(SameOriginReduceAcceptLanguageDeprecationOTBrowserTest,
                       IframeRequestRestartWithCriticalTrial) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "es",
                  .avail_language_in_child = "es, en-US",
                  .vary_in_child = "accept-language",
                  .is_critical_origin_trial = true},
                 {SameOriginIframeUrl(), SimpleRequestUrl()});
  std::vector<std::string> user_accept_languages = {"zh", "en-US"};
  SetPrefsAcceptLanguage(user_accept_languages);

  // Only one fetch reduced Accept-Language prefs for initially add the
  // Accept-Language HTTP header, no fetch call for restart request since we
  // added the deprecation trial as critical trial. Also, we add the critical
  // trial, the negotiated language should take effect for the first request
  // after opt-out the deprecation trial.
  VerifySubrequest(
      /*url=*/SameOriginIframeUrl(),
      /*last_request_path=*/"/subframe_simple.html",
      /*user_accept_languages=*/user_accept_languages,
      /*expect_restart_count=*/1,
      /*expect_fetch_count=*/1,
      /*expect_opt_in_fq_language=*/std::nullopt,
      /*expect_opt_out_fq_language=*/"en-US,en;q=0.9",
      /*expect_reduced_accept_language=*/"en-US,en;q=0.9");
}

IN_PROC_BROWSER_TEST_F(SameOriginReduceAcceptLanguageDeprecationOTBrowserTest,
                       IframeRequestNoRestart) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .is_critical_origin_trial = false},
                 {{SameOriginIframeUrl(), SimpleRequestUrl()}});
  std::vector<std::string> user_accept_languages = {"es", "ja"};
  SetPrefsAcceptLanguage(user_accept_languages);

  // No restart
  VerifySubrequest(
      /*url=*/SameOriginIframeUrl(),
      /*last_request_path=*/"/subframe_simple.html",
      /*user_accept_languages=*/user_accept_languages,
      /*expect_restart_count=*/0,
      /*expect_fetch_count=*/1,
      /*expect_opt_in_fq_language=*/std::nullopt,
      /*expect_opt_out_fq_language=*/"es",
      /*expect_reduced_accept_language=*/"es");
}

IN_PROC_BROWSER_TEST_F(SameOriginReduceAcceptLanguageDeprecationOTBrowserTest,
                       IframeRequestNoRestartWithCriticalTrial) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .is_critical_origin_trial = true},
                 {{SameOriginIframeUrl(), SimpleRequestUrl()}});
  std::vector<std::string> user_accept_languages = {"es", "ja"};
  SetPrefsAcceptLanguage(user_accept_languages);

  // No restart
  VerifySubrequest(
      /*url=*/SameOriginIframeUrl(),
      /*last_request_path=*/"/subframe_simple.html",
      /*user_accept_languages=*/user_accept_languages,
      /*expect_restart_count=*/0,
      /*expect_fetch_count=*/1,
      /*expect_opt_in_fq_language=*/std::nullopt,
      /*expect_opt_out_fq_language=*/"es",
      /*expect_reduced_accept_language=*/"es");
}

IN_PROC_BROWSER_TEST_F(SameOriginReduceAcceptLanguageDeprecationOTBrowserTest,
                       ImgSubresourceRestart) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "es",
                  .avail_language_in_child = "es, en-US",
                  .vary_in_child = "accept-language",
                  .is_critical_origin_trial = false},
                 {SameOriginImgUrl(), SimpleImgUrl()});
  std::vector<std::string> user_accept_languages = {"zh", "en-US"};
  SetPrefsAcceptLanguage(user_accept_languages);

  // Total two different url requests:
  // * same_origin_image_url: one fetch for initially adding header and another
  // one for the restart request adding header.
  // * subresource_simple.jpg: no language fetch on subresource.
  //
  // For the first subresource request after site opt-in the deprecation OT.
  // We won't add the Accept-Language since language negotiation restart the
  // request and find a valid deprecation origin trial token. Network layer
  // will add the full list of user's Accept-Language.
  // For the first subresource request after site opt-out (or invalid token)
  // the deprecation OT, we also won't add the Accept-Language since we cleared
  // the commit language for subresource once we see invalid OT token when
  // committing the navigation.
  VerifySubrequest(
      /*url=*/SameOriginImgUrl(),
      /*last_request_path=*/"/subresource_simple.jpg",
      /*user_accept_languages=*/user_accept_languages,
      /*expect_restart_count=*/1,
      /*expect_fetch_count=*/2,
      /*expect_opt_in_fq_language=*/std::nullopt,
      /*expect_opt_out_fq_language=*/std::nullopt,
      /*expect_reduced_accept_language=*/"en-US,en;q=0.9");
}

IN_PROC_BROWSER_TEST_F(SameOriginReduceAcceptLanguageDeprecationOTBrowserTest,
                       ImgSubresourceRestartWithCriticalTrial) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "es",
                  .avail_language_in_child = "es, en-US",
                  .vary_in_child = "accept-language",
                  .is_critical_origin_trial = true},
                 {SameOriginImgUrl(), SimpleImgUrl()});
  std::vector<std::string> user_accept_languages = {"zh", "en-US"};
  SetPrefsAcceptLanguage(user_accept_languages);

  // Only one fetch call for initially add the reduced Accept-Language header,
  // no fetch call for restart request since we added the deprecation trial as
  // critical trial.
  VerifySubrequest(
      /*url=*/SameOriginImgUrl(),
      /*last_request_path=*/"/subresource_simple.jpg",
      /*user_accept_languages=*/user_accept_languages,
      /*expect_restart_count=*/1,
      /*expect_fetch_count=*/1,
      /*expect_opt_in_fq_language=*/std::nullopt,
      /*expect_opt_out_fq_language=*/std::nullopt,
      /*expect_reduced_accept_language=*/"en-US,en;q=0.9");
}

IN_PROC_BROWSER_TEST_F(SameOriginReduceAcceptLanguageDeprecationOTBrowserTest,
                       ImgSubresourceNoRestart) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language"},
                 {{SameOriginImgUrl(), SimpleImgUrl()}});
  std::vector<std::string> user_accept_languages = {"es", "ja"};
  SetPrefsAcceptLanguage(user_accept_languages);

  // No restart
  VerifySubrequest(
      /*url=*/SameOriginImgUrl(),
      /*last_request_path=*/"/subresource_simple.jpg",
      /*user_accept_languages=*/user_accept_languages,
      /*expect_restart_count=*/0,
      /*expect_fetch_count=*/1,
      /*expect_opt_in_fq_language=*/std::nullopt,
      /*expect_opt_out_fq_language=*/std::nullopt,
      /*expect_reduced_accept_language=*/"es");
}

IN_PROC_BROWSER_TEST_F(SameOriginReduceAcceptLanguageDeprecationOTBrowserTest,
                       ImgSubresourceNoRestartWithCriticalTrial) {
  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .is_critical_origin_trial = true},
                 {{SameOriginImgUrl(), SimpleImgUrl()}});
  std::vector<std::string> user_accept_languages = {"es", "ja"};
  SetPrefsAcceptLanguage(user_accept_languages);

  // No restart
  VerifySubrequest(
      /*url=*/SameOriginImgUrl(),
      /*last_request_path=*/"/subresource_simple.jpg",
      /*user_accept_languages=*/user_accept_languages,
      /*expect_restart_count=*/0,
      /*expect_fetch_count=*/1,
      /*expect_opt_in_fq_language=*/std::nullopt,
      /*expect_opt_out_fq_language=*/std::nullopt,
      /*expect_reduced_accept_language=*/"es");
}

// Browser tests verify third party deprecation origin trial.
class ThirdPartyReduceAcceptLanguageDeprecationOTBrowserTest
    : public ThirdPartyReduceAcceptLanguageBrowserTest {
 protected:
  void EnabledFeatures() override {
    // Explicit enable feature ReduceAcceptLanguage ReduceAcceptLanguage.
    scoped_feature_list_.InitWithFeatures(
        {network::features::kReduceAcceptLanguage}, {});
  }
};

// For third-party embedded as an iframe, the third-party can opt-in the
// deprecation trial as a first party deprecation origin trial.
IN_PROC_BROWSER_TEST_F(ThirdPartyReduceAcceptLanguageDeprecationOTBrowserTest,
                       IframeRequests) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "zh",
                  .avail_language_in_child = "zh",
                  .vary_in_child = "accept-language"},
                 {CrossOriginIframeUrl(), SimpleThirdPartyRequestUrl()});

  SetOriginTrialThirdPartyToken(kValidMySiteFirstPartyToken);
  SetPrefsAcceptLanguage({"zh", "en-US"});

  // The first third-party iframe subrequest expect continue to send reduced
  // Accept-Language which is inherited from the top-level frame request.
  NavigateAndVerifyAcceptLanguageOfLastRequest(CrossOriginIframeUrl(),
                                               "en-US,en;q=0.9");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure only one restart to do the language negotiation.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 1);
  // cross_origin_iframe_url: one fetch for initially adding header and
  // another one for the restart request adding header.
  // simple_3p_request_url: one fetch for initially adding header.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 3);
  // Persist reduce accept language happens.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 1);

  EXPECT_EQ(LastRequestUrl().GetPath(), "/subframe_simple_3p.html");

  // For the second request, we expect no reduced Accept-Language send once
  // the deprecation origin trial takes effect.
  NavigateAndVerifyAcceptLanguageOfLastRequest(CrossOriginIframeUrl(),
                                               std::nullopt);

  // For the first request we continue to send the reduced Accept-Language since
  // we persist the deprecation origin trial token on a third-party context
  // which means the partition origin is first part origin.
  // For the second request, we won't send reduce Accept-Language as deprecation
  // origin trial take effects, in this case, the partition origin is the
  // third-party origin itself.
  base::HistogramTester histograms2;
  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  NavigateAndVerifyAcceptLanguageOfLastRequest(SimpleThirdPartyRequestUrl(),
                                               "zh");
  NavigateAndVerifyAcceptLanguageOfLastRequest(SimpleThirdPartyRequestUrl(),
                                               std::nullopt);
  histograms2.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 0);
}

IN_PROC_BROWSER_TEST_F(ThirdPartyReduceAcceptLanguageDeprecationOTBrowserTest,
                       JavaScriptRequest) {
  base::HistogramTester histograms;

  SetTestOptions(
      {.content_language_in_parent = "es",
       .avail_language_in_parent = "es, en-US",
       .vary_in_parent = "accept-language",
       .content_language_in_child = "zh",
       .avail_language_in_child = "zh",
       .vary_in_child = "accept-language"},
      {CrossOriginSubresourceUrl(), CrossOriginMetaTagInjectingJavascriptUrl(),
       CrossOriginCssRequestUrl()});

  SetOriginTrialThirdPartyToken(kValidThirdPartyToken);
  SetPrefsAcceptLanguage({"zh", "en-US"});

  // Third party iframe subrequest expect inherit the reduced Accept-Language
  // from the top-level navigation requests.
  NavigateAndVerifyAcceptLanguageOfLastRequest(CrossOriginSubresourceUrl(),
                                               "zh");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Ensure no restart happen.
  histograms.ExpectBucketCount(
      "ReduceAcceptLanguage.AcceptLanguageNegotiationRestart",
      /*=kNavigationRestarted=*/3, 0);
  // One fetch for initially checking whether need to add reduce Accept-Language
  // header to the top-level frame.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.FetchLatencyUs", 1);
  // No persist reduce accept language happens.
  histograms.ExpectTotalCount("ReduceAcceptLanguage.StoreLatency", 0);

  // All subresources should have been loaded,
  EXPECT_THAT(intercepted_load_urls_,
              testing::IsSupersetOf({CrossOriginMetaTagInjectingJavascriptUrl(),
                                     CrossOriginCssRequestUrl()}));

  // Ensure third-party JavaScript access JS getters get the full list of
  // accept-language.
  VerifyNavigatorLanguages({"zh", "en-US"});
}

// Browser tests verify reduce the total number of Accept-Language.
class ReduceAcceptLanguageCountBrowserTest
    : public ReduceAcceptLanguageBrowserTest,
      public testing::WithParamInterface<bool> {
 protected:
  void EnabledFeatures() override {
    // If explicitly set custom count set 5 as max.
    if (GetParam()) {
      scoped_feature_list_.InitWithFeaturesAndParameters(
          /*enabled_features=*/{{network::features::kReduceAcceptLanguageCount,
                                 {{network::features::kMaxAcceptLanguage.name,
                                   "5"}}}},
          /*disabled_features=*/{network::features::kReduceAcceptLanguage,
                                 network::features::kReduceAcceptLanguageHTTP});
    } else {
      scoped_feature_list_.InitWithFeatures(
          /*enabled_features=*/{network::features::kReduceAcceptLanguageCount},
          /*disabled_features=*/{network::features::kReduceAcceptLanguage,
                                 network::features::kReduceAcceptLanguageHTTP});
    }
  }
};

INSTANTIATE_TEST_SUITE_P(FeatureFlag,
                         ReduceAcceptLanguageCountBrowserTest,
                         testing::Values(true, false));

// TODO(crbug.com/542347163): Re-enable test.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_RegularRequest DISABLED_RegularRequest
#else
#define MAYBE_RegularRequest RegularRequest
#endif
IN_PROC_BROWSER_TEST_P(ReduceAcceptLanguageCountBrowserTest,
                       MAYBE_RegularRequest) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "en",
                  .avail_language_in_parent = "en, en-US",
                  .vary_in_parent = "accept-language"},
                 {SameOriginRequestUrl()});
  SetPrefsAcceptLanguage(base::SplitString(
      kLargeLanguages, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL));

  // Expect no Accept-Language header added because browser_tests can only check
  // headers in navigation layer, browser_tests can't see headers added by
  // network stack.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginRequestUrl(),
                                               std::nullopt);
  if (GetParam()) {
    VerifyNavigatorLanguages({"zh", "zh-CN", "en-US", "en", "af"});
  } else {
    VerifyNavigatorLanguages(
        {"zh", "zh-CN", "en-US", "en", "af", "sq", "am", "ar", "an", "hy"});
  }

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Expect 5 samples recorded for kLargeLanguages when SetPrefsAcceptLanguage
  // is called (1 during initial profile setup, twice more when
  // SetPrefsAcceptLanguage is called to sync the preference to the renderer and
  // network services, 1 for WebUI Omnibox WebContents, and 1 for Omnibox Aim
  // Popup Webcontents).
  histograms.ExpectBucketCount("LanguageUsage.AcceptLanguage.Count2",
                               kLargeLanguagesCount, 5);
}

// TODO(crbug.com/542347163): Re-enable test.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_Iframe DISABLED_Iframe
#else
#define MAYBE_Iframe Iframe
#endif
IN_PROC_BROWSER_TEST_P(ReduceAcceptLanguageCountBrowserTest, MAYBE_Iframe) {
  base::HistogramTester histograms;

  SetTestOptions({.content_language_in_parent = "es",
                  .avail_language_in_parent = "es, en-US",
                  .vary_in_parent = "accept-language",
                  .content_language_in_child = "es",
                  .avail_language_in_child = "es, en-US",
                  .vary_in_child = "accept-language"},
                 {SameOriginIframeUrl(), SimpleRequestUrl()});

  SetPrefsAcceptLanguage(base::SplitString(
      kLargeLanguages, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL));

  // Expect no Accept-Language header added because browser_tests can only check
  // headers in navigation layer, browser_tests can't see headers added by
  // network stack.
  NavigateAndVerifyAcceptLanguageOfLastRequest(SameOriginIframeUrl(),
                                               std::nullopt);
  if (GetParam()) {
    VerifyNavigatorLanguages({"zh", "zh-CN", "en-US", "en", "af"});
  } else {
    VerifyNavigatorLanguages(
        {"zh", "zh-CN", "en-US", "en", "af", "sq", "am", "ar", "an", "hy"});
  }
  EXPECT_EQ(LastRequestUrl().GetPath(), "/subframe_simple.html");

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  // Expect 5 samples recorded for kLargeLanguages when SetPrefsAcceptLanguage
  // is called (1 during initial profile setup, twice more when
  // SetPrefsAcceptLanguage is called to sync the preference to the renderer and
  // network services, 1 for WebUI Omnibox WebContents, and 1 for Omnibox Aim
  // Popup Webcontents).
  histograms.ExpectBucketCount("LanguageUsage.AcceptLanguage.Count2",
                               kLargeLanguagesCount, 5);
}
