blob: d035f40418861bfd44faf2eebf31aba45c9cdd01 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/variations/net/variations_http_headers.h"
#include <map>
#include <memory>
#include <optional>
#include <string_view>
#include "base/base64.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/json/json_file_value_serializer.h"
#include "base/metrics/field_trial.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_browser_main.h"
#include "chrome/browser/chrome_browser_main_extra_parts.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
#include "chrome/browser/predictors/predictors_features.h"
#include "chrome/browser/predictors/predictors_switches.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/metrics_services_manager/metrics_services_manager.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/optimization_guide/core/optimization_guide_features.h"
#include "components/optimization_guide/proto/hints.pb.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/variations/pref_names.h"
#include "components/variations/proto/layer.pb.h"
#include "components/variations/proto/study.pb.h"
#include "components/variations/proto/variations_seed.pb.h"
#include "components/variations/variations.mojom.h"
#include "components/variations/variations_associated_data.h"
#include "components/variations/variations_features.h"
#include "components/variations/variations_ids_provider.h"
#include "components/variations/variations_switches.h"
#include "components/variations/variations_test_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/network_connection_change_simulator.h"
#include "content/public/test/simple_url_loader_test_helper.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/zlib/google/compression_utils.h"
#include "url/gurl.h"
namespace variations {
namespace {
constexpr char kTrialName[] = "t1";
constexpr std::string_view kSomeStudyName = "SomeStudy";
constexpr std::string_view kLimitedLayerStudyName = "LimitedLayerStudy";
constexpr int kGenericExperimentGroupId = 12;
constexpr int kGenericExperimentGroupTriggerId = 789;
struct ExperimentIdOptions {
std::optional<int> id;
std::optional<int> trigger_id;
};
// Returns a group named "Group" with its weight equal to 1 and the specified
// ID, if any. CHECKs if both IDs are given.
Study::Experiment CreateExperimentGroup(
const ExperimentIdOptions& id_options = {}) {
Study::Experiment group;
group.set_name("Group");
group.set_probability_weight(1);
if (id_options.id.has_value()) {
CHECK(id_options.trigger_id == std::nullopt);
group.set_google_web_experiment_id(*id_options.id);
}
if (id_options.trigger_id.has_value()) {
CHECK(id_options.id == std::nullopt);
group.set_google_web_trigger_experiment_id(*id_options.trigger_id);
}
return group;
}
// Returns a seed with the following:
// * A 100-slot limited layer with a 100-slot layer member.
// * A limited-layer-constrained study with the given group and permanent
// consistency.
// * A generic study not constrained to any layers.
//
// If a group isn't given, then the seed contains a limited layer, no
// layer-constrained studies, and a generic study. In practice, clients aren't
// expected to receive layers without studies that reference them.
VariationsSeed CreateTestSeedWithLimitedEntropyLayer(
std::optional<Study::Experiment> limited_layer_study_group) {
VariationsSeed seed;
auto* layer = seed.add_layers();
layer->set_id(123);
layer->set_num_slots(100);
layer->set_entropy_mode(Layer::LIMITED);
auto* layer_member = layer->add_members();
layer_member->set_id(1);
auto* slot_range = layer_member->add_slots();
slot_range->set_start(0);
slot_range->set_end(99);
Study base_study;
base_study.set_consistency(Study::PERMANENT);
auto* filter = base_study.mutable_filter();
filter->add_channel(Study::UNKNOWN);
filter->add_channel(Study::CANARY);
filter->add_channel(Study::DEV);
filter->add_channel(Study::BETA);
filter->add_channel(Study::STABLE);
filter->add_platform(Study::PLATFORM_WINDOWS);
filter->add_platform(Study::PLATFORM_MAC);
filter->add_platform(Study::PLATFORM_LINUX);
filter->add_platform(Study::PLATFORM_CHROMEOS);
Study some_study = base_study;
some_study.set_name(kSomeStudyName);
*some_study.add_experiment() = CreateExperimentGroup();
*seed.add_study() = some_study;
if (!limited_layer_study_group.has_value()) {
// Skip creating a layer-constrained study.
return seed;
}
Study layer_study = base_study;
layer_study.set_name(kLimitedLayerStudyName);
*layer_study.add_experiment() = *limited_layer_study_group;
auto* layer_member_reference = layer_study.mutable_layer();
layer_member_reference->set_layer_id(123);
layer_member_reference->add_layer_member_ids(1);
*seed.add_study() = layer_study;
return seed;
}
class VariationHeaderSetter : public ChromeBrowserMainExtraParts {
public:
VariationHeaderSetter() = default;
VariationHeaderSetter(const VariationHeaderSetter&) = delete;
VariationHeaderSetter& operator=(const VariationHeaderSetter&) = delete;
~VariationHeaderSetter() override = default;
// ChromeBrowserMainExtraParts:
void PostEarlyInitialization() override {
// Set up some fake variations.
auto* variations_provider = VariationsIdsProvider::GetInstance();
variations_provider->ForceVariationIds(
{base::NumberToString(kGenericExperimentGroupId),
"t" + base::NumberToString(kGenericExperimentGroupTriggerId)},
"");
}
};
class VariationsHttpHeadersBrowserTest
: public InProcessBrowserTest {
public:
VariationsHttpHeadersBrowserTest()
: https_server_(net::test_server::EmbeddedTestServer::TYPE_HTTPS) {}
VariationsHttpHeadersBrowserTest(const VariationsHttpHeadersBrowserTest&) =
delete;
VariationsHttpHeadersBrowserTest& operator=(
const VariationsHttpHeadersBrowserTest&) = delete;
~VariationsHttpHeadersBrowserTest() override = default;
void CreatedBrowserMainParts(content::BrowserMainParts* parts) override {
InProcessBrowserTest::CreatedBrowserMainParts(parts);
static_cast<ChromeBrowserMainParts*>(parts)->AddParts(
std::make_unique<VariationHeaderSetter>());
}
void SetUp() override {
ASSERT_TRUE(server()->InitializeAndListen());
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
content::NetworkConnectionChangeSimulator().SetConnectionType(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
host_resolver()->AddRule("*", "127.0.0.1");
base::FilePath test_data_dir;
ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
server()->ServeFilesFromDirectory(test_data_dir);
server()->RegisterRequestHandler(
base::BindRepeating(&VariationsHttpHeadersBrowserTest::RequestHandler,
base::Unretained(this)));
server()->StartAcceptingConnections();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(::switches::kIgnoreCertificateErrors);
}
const net::EmbeddedTestServer* server() const { return &https_server_; }
net::EmbeddedTestServer* server() { return &https_server_; }
GURL GetGoogleUrlWithPath(const std::string& path) const {
return server()->GetURL("www.google.com", path);
}
GURL GetGoogleUrl() const { return GetGoogleUrlWithPath("/landing.html"); }
GURL GetGoogleIframeUrl() const {
return GetGoogleUrlWithPath("/iframe.html");
}
GURL GetGoogleSubresourceFetchingWorkerUrl() const {
return GetGoogleUrlWithPath("/subresource_fetch_worker.js");
}
GURL GetGoogleRedirectUrl1() const {
return GetGoogleUrlWithPath("/redirect");
}
GURL GetGoogleRedirectUrl2() const {
return GetGoogleUrlWithPath("/redirect2");
}
GURL GetGoogleSubresourceUrl() const {
return GetGoogleUrlWithPath("/logo.png");
}
GURL GetExampleUrlWithPath(const std::string& path) const {
return server()->GetURL("www.example.com", path);
}
GURL GetExampleUrl() const { return GetExampleUrlWithPath("/landing.html"); }
void WaitForRequest(const GURL& url) {
auto it = received_headers_.find(url);
if (it != received_headers_.end())
return;
base::RunLoop loop;
done_callbacks_.emplace(url, loop.QuitClosure());
loop.Run();
}
// Returns whether a given |header| has been received for a |url|. If
// |url| has not been observed, fails an EXPECT and returns false.
bool HasReceivedHeader(const GURL& url, const std::string& header) const {
auto it = received_headers_.find(url);
EXPECT_TRUE(it != received_headers_.end());
if (it == received_headers_.end())
return false;
return it->second.find(header) != it->second.end();
}
// Returns the |header| received by |url| or nullopt if it hasn't been
// received. Fails an EXPECT if |url| hasn't been observed.
std::optional<std::string> GetReceivedHeader(
const GURL& url,
const std::string& header) const {
auto it = received_headers_.find(url);
EXPECT_TRUE(it != received_headers_.end());
if (it == received_headers_.end())
return std::nullopt;
auto it2 = it->second.find(header);
if (it2 == it->second.end())
return std::nullopt;
return it2->second;
}
void ClearReceivedHeaders() { received_headers_.clear(); }
bool LoadIframe(const content::ToRenderFrameHost& execution_target,
const GURL& url) {
if (!url.is_valid())
return false;
return EvalJs(execution_target, content::JsReplace(R"(
(async () => {
return new Promise(resolve => {
const iframe = document.createElement('iframe');
iframe.addEventListener('load', () => { resolve(true); });
iframe.addEventListener('error', () => { resolve(false); });
iframe.src = $1;
document.body.appendChild(iframe);
});
})();
)",
url))
.ExtractBool();
}
bool FetchResource(const content::ToRenderFrameHost& execution_target,
const GURL& url) {
if (!url.is_valid()) {
return false;
}
return EvalJs(execution_target, content::JsReplace(R"(
(async () => {
try {
await fetch($1);
return true;
} catch {
return false;
}
})();
)",
url))
.ExtractBool();
}
bool RunSubresourceFetchingWorker(
const content::ToRenderFrameHost& execution_target,
const GURL& worker_url,
const GURL& subresource_url) {
if (!worker_url.is_valid() || !subresource_url.is_valid()) {
return false;
}
return EvalJs(execution_target,
content::JsReplace(R"(
(async () => {
return await new Promise(resolve => {
const worker = new Worker($1);
worker.addEventListener('message', (e) => { resolve(e.data); });
worker.postMessage($2);
});
})();
)",
worker_url, subresource_url))
.ExtractBool();
}
content::WebContents* GetWebContents() { return GetWebContents(browser()); }
content::WebContents* GetWebContents(Browser* browser) {
return browser->tab_strip_model()->GetActiveWebContents();
}
void GoogleWebVisibilityTopFrameTest(bool top_frame_is_first_party);
// Registers a service worker for google.com root scope.
void RegisterServiceWorker(const std::string& worker_path) {
GURL url =
GetGoogleUrlWithPath("/service_worker/create_service_worker.html");
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
EXPECT_EQ("DONE", EvalJs(GetWebContents(),
base::StringPrintf("register('%s', '/');",
worker_path.c_str())));
}
// Registers the given service worker for google.com then tests navigation and
// subresource requests through the worker have X-Client-Data when
// appropriate.
void ServiceWorkerTest(const std::string& worker_path) {
RegisterServiceWorker(worker_path);
// Navigate to a Google URL.
GURL page_url =
GetGoogleUrlWithPath("/service_worker/fetch_from_page.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url));
EXPECT_TRUE(HasReceivedHeader(page_url, "X-Client-Data"));
// Check that there is a controller to check that the test is really testing
// service worker.
EXPECT_EQ(true,
EvalJs(GetWebContents(), "!!navigator.serviceWorker.controller"));
// Verify subresource requests from the page also have X-Client-Data.
EXPECT_EQ("hello", EvalJs(GetWebContents(),
base::StrCat({"fetch_from_page('",
GetGoogleUrl().spec(), "');"})));
EXPECT_TRUE(HasReceivedHeader(GetGoogleUrl(), "X-Client-Data"));
// But not if they are to non-Google domains.
EXPECT_EQ("hello", EvalJs(GetWebContents(),
base::StrCat({"fetch_from_page('",
GetExampleUrl().spec(), "');"})));
EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
// Navigate to a Google URL which causes redirects.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GetGoogleRedirectUrl1()));
// Verify redirect requests from google domains.
// Redirect to google domains.
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
// Redirect to non-google domains.
EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
}
// Creates a worker and tests that the main script and import scripts have
// X-Client-Data when appropriate. |page| is the page that creates the
// specified |worker|, which should be an "import_*_worker.js" script that is
// expected to import "empty.js" (as a relative path) and also accept an
// "import=" parameter specifying another script to import. This allows
// testing that the empty.js import request for google.com has the header, and
// an import request to example.com does not have the header.
void WorkerScriptTest(const std::string& page, const std::string& worker) {
// Build a worker URL for a google.com worker that imports
// an example.com script.
GURL absolute_import = GetExampleUrlWithPath("/workers/empty.js");
const std::string worker_path = base::StrCat(
{worker, "?import=",
base::EscapeQueryParamValue(absolute_import.spec(), false)});
GURL worker_url = GetGoogleUrlWithPath(worker_path);
// Build the page URL that tells the page to create the worker.
const std::string page_path =
base::StrCat({page, "?worker_url=",
base::EscapeQueryParamValue(worker_url.spec(), false)});
GURL page_url = GetGoogleUrlWithPath(page_path);
// Navigate and test.
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url));
EXPECT_EQ("DONE", EvalJs(GetWebContents(), "waitForMessage();"));
// The header should be on the main script request.
EXPECT_TRUE(HasReceivedHeader(worker_url, "X-Client-Data"));
// And on import script requests to Google.
EXPECT_TRUE(HasReceivedHeader(GetGoogleUrlWithPath("/workers/empty.js"),
"X-Client-Data"));
// But not on requests not to Google.
EXPECT_FALSE(HasReceivedHeader(absolute_import, "X-Client-Data"));
}
private:
// Custom request handler that record request headers and simulates a redirect
// from google.com to example.com.
std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
const net::test_server::HttpRequest& request);
net::EmbeddedTestServer https_server_;
// Stores the observed HTTP Request headers.
std::map<GURL, net::test_server::HttpRequest::HeaderMap> received_headers_;
// For waiting for requests.
std::map<GURL, base::OnceClosure> done_callbacks_;
};
std::unique_ptr<net::test_server::HttpResponse>
VariationsHttpHeadersBrowserTest::RequestHandler(
const net::test_server::HttpRequest& request) {
// Retrieve the host name (without port) from the request headers.
std::string host;
if (request.headers.find("Host") != request.headers.end())
host = request.headers.find("Host")->second;
if (host.find(':') != std::string::npos)
host = host.substr(0, host.find(':'));
// Recover the original URL of the request by replacing the host name in
// request.GetURL() (which is 127.0.0.1) with the host name from the request
// headers.
GURL::Replacements replacements;
replacements.SetHostStr(host);
GURL original_url = request.GetURL().ReplaceComponents(replacements);
// Memorize the request headers for this URL for later verification.
received_headers_[original_url] = request.headers;
auto iter = done_callbacks_.find(original_url);
if (iter != done_callbacks_.end()) {
std::move(iter->second).Run();
}
// Set up a test server that redirects according to the
// following redirect chain:
// https://www.google.com:<port>/redirect
// --> https://www.google.com:<port>/redirect2
// --> https://www.example.com:<port>/
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->AddCustomHeader("Access-Control-Allow-Origin", "*");
if (request.relative_url == GetGoogleRedirectUrl1().path()) {
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader("Location", GetGoogleRedirectUrl2().spec());
} else if (request.relative_url == GetGoogleRedirectUrl2().path()) {
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader("Location", GetExampleUrl().spec());
} else if (request.relative_url == GetExampleUrl().path()) {
http_response->set_code(net::HTTP_OK);
http_response->set_content("hello");
http_response->set_content_type("text/html");
} else if (request.relative_url == GetGoogleIframeUrl().path()) {
http_response->set_code(net::HTTP_OK);
http_response->set_content("hello");
http_response->set_content_type("text/html");
} else if (request.relative_url == GetGoogleSubresourceUrl().path()) {
http_response->set_code(net::HTTP_OK);
http_response->set_content("");
http_response->set_content_type("image/png");
} else if (request.relative_url ==
GetGoogleSubresourceFetchingWorkerUrl().path()) {
http_response->set_code(net::HTTP_OK);
http_response->set_content(R"(
self.addEventListener('message', async (e) => {
try {
await fetch(e.data);
self.postMessage(true);
} catch {
self.postMessage(false);
}
});
)");
http_response->set_content_type("text/html");
} else {
return nullptr;
}
return http_response;
}
struct LimitedLayerTestParams {
std::string test_name;
Study::Experiment group;
};
class VariationsHttpHeadersBrowserTestWithLimitedLayerBase
: public VariationsHttpHeadersBrowserTest {
public:
VariationsHttpHeadersBrowserTestWithLimitedLayerBase() = default;
~VariationsHttpHeadersBrowserTestWithLimitedLayerBase() override = default;
protected:
// BrowserTestBase:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kAcceptEmptySeedSignatureForTesting);
DisableTestingConfig();
VariationsHttpHeadersBrowserTest::SetUpCommandLine(command_line);
}
bool SetUpUserDataDirectoryWithGroup(std::optional<Study::Experiment> group) {
const base::FilePath user_data_dir =
base::PathService::CheckedGet(chrome::DIR_USER_DATA);
const base::FilePath seed_file_path =
user_data_dir.AppendASCII("VariationsSeedV1");
const base::FilePath local_state_path =
user_data_dir.Append(chrome::kLocalStateFilename);
std::string serialized_seed = CreateTestSeedWithLimitedEntropyLayer(
/*limited_layer_study_group=*/group)
.SerializeAsString();
std::string compressed_seed;
compression::GzipCompress(serialized_seed, &compressed_seed);
// Write the seed for the seed file experiment's treatment-group clients.
CHECK(base::WriteFile(seed_file_path, compressed_seed));
// Write the seed for the seed file experiment's control-group clients.
base::Value::Dict local_state;
local_state.SetByDottedPath(prefs::kVariationsCompressedSeed,
base::Base64Encode(compressed_seed));
CHECK(JSONFileValueSerializer(local_state_path).Serialize(local_state));
return true;
}
bool IsPrefDefaultValue(std::string_view pref_name) {
return local_state()->FindPreference(pref_name)->IsDefaultValue();
}
PrefService* local_state() { return g_browser_process->local_state(); }
};
class VariationsHttpHeadersBrowserTestWithActiveLimitedLayer
: public VariationsHttpHeadersBrowserTestWithLimitedLayerBase,
public ::testing::WithParamInterface<LimitedLayerTestParams> {
public:
VariationsHttpHeadersBrowserTestWithActiveLimitedLayer() = default;
~VariationsHttpHeadersBrowserTestWithActiveLimitedLayer() override = default;
protected:
// InProcessBrowserTest:
bool SetUpUserDataDirectory() override {
return VariationsHttpHeadersBrowserTestWithLimitedLayerBase::
SetUpUserDataDirectoryWithGroup(/*group=*/GetParam().group);
}
};
class VariationsHttpHeadersBrowserTestWithInactiveLimitedLayer
: public VariationsHttpHeadersBrowserTestWithLimitedLayerBase {
public:
VariationsHttpHeadersBrowserTestWithInactiveLimitedLayer() = default;
~VariationsHttpHeadersBrowserTestWithInactiveLimitedLayer() override =
default;
protected:
// InProcessBrowserTest:
bool SetUpUserDataDirectory() override {
return VariationsHttpHeadersBrowserTestWithLimitedLayerBase::
SetUpUserDataDirectoryWithGroup(/*group=*/std::nullopt);
}
};
// Associates |id| with GOOGLE_WEB_PROPERTIES_SIGNED_IN and creates a field
// trial for it.
void CreateGoogleSignedInFieldTrial(VariationID id) {
scoped_refptr<base::FieldTrial> trial_1(CreateTrialAndAssociateId(
"t1", "g1", GOOGLE_WEB_PROPERTIES_SIGNED_IN, id));
auto* provider = VariationsIdsProvider::GetInstance();
mojom::VariationsHeadersPtr signed_in_headers =
provider->GetClientDataHeaders(/*is_signed_in=*/true);
mojom::VariationsHeadersPtr signed_out_headers =
provider->GetClientDataHeaders(/*is_signed_in=*/false);
EXPECT_NE(
signed_in_headers->headers_map.at(mojom::GoogleWebVisibility::ANY),
signed_out_headers->headers_map.at(mojom::GoogleWebVisibility::ANY));
EXPECT_NE(signed_in_headers->headers_map.at(
mojom::GoogleWebVisibility::FIRST_PARTY),
signed_out_headers->headers_map.at(
mojom::GoogleWebVisibility::FIRST_PARTY));
}
// Creates FieldTrials associated with the FIRST_PARTY IDCollectionKeys and
// their corresponding ANY_CONTEXT keys.
void CreateFieldTrialsWithDifferentVisibilities() {
scoped_refptr<base::FieldTrial> trial_1(CreateTrialAndAssociateId(
"t1", "g1", GOOGLE_WEB_PROPERTIES_ANY_CONTEXT, 11));
scoped_refptr<base::FieldTrial> trial_2(CreateTrialAndAssociateId(
"t2", "g2", GOOGLE_WEB_PROPERTIES_FIRST_PARTY, 22));
scoped_refptr<base::FieldTrial> trial_3(CreateTrialAndAssociateId(
"t3", "g3", GOOGLE_WEB_PROPERTIES_TRIGGER_ANY_CONTEXT, 33));
scoped_refptr<base::FieldTrial> trial_4(CreateTrialAndAssociateId(
"t4", "g4", GOOGLE_WEB_PROPERTIES_TRIGGER_FIRST_PARTY, 44));
auto* provider = VariationsIdsProvider::GetInstance();
mojom::VariationsHeadersPtr signed_in_headers =
provider->GetClientDataHeaders(/*is_signed_in=*/true);
mojom::VariationsHeadersPtr signed_out_headers =
provider->GetClientDataHeaders(/*is_signed_in=*/false);
EXPECT_NE(signed_in_headers->headers_map.at(mojom::GoogleWebVisibility::ANY),
signed_in_headers->headers_map.at(
mojom::GoogleWebVisibility::FIRST_PARTY));
EXPECT_NE(signed_out_headers->headers_map.at(mojom::GoogleWebVisibility::ANY),
signed_out_headers->headers_map.at(
mojom::GoogleWebVisibility::FIRST_PARTY));
}
// Sets the limited entropy randomization source to a custom value so that we
// can have an expectation about a specific group being chosen.
void SetUpLimitedEntropyRandomizationSource() {
PrefService* local_state = g_browser_process->local_state();
local_state->SetString(
metrics::prefs::kMetricsLimitedEntropyRandomizationSource,
"00000000000000000000000000000001");
}
// Creates a trial named "t1" with 100 groups. If
// `with_google_web_experiment_ids` is true, each group will be associated with
// a variation ID.
// TODO(crbug.com/40729905): Refactor this so that creating the field trial
// either uses a different API or tighten the current API to set up a field
// trial that can only be made with the low entropy provider.
void CreateFieldTrial(const base::FieldTrial::EntropyProvider& entropy_provider,
bool with_google_web_experiment_ids) {
scoped_refptr<base::FieldTrial> trial =
base::FieldTrialList::FactoryGetFieldTrial(kTrialName, 100, "default",
entropy_provider);
for (int i = 1; i < 101; ++i) {
const std::string group_name = base::StringPrintf("%d", i);
if (with_google_web_experiment_ids) {
AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES_ANY_CONTEXT,
trial->trial_name(), group_name, i);
}
trial->AppendGroup(group_name, 1);
}
// Activate the trial. This corresponds to ACTIVATE_ON_STARTUP for server-side
// studies.
trial->Activate();
}
// Verify in an integration test that the variations header (X-Client-Data) is
// attached to network requests to Google but stripped on redirects.
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
TestStrippingHeadersFromResourceRequest) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetGoogleRedirectUrl1()));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
}
// Verify in an integration that that the variations header (X-Client-Data) is
// correctly attached and stripped from network requests.
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
TestStrippingHeadersFromSubresourceRequest) {
GURL url = server()->GetURL("/simple_page.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
EXPECT_TRUE(
FetchResource(GetWebContents(browser()), GetGoogleRedirectUrl1()));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
}
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest, Incognito) {
Browser* incognito = CreateIncognitoBrowser();
ASSERT_TRUE(ui_test_utils::NavigateToURL(incognito, GetGoogleUrl()));
EXPECT_FALSE(HasReceivedHeader(GetGoogleUrl(), "X-Client-Data"));
EXPECT_TRUE(
FetchResource(GetWebContents(incognito), GetGoogleSubresourceUrl()));
EXPECT_FALSE(HasReceivedHeader(GetGoogleSubresourceUrl(), "X-Client-Data"));
}
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest, UserSignedIn) {
// Ensure GetClientDataHeader() returns different values when signed in vs
// not signed in.
VariationID signed_in_id = 8;
CreateGoogleSignedInFieldTrial(signed_in_id);
// Sign the user in.
signin::MakePrimaryAccountAvailable(
IdentityManagerFactory::GetForProfile(browser()->profile()),
"main_email@gmail.com", signin::ConsentLevel::kSync);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetGoogleUrl()));
std::optional<std::string> header =
GetReceivedHeader(GetGoogleUrl(), "X-Client-Data");
ASSERT_TRUE(header);
// Verify that the received header contains the ID.
std::set<VariationID> ids;
std::set<VariationID> trigger_ids;
ASSERT_TRUE(ExtractVariationIds(header.value(), &ids, &trigger_ids));
EXPECT_TRUE(base::Contains(ids, signed_in_id));
// Verify that both headers returned by GetClientDataHeaders() contain the ID.
mojom::VariationsHeadersPtr headers =
VariationsIdsProvider::GetInstance()->GetClientDataHeaders(
/*is_signed_in=*/true);
const std::string variations_header_first_party =
headers->headers_map.at(mojom::GoogleWebVisibility::FIRST_PARTY);
const std::string variations_header_any_context =
headers->headers_map.at(mojom::GoogleWebVisibility::ANY);
std::set<VariationID> ids_first_party;
std::set<VariationID> trigger_ids_first_party;
ASSERT_TRUE(ExtractVariationIds(variations_header_first_party,
&ids_first_party, &trigger_ids_first_party));
EXPECT_TRUE(base::Contains(ids_first_party, signed_in_id));
std::set<VariationID> ids_any_context;
std::set<VariationID> trigger_ids_any_context;
ASSERT_TRUE(ExtractVariationIds(variations_header_any_context,
&ids_any_context, &trigger_ids_any_context));
EXPECT_TRUE(base::Contains(ids_any_context, signed_in_id));
}
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest, UserNotSignedIn) {
// Ensure GetClientDataHeader() returns different values when signed in vs
// not signed in.
VariationID signed_in_id = 8;
CreateGoogleSignedInFieldTrial(signed_in_id);
// By default the user is not signed in.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetGoogleUrl()));
std::optional<std::string> header =
GetReceivedHeader(GetGoogleUrl(), "X-Client-Data");
ASSERT_TRUE(header);
// Verify that the received header does not contain the ID.
std::set<VariationID> ids;
std::set<VariationID> trigger_ids;
ASSERT_TRUE(ExtractVariationIds(header.value(), &ids, &trigger_ids));
EXPECT_FALSE(base::Contains(ids, signed_in_id));
// Verify that both headers returned by GetClientDataHeaders() do not contain
// the ID.
mojom::VariationsHeadersPtr headers =
VariationsIdsProvider::GetInstance()->GetClientDataHeaders(
/*is_signed_in=*/false);
const std::string variations_header_first_party =
headers->headers_map.at(mojom::GoogleWebVisibility::FIRST_PARTY);
const std::string variations_header_any_context =
headers->headers_map.at(mojom::GoogleWebVisibility::ANY);
std::set<VariationID> ids_first_party;
std::set<VariationID> trigger_ids_first_party;
ASSERT_TRUE(ExtractVariationIds(variations_header_first_party,
&ids_first_party, &trigger_ids_first_party));
EXPECT_FALSE(base::Contains(ids_first_party, signed_in_id));
std::set<VariationID> ids_any_context;
std::set<VariationID> trigger_ids_any_context;
ASSERT_TRUE(ExtractVariationIds(variations_header_any_context,
&ids_any_context, &trigger_ids_any_context));
EXPECT_FALSE(base::Contains(ids_any_context, signed_in_id));
}
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
PRE_CheckLowEntropySourceValue) {
// We use the PRE_ prefix mechanism to ensure that this test always runs
// before CheckLowEntropyValue(). None of the subclasses in the
// InProcessBrowserTest class allow us to set this pref early enough to be
// read by the variations code, which runs very early during the browser
// startup.
PrefService* local_state = g_browser_process->local_state();
local_state->SetInteger(metrics::prefs::kMetricsLowEntropySource, 5);
}
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
CheckLowEntropySourceValue) {
auto entropy_providers = g_browser_process->GetMetricsServicesManager()
->CreateEntropyProvidersForTesting();
// `with_google_web_experiment_ids` is true so that the low entropy provider
// is used for randomization.
CreateFieldTrial(entropy_providers->low_entropy(),
/*with_google_web_experiment_ids=*/true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetGoogleUrl()));
std::optional<std::string> header =
GetReceivedHeader(GetGoogleUrl(), "X-Client-Data");
ASSERT_TRUE(header);
std::set<VariationID> variation_ids;
std::set<VariationID> trigger_ids;
ASSERT_TRUE(
ExtractVariationIds(header.value(), &variation_ids, &trigger_ids));
// 3320983 is the offset value of kLowEntropySourceVariationIdRangeMin + 5.
EXPECT_TRUE(base::Contains(variation_ids, 3320983));
// Check that the reported group in the header is consistent with the low
// entropy source. 33 is the group that is derived from the low entropy source
// value of 5.
EXPECT_TRUE(base::Contains(variation_ids, 33));
}
INSTANTIATE_TEST_SUITE_P(
,
VariationsHttpHeadersBrowserTestWithActiveLimitedLayer,
::testing::Values(
LimitedLayerTestParams{.test_name = "LimitedLayerStudyWithExperimentID",
.group = CreateExperimentGroup({.id = 3389050})},
LimitedLayerTestParams{
.test_name = "LimitedLayerStudyWithTriggerExperimentID",
.group = CreateExperimentGroup({.trigger_id = 3389051})},
LimitedLayerTestParams{
.test_name = "LimitedLayerStudyWithoutExperimentIDs",
.group = CreateExperimentGroup()}),
[](const ::testing::TestParamInfo<LimitedLayerTestParams>& params) {
return params.param.test_name;
});
// Verifies that a client's low entropy source value is omitted from the
// X-Client-Data header when a seed with an active limited layer is applied. A
// limited layer is active when a limited-layer-constrained study applies to the
// client's channel, platform, and Chrome version.
IN_PROC_BROWSER_TEST_P(VariationsHttpHeadersBrowserTestWithActiveLimitedLayer,
OmitLowEntropySource) {
// Check that both the low and limited entropy sources have been generated.
ASSERT_FALSE(IsPrefDefaultValue(
metrics::prefs::kMetricsLimitedEntropyRandomizationSource));
ASSERT_FALSE(IsPrefDefaultValue(metrics::prefs::kMetricsLowEntropySource));
// Check that the seed was applied by checking that the generic study was
// registered.
ASSERT_TRUE(base::FieldTrialList::TrialExists(kSomeStudyName));
// Check that the limited-layer-constrained study was also registered.
ASSERT_TRUE(base::FieldTrialList::TrialExists(kLimitedLayerStudyName));
// Cause the study group's experiment ID (if any) to be included in eligible
// X-Client-Data headers.
base::FieldTrialList::Find(kLimitedLayerStudyName)->Activate();
// Make a request and get its VariationIDs.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetGoogleUrl()));
std::optional<std::string> header =
GetReceivedHeader(GetGoogleUrl(), "X-Client-Data");
ASSERT_FALSE(header == std::nullopt);
std::set<VariationID> ids;
std::set<VariationID> trigger_ids;
ASSERT_TRUE(ExtractVariationIds(header.value(), &ids, &trigger_ids));
// Check that the client's offset low entropy source value was omitted from
// the X-Client-Data header.
const int low_entropy_source =
local_state()->GetInteger(metrics::prefs::kMetricsLowEntropySource);
const int offset_low_entropy_source =
low_entropy_source + internal::kLowEntropySourceVariationIdRangeMin;
EXPECT_FALSE(base::Contains(ids, offset_low_entropy_source));
EXPECT_FALSE(base::Contains(trigger_ids, offset_low_entropy_source));
std::set<VariationID> expected_ids{kGenericExperimentGroupId};
std::set<VariationID> expected_trigger_ids{kGenericExperimentGroupTriggerId};
std::optional<Study::Experiment> limited_layer_study_group = GetParam().group;
if (limited_layer_study_group.has_value() &&
limited_layer_study_group->has_google_web_experiment_id()) {
expected_ids.insert(limited_layer_study_group->google_web_experiment_id());
} else if (limited_layer_study_group.has_value() &&
limited_layer_study_group
->has_google_web_trigger_experiment_id()) {
expected_trigger_ids.insert(
limited_layer_study_group->google_web_trigger_experiment_id());
}
EXPECT_THAT(ids, ::testing::UnorderedElementsAreArray(expected_ids));
EXPECT_THAT(trigger_ids,
::testing::UnorderedElementsAreArray(expected_trigger_ids));
}
// Verifies that a client's low entropy source value is included in the
// X-Client-Data header when a seed with an inactive limited layer is applied. A
// limited layer is inactive when the seed contains a limited layer but no
// limited-layer-constrained studies apply to the client's channel, platform,
// and Chrome version.
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTestWithInactiveLimitedLayer,
SendLowEntropySource) {
// Check that both the low and limited entropy sources have been generated.
ASSERT_FALSE(IsPrefDefaultValue(
metrics::prefs::kMetricsLimitedEntropyRandomizationSource));
ASSERT_FALSE(IsPrefDefaultValue((metrics::prefs::kMetricsLowEntropySource)));
// Check that the seed was applied by checking that the generic study was
// registered.
ASSERT_TRUE(base::FieldTrialList::TrialExists(kSomeStudyName));
// Check that the limited-layer-constrained study was not registered.
ASSERT_FALSE(base::FieldTrialList::TrialExists(kLimitedLayerStudyName));
// Make a request and get its VariationIDs.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetGoogleUrl()));
std::optional<std::string> header =
GetReceivedHeader(GetGoogleUrl(), "X-Client-Data");
ASSERT_FALSE(header == std::nullopt);
std::set<VariationID> ids;
std::set<VariationID> trigger_ids;
ASSERT_TRUE(ExtractVariationIds(header.value(), &ids, &trigger_ids));
// Check that the client's offset low entropy source value was included in
// the X-Client-Data header.
const int low_entropy_source =
local_state()->GetInteger(metrics::prefs::kMetricsLowEntropySource);
const int offset_low_entropy_source =
low_entropy_source + internal::kLowEntropySourceVariationIdRangeMin;
EXPECT_THAT(ids, ::testing::UnorderedElementsAreArray(
{kGenericExperimentGroupId, offset_low_entropy_source}));
EXPECT_THAT(trigger_ids, ::testing::UnorderedElementsAreArray(
{kGenericExperimentGroupTriggerId}));
}
// The PRE_ prefix ensures this runs before
// LimitedEntropyRandomization_ExperimentLogging.
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
PRE_LimitedEntropyRandomization_ExperimentLogging) {
SetUpLimitedEntropyRandomizationSource();
}
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
LimitedEntropyRandomization_ExperimentLogging) {
// CreateEntropyProvidersForTesting() ensures a limited entropy provider is
// created.
auto entropy_providers = g_browser_process->GetMetricsServicesManager()
->CreateEntropyProvidersForTesting();
ASSERT_TRUE(entropy_providers->has_limited_entropy());
// Create a field trial that will be randomized with the limited entropy
// provider.
CreateFieldTrial(entropy_providers->limited_entropy(),
/*with_google_web_experiment_ids=*/true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetGoogleUrl()));
std::optional<std::string> header =
GetReceivedHeader(GetGoogleUrl(), "X-Client-Data");
ASSERT_TRUE(header);
std::set<VariationID> variation_ids;
std::set<VariationID> trigger_ids;
ASSERT_TRUE(
ExtractVariationIds(header.value(), &variation_ids, &trigger_ids));
// 56 is the group that is derived from the setup in
// `PRE_CheckGoogleWebExperimentIdUnderLimitedEntropyRandomization`.
EXPECT_EQ("56", base::FieldTrialList::FindFullName(kTrialName));
// Check that the reported group in the header is consistent with the
// limited entropy randomization source.
EXPECT_TRUE(base::Contains(variation_ids, 56));
}
// The PRE_ prefix ensures this runs before
// LimitedEntropyRandomization_ExperimentLoggingWithoutGoogleWebExperimentationId.
IN_PROC_BROWSER_TEST_F(
VariationsHttpHeadersBrowserTest,
PRE_LimitedEntropyRandomization_ExperimentLoggingWithoutGoogleWebExperimentationId) {
SetUpLimitedEntropyRandomizationSource();
}
IN_PROC_BROWSER_TEST_F(
VariationsHttpHeadersBrowserTest,
LimitedEntropyRandomization_ExperimentLoggingWithoutGoogleWebExperimentationId) {
// CreateEntropyProvidersForTesting() ensures a limited entropy provider is
// created.
auto entropy_providers = g_browser_process->GetMetricsServicesManager()
->CreateEntropyProvidersForTesting();
ASSERT_TRUE(entropy_providers->has_limited_entropy());
CreateFieldTrial(entropy_providers->limited_entropy(),
/*with_google_web_experiment_ids=*/false);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetGoogleUrl()));
std::optional<std::string> header =
GetReceivedHeader(GetGoogleUrl(), "X-Client-Data");
ASSERT_TRUE(header);
std::set<VariationID> variation_ids;
std::set<VariationID> trigger_ids;
ASSERT_TRUE(
ExtractVariationIds(header.value(), &variation_ids, &trigger_ids));
// 56 is the group that is derived from the setup in
// `PRE_CheckGoogleWebExperimentIdUnderLimitedEntropyRandomization`.
EXPECT_EQ("56", base::FieldTrialList::FindFullName(kTrialName));
// The experiment does not have a google_web_experiment_id and thus should
// NOT appear in the header.
EXPECT_FALSE(base::Contains(variation_ids, 56));
}
void VariationsHttpHeadersBrowserTest::GoogleWebVisibilityTopFrameTest(
bool top_frame_is_first_party) {
CreateFieldTrialsWithDifferentVisibilities();
mojom::VariationsHeadersPtr signed_out_headers =
VariationsIdsProvider::GetInstance()->GetClientDataHeaders(
/*is_signed_in=*/false);
const std::string expected_header_value =
top_frame_is_first_party
? signed_out_headers->headers_map.at(
mojom::GoogleWebVisibility::FIRST_PARTY)
: signed_out_headers->headers_map.at(mojom::GoogleWebVisibility::ANY);
// Load a top frame.
const GURL top_frame_url =
top_frame_is_first_party ? GetGoogleUrl() : GetExampleUrl();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), top_frame_url));
if (top_frame_is_first_party) {
EXPECT_EQ(GetReceivedHeader(top_frame_url, "X-Client-Data"),
expected_header_value);
} else {
EXPECT_FALSE(GetReceivedHeader(top_frame_url, "X-Client-Data"));
}
// Load Google iframe.
EXPECT_TRUE(LoadIframe(GetWebContents(browser()), GetGoogleIframeUrl()));
EXPECT_EQ(GetReceivedHeader(GetGoogleIframeUrl(), "X-Client-Data"),
expected_header_value);
// Fetch Google subresource.
EXPECT_TRUE(FetchResource(ChildFrameAt(GetWebContents(browser()), 0),
GetGoogleSubresourceUrl()));
EXPECT_EQ(GetReceivedHeader(GetGoogleSubresourceUrl(), "X-Client-Data"),
expected_header_value);
// Prepare for loading Google subresource from a dedicated worker. The same
// URL subresource was loaded above. So need to clear `received_headers_`.
ClearReceivedHeaders();
// Start Google worker and fetch Google subresource from the worker.
EXPECT_TRUE(RunSubresourceFetchingWorker(
ChildFrameAt(GetWebContents(browser()), 0),
GetGoogleSubresourceFetchingWorkerUrl(), GetGoogleSubresourceUrl()));
EXPECT_EQ(GetReceivedHeader(GetGoogleSubresourceFetchingWorkerUrl(),
"X-Client-Data"),
expected_header_value);
EXPECT_EQ(GetReceivedHeader(GetGoogleSubresourceUrl(), "X-Client-Data"),
expected_header_value);
}
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
TestGoogleWebVisibilityInFirstPartyContexts) {
GoogleWebVisibilityTopFrameTest(/*top_frame_is_first_party=*/true);
}
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
TestGoogleWebVisibilityInThirdPartyContexts) {
GoogleWebVisibilityTopFrameTest(/*top_frame_is_first_party=*/false);
}
IN_PROC_BROWSER_TEST_F(
VariationsHttpHeadersBrowserTest,
TestStrippingHeadersFromRequestUsingSimpleURLLoaderWithProfileNetworkContext) {
GURL url = GetGoogleRedirectUrl1();
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = url;
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
std::unique_ptr<network::SimpleURLLoader> loader =
CreateSimpleURLLoaderWithVariationsHeaderUnknownSignedIn(
std::move(resource_request), InIncognito::kNo,
TRAFFIC_ANNOTATION_FOR_TESTS);
content::StoragePartition* partition =
browser()->profile()->GetDefaultStoragePartition();
network::SharedURLLoaderFactory* loader_factory =
partition->GetURLLoaderFactoryForBrowserProcess().get();
content::SimpleURLLoaderTestHelper loader_helper;
loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
loader_factory, loader_helper.GetCallbackDeprecated());
// Wait for the response to complete.
loader_helper.WaitForCallback();
EXPECT_EQ(net::OK, loader->NetError());
EXPECT_TRUE(loader_helper.response_body());
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
}
IN_PROC_BROWSER_TEST_F(
VariationsHttpHeadersBrowserTest,
TestStrippingHeadersFromRequestUsingSimpleURLLoaderWithGlobalSystemNetworkContext) {
GURL url = GetGoogleRedirectUrl1();
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = url;
std::unique_ptr<network::SimpleURLLoader> loader =
CreateSimpleURLLoaderWithVariationsHeaderUnknownSignedIn(
std::move(resource_request), InIncognito::kNo,
TRAFFIC_ANNOTATION_FOR_TESTS);
network::SharedURLLoaderFactory* loader_factory =
g_browser_process->system_network_context_manager()
->GetSharedURLLoaderFactory()
.get();
content::SimpleURLLoaderTestHelper loader_helper;
loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
loader_factory, loader_helper.GetCallbackDeprecated());
// Wait for the response to complete.
loader_helper.WaitForCallback();
EXPECT_EQ(net::OK, loader->NetError());
EXPECT_TRUE(loader_helper.response_body());
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl1(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetGoogleRedirectUrl2(), "X-Client-Data"));
EXPECT_TRUE(HasReceivedHeader(GetExampleUrl(), "Host"));
EXPECT_FALSE(HasReceivedHeader(GetExampleUrl(), "X-Client-Data"));
}
// Verify in an integration test that the variations header (X-Client-Data) is
// attached to service worker navigation preload requests. Regression test
// for https://crbug.com/873061.
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
ServiceWorkerNavigationPreload) {
// Register a service worker that uses navigation preload.
RegisterServiceWorker("/service_worker/navigation_preload_worker.js");
// Verify "X-Client-Data" is present on the navigation to Google.
// Also test that "Service-Worker-Navigation-Preload" is present to verify
// we are really testing the navigation preload request.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetGoogleUrl()));
EXPECT_TRUE(HasReceivedHeader(GetGoogleUrl(), "X-Client-Data"));
EXPECT_TRUE(
HasReceivedHeader(GetGoogleUrl(), "Service-Worker-Navigation-Preload"));
}
// Verify in an integration test that the variations header (X-Client-Data) is
// attached to requests after the service worker falls back to network.
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
ServiceWorkerNetworkFallback) {
ServiceWorkerTest("/service_worker/network_fallback_worker.js");
}
// Verify in an integration test that the variations header (X-Client-Data) is
// not exposed in the service worker fetch event.
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
ServiceWorkerDoesNotSeeHeader) {
ServiceWorkerTest("/service_worker/fail_on_variations_header_worker.js");
}
// Verify in an integration test that the variations header (X-Client-Data) is
// attached to requests after the service worker does
// respondWith(fetch(request)).
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
ServiceWorkerRespondWithFetch) {
ServiceWorkerTest("/service_worker/respond_with_fetch_worker.js");
}
// Verify in an integration test that the variations header (X-Client-Data) is
// attached to requests for service worker scripts when installing and updating.
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest, ServiceWorkerScript) {
// Register a service worker that imports scripts.
GURL absolute_import = GetExampleUrlWithPath("/service_worker/empty.js");
const std::string worker_path =
"/service_worker/import_scripts_worker.js?import=" +
base::EscapeQueryParamValue(absolute_import.spec(), false);
RegisterServiceWorker(worker_path);
// Test that the header is present on the main script request.
EXPECT_TRUE(
HasReceivedHeader(GetGoogleUrlWithPath(worker_path), "X-Client-Data"));
// And on import script requests to Google.
EXPECT_TRUE(HasReceivedHeader(
GetGoogleUrlWithPath("/service_worker/empty.js"), "X-Client-Data"));
// But not on requests not to Google.
EXPECT_FALSE(HasReceivedHeader(absolute_import, "X-Client-Data"));
// Prepare for the update case.
ClearReceivedHeaders();
// Tries to update the service worker.
EXPECT_EQ("DONE", EvalJs(GetWebContents(), "update();"));
// Test that the header is present on the main script request.
EXPECT_TRUE(
HasReceivedHeader(GetGoogleUrlWithPath(worker_path), "X-Client-Data"));
// And on import script requests to Google.
EXPECT_TRUE(HasReceivedHeader(
GetGoogleUrlWithPath("/service_worker/empty.js"), "X-Client-Data"));
// But not on requests not to Google.
EXPECT_FALSE(HasReceivedHeader(absolute_import, "X-Client-Data"));
}
// Verify in an integration test that the variations header (X-Client-Data) is
// attached to requests for shared worker scripts.
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest, SharedWorkerScript) {
WorkerScriptTest("/workers/create_shared_worker.html",
"/workers/import_scripts_shared_worker.js");
}
// Verify in an integration test that the variations header (X-Client-Data) is
// attached to requests for dedicated worker scripts.
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
DedicatedWorkerScript) {
WorkerScriptTest("/workers/create_dedicated_worker.html",
"/workers/import_scripts_dedicated_worker.js");
}
// A test fixture for testing prefetches from the Loading Predictor.
class VariationsHttpHeadersBrowserTestWithOptimizationGuide
: public VariationsHttpHeadersBrowserTest {
public:
VariationsHttpHeadersBrowserTestWithOptimizationGuide() {
std::vector<base::test::FeatureRefAndParams> enabled = {
{features::kLoadingPredictorPrefetch, {}},
{features::kLoadingPredictorUseOptimizationGuide,
{{"use_predictions_for_preconnect", "true"}}},
{optimization_guide::features::kOptimizationHints, {}}};
std::vector<base::test::FeatureRef> disabled = {
features::kLoadingPredictorUseLocalPredictions};
feature_list_.InitWithFeaturesAndParameters(enabled, disabled);
}
void SetUpCommandLine(base::CommandLine* command_line) override {
VariationsHttpHeadersBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(
::switches::kLoadingPredictorAllowLocalRequestForTesting);
}
std::unique_ptr<content::TestNavigationManager> NavigateToURLAsync(
const GURL& url) {
chrome::NewTab(browser());
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
DCHECK(tab);
auto observer = std::make_unique<content::TestNavigationManager>(tab, url);
tab->GetController().LoadURL(url, content::Referrer(),
ui::PAGE_TRANSITION_TYPED, std::string());
return observer;
}
void SetUpOptimizationHint(
const GURL& url,
const std::vector<std::string>& predicted_subresource_urls) {
auto* optimization_guide_keyed_service =
OptimizationGuideKeyedServiceFactory::GetForProfile(
browser()->profile());
ASSERT_TRUE(optimization_guide_keyed_service);
optimization_guide::proto::LoadingPredictorMetadata
loading_predictor_metadata;
for (const auto& subresource_url : predicted_subresource_urls) {
loading_predictor_metadata.add_subresources()->set_url(subresource_url);
}
optimization_guide::OptimizationMetadata optimization_metadata;
optimization_metadata.set_loading_predictor_metadata(
loading_predictor_metadata);
optimization_guide_keyed_service->AddHintForTesting(
url, optimization_guide::proto::LOADING_PREDICTOR,
optimization_metadata);
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Verify in an integration test that that the variations header (X-Client-Data)
// is correctly attached to prefetch requests from the Loading Predictor.
IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTestWithOptimizationGuide,
Prefetch) {
GURL url = server()->GetURL("test.com", "/simple_page.html");
GURL google_url = GetGoogleSubresourceUrl();
GURL non_google_url = GetExampleUrl();
// Set up optimization hints.
std::vector<std::string> hints = {google_url.spec(), non_google_url.spec()};
SetUpOptimizationHint(url, hints);
// Navigate.
auto observer = NavigateToURLAsync(url);
EXPECT_TRUE(observer->WaitForRequestStart());
WaitForRequest(google_url);
WaitForRequest(non_google_url);
// Expect header on google urls only.
EXPECT_TRUE(HasReceivedHeader(google_url, "X-Client-Data"));
EXPECT_FALSE(HasReceivedHeader(non_google_url, "X-Client-Data"));
}
} // namespace
} // namespace variations