blob: a20e59137915101ae3ec96035259316e7003b55e [file] [log] [blame]
// 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::nullopt;
std::optional<std::string> avail_language_in_parent = std::nullopt;
std::optional<std::string> vary_in_parent = std::nullopt;
std::optional<std::string> content_language_in_child = std::nullopt;
std::optional<std::string> avail_language_in_child = std::nullopt;
std::optional<std::string> vary_in_child = std::nullopt;
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";
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()
->profile()
->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::Value::List 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()->profile()->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.path();
if (base::Contains(kSubresourcePaths, 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 (base::Contains(kServiceWorkerPaths, 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_piece()));
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().path(), "/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().path(), "/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().path(), "/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()->profile())
->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().path(), "/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().path(), "/subframe_simple.html");
// Disable script for first party origin.
HostContentSettingsMapFactory::GetForProfile(browser()->profile())
->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().path(), "/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().path(), "/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().path(), "/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().path(), "/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().path(), "/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().path(), "/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().path(), "/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().path(), "/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().path());
}
// 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 (!base::Contains(kValidPaths, 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 (!base::Contains(kValidPaths, 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 (!base::Contains(kValidPaths, 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 (!base::Contains(kValidPaths, 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().path(), 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().path(), 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().path(), 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().path(), "/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));
IN_PROC_BROWSER_TEST_P(ReduceAcceptLanguageCountBrowserTest, 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 a total count of 3. The histogram is recorded once during initial
// profile setup, and then twice more when SetPrefsAcceptLanguage is called
// to sync the preference to the renderer and network services.
histograms.ExpectTotalCount("LanguageUsage.AcceptLanguage.Count2", 3);
}
IN_PROC_BROWSER_TEST_P(ReduceAcceptLanguageCountBrowserTest, 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().path(), "/subframe_simple.html");
metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
// Expect a total count of 3. The histogram is recorded once during initial
// profile setup, and then twice more when SetPrefsAcceptLanguage is called
// to sync the preference to the renderer and network services.
histograms.ExpectTotalCount("LanguageUsage.AcceptLanguage.Count2", 3);
}