blob: 4dd1b4976fcd9a134df60a9eab960379365a406f [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file tests that Web Workers (a Content feature) work in the Chromium
// embedder.
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/test/bind.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/cookie_settings.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_context.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 "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 "third_party/blink/public/common/features.h"
#include "third_party/re2/src/re2/re2.h"
namespace {
enum class UserAgentOriginTrialTestType {
UAReduction,
UADeprecation,
UAReductionAndDeprecation
};
} // namespace
// A simple fixture used for testing dedicated workers and shared workers. The
// fixture stashes the HTTP request to the worker script for inspecting the
// headers.
//
// This is in //chrome instead of //content since the tests exercise the
// |kBlockThirdPartyCookies| preference which is not a //content concept.
class ChromeWorkerBrowserTest : public InProcessBrowserTest {
public:
void SetUp() override {
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(&ChromeWorkerBrowserTest::CaptureHeaderHandler,
base::Unretained(this), "/capture"));
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
embedded_test_server()->StartAcceptingConnections();
}
protected:
// Tests worker script fetch (always same-origin) is not affected by the
// third-party cookie blocking configuration.
// This is the regression test for https://crbug.com/933287.
void TestWorkerScriptFetchWithThirdPartyCookieBlocking(
content_settings::CookieControlsMode cookie_controls_mode,
const std::string& test_url) {
const std::string kCookie = "foo=bar";
// Set up third-party cookie blocking.
browser()->profile()->GetPrefs()->SetInteger(
prefs::kCookieControlsMode, static_cast<int>(cookie_controls_mode));
// Make sure cookies are not set.
ASSERT_TRUE(
GetCookies(browser()->profile(), embedded_test_server()->base_url())
.empty());
// Request for the worker script should not send cookies.
{
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(test_url)));
run_loop.Run();
EXPECT_FALSE(base::Contains(header_map_, "Cookie"));
}
// Set a cookie.
ASSERT_TRUE(SetCookie(browser()->profile(),
embedded_test_server()->base_url(), kCookie));
// Request for the worker script should send the cookie regardless of the
// third-party cookie blocking configuration.
{
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(test_url)));
run_loop.Run();
EXPECT_TRUE(base::Contains(header_map_, "Cookie"));
EXPECT_EQ(kCookie, header_map_["Cookie"]);
}
}
// TODO(nhiroki): Add tests for creating workers from third-party iframes
// while third-party cookie blocking is enabled. This expects that cookies are
// not blocked.
private:
std::unique_ptr<net::test_server::HttpResponse> CaptureHeaderHandler(
const std::string& path,
const net::test_server::HttpRequest& request) {
if (request.GetURL().path() != path)
return nullptr;
// Stash the HTTP request headers.
header_map_ = request.headers;
std::move(quit_closure_).Run();
return std::make_unique<net::test_server::BasicHttpResponse>();
}
net::test_server::HttpRequest::HeaderMap header_map_;
base::OnceClosure quit_closure_;
};
IN_PROC_BROWSER_TEST_F(ChromeWorkerBrowserTest,
DedicatedWorkerScriptFetchWithThirdPartyBlocking) {
TestWorkerScriptFetchWithThirdPartyCookieBlocking(
content_settings::CookieControlsMode::kBlockThirdParty,
"/workers/create_dedicated_worker.html?worker_url=/capture");
}
IN_PROC_BROWSER_TEST_F(ChromeWorkerBrowserTest,
DedicatedWorkerScriptFetchWithoutThirdPartyBlocking) {
TestWorkerScriptFetchWithThirdPartyCookieBlocking(
content_settings::CookieControlsMode::kOff,
"/workers/create_dedicated_worker.html?worker_url=/capture");
}
IN_PROC_BROWSER_TEST_F(ChromeWorkerBrowserTest,
SharedWorkerScriptFetchWithThirdPartyBlocking) {
TestWorkerScriptFetchWithThirdPartyCookieBlocking(
content_settings::CookieControlsMode::kBlockThirdParty,
"/workers/create_shared_worker.html?worker_url=/capture");
}
IN_PROC_BROWSER_TEST_F(ChromeWorkerBrowserTest,
SharedWorkerScriptFetchWithoutThirdPartyBlocking) {
TestWorkerScriptFetchWithThirdPartyCookieBlocking(
content_settings::CookieControlsMode::kOff,
"/workers/create_shared_worker.html?worker_url=/capture");
}
// A test fixture used for testing that dedicated and shared workers have the
// correct user agent value, either the full user agent string or the reduced
// user agent string if the UserAgentReduction Origin Trial is on.
class ChromeWorkerUserAgentBrowserTest
: public InProcessBrowserTest,
public testing::WithParamInterface<UserAgentOriginTrialTestType> {
public:
ChromeWorkerUserAgentBrowserTest() = default;
// The URL that was used to register the Origin Trial token.
static constexpr char kOriginUrl[] = "https://127.0.0.1:44444";
void SetUpCommandLine(base::CommandLine* command_line) override {
// The public key for the default privatey key used by the
// tools/origin_trials/generate_token.py tool.
static constexpr char kOriginTrialTestPublicKey[] =
"dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNA=";
command_line->AppendSwitchASCII("origin-trial-public-key",
kOriginTrialTestPublicKey);
}
// We use a URLLoaderInterceptor, rather than the EmbeddedTestServer, since
// the origin trial token in the response is associated with a fixed origin,
// whereas EmbeddedTestServer serves content on a random port.
std::unique_ptr<content::URLLoaderInterceptor> CreateUrlLoaderInterceptor() {
return std::make_unique<content::URLLoaderInterceptor>(
base::BindLambdaForTesting(
[&](content::URLLoaderInterceptor::RequestParams* params) {
if (expected_request_urls_.find(params->url_request.url) ==
expected_request_urls_.end())
return false;
std::string path = "chrome/test/data/workers";
path.append(std::string(params->url_request.url.path_piece()));
std::string headers = "HTTP/1.1 200 OK\n";
base::StrAppend(
&headers,
{"Content-Type: text/",
base::EndsWith(params->url_request.url.path_piece(), ".js")
? "javascript"
: "html",
"\n"});
// Generated by running (in tools/origin_trials):
// generate_token.py https://127.0.0.1:44444 UserAgentReduction
// --expire-timestamp=2000000000
static constexpr char kUAReducedOriginTrialToken[] =
"A93QtcQ0CRKf5ioPasUwNbweXQWgbI4ZEshiz+"
"YS7dkQEWVfW9Ua2pTnA866sZwRzuElkPwsUdGdIaW0fRUP8AwAAABceyJvcm"
"lnaW4iOiAiaH"
"R0cHM6Ly8xMjcuMC4wLjE6NDQ0NDQiLCAiZmVhdHVyZSI6ICJVc2VyQWdlbn"
"RSZWR1Y3Rpb2"
"4iLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0=";
// Generated by running (in tools/origin_trials):
// generate_token.py https://127.0.0.1:44444
// SendFullUserAgentAfterReduction
// --expire-timestamp=2000000000
static constexpr char kUAFullOriginTrialToken[] =
"A6+Ti/9KuXTgmFzOQwkTuO8k0QFH8vUaxmv0CllAET1/"
"307KShF6fhskMuBqFUvqO7ViAkZ+"
"NSeJhQI0n5aLggsAAABpeyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6"
"NDQ0NDQiLCAiZmVhdHVyZSI6ICJTZW5kRnVsbFVzZXJBZ2VudEFmdGVyUmVk"
"dWN0aW9uIiwgImV4cGlyeSI6IDIwMDAwMDAwMDB9";
switch (GetParam()) {
case UserAgentOriginTrialTestType::UAReduction:
base::StrAppend(
&headers,
{"Origin-Trial: ", kUAReducedOriginTrialToken, "\n"});
break;
case UserAgentOriginTrialTestType::UADeprecation:
base::StrAppend(
&headers,
{"Origin-Trial: ", kUAFullOriginTrialToken, "\n"});
break;
case UserAgentOriginTrialTestType::UAReductionAndDeprecation:
base::StrAppend(&headers,
{"Origin-Trial: ", kUAReducedOriginTrialToken,
",", kUAFullOriginTrialToken, "\n"});
break;
default:
break;
}
content::URLLoaderInterceptor::WriteResponse(
path, params->client.get(), &headers);
return true;
}));
}
void SetExpectedRequestURLs(const std::set<GURL>& expected_request_urls) {
expected_request_urls_ = expected_request_urls;
}
// Return |true| in the two conditions: 1) if the user agent minor version
// matches "0.0.0" and we expect the user agent to be reduced in UAReduction
// origin trial. 2) if the user agent minor version doesn't match "0.0.0" and
// we don't expect the user agent to be reduced in UAReduction origin trial.
// Otherwise, return false. We should not always expect reduced UA when
// kReduceUserAgentMinorVersion feature turns on, it would give false positive
// test results when the feature turns on as default. For example, if we
// expect full UA in the UADeprecation origin trial with
// kReduceUserAgentMinorVersion turned on, the actual value gives reduced UA,
// and the validation will succeed in this case which causes us to ignore
// actual bugs in code.
void CheckUserAgentString(const std::string& user_agent_value,
const bool expected_user_agent_reduced) {
// A regular expression that matches Chrome/{major_version}.{minor_version}
// in the User-Agent string, where the {minor_version} is captured.
static constexpr char kChromeVersionRegex[] =
"Chrome/[0-9]+\\.([0-9]+\\.[0-9]+\\.[0-9]+)";
// The minor version in the reduced UA string is always "0.0.0".
static constexpr char kReducedMinorVersion[] = "0.0.0";
std::string minor_version;
EXPECT_TRUE(re2::RE2::PartialMatch(user_agent_value, kChromeVersionRegex,
&minor_version));
if (expected_user_agent_reduced) {
EXPECT_EQ(minor_version, kReducedMinorVersion);
} else {
EXPECT_NE(minor_version, kReducedMinorVersion);
}
}
private:
std::set<GURL> expected_request_urls_;
};
constexpr char ChromeWorkerUserAgentBrowserTest::kOriginUrl[];
IN_PROC_BROWSER_TEST_P(ChromeWorkerUserAgentBrowserTest, SharedWorker) {
const GURL main_page_url = GURL(base::StrCat(
{kOriginUrl,
"/create_shared_worker.html?worker_url=onconnect_user_agent.js"}));
const GURL worker_url =
GURL(base::StrCat({kOriginUrl, "/onconnect_user_agent.js"}));
SetExpectedRequestURLs({main_page_url, worker_url});
std::unique_ptr<content::URLLoaderInterceptor> interceptor =
CreateUrlLoaderInterceptor();
// Navigate to the page that has the scripts for registering the worker.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_page_url));
// Check the result of navigator.userAgent called from the worker.
CheckUserAgentString(
EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
"waitForMessage()")
.ExtractString(),
/*expected_user_agent_reduced=*/GetParam() ==
UserAgentOriginTrialTestType::UAReduction);
}
IN_PROC_BROWSER_TEST_P(ChromeWorkerUserAgentBrowserTest,
DedicatedWorkerCreatedFromFrame) {
const GURL main_page_url = GURL(base::StrCat(
{kOriginUrl, "/create_dedicated_worker.html?worker_url=user_agent.js"}));
const GURL worker_url = GURL(base::StrCat({kOriginUrl, "/user_agent.js"}));
SetExpectedRequestURLs({main_page_url, worker_url});
std::unique_ptr<content::URLLoaderInterceptor> interceptor =
CreateUrlLoaderInterceptor();
// Navigate to the page that has the scripts for registering the worker.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_page_url));
// Check the result of navigator.userAgent called from the worker.
CheckUserAgentString(
EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
"waitForMessage()")
.ExtractString(),
/*expected_user_agent_reduced=*/GetParam() ==
UserAgentOriginTrialTestType::UAReduction);
}
IN_PROC_BROWSER_TEST_P(ChromeWorkerUserAgentBrowserTest,
DedicatedWorkerCreatedFromDedicatedWorker) {
const GURL main_page_url =
GURL(base::StrCat({kOriginUrl,
"/create_dedicated_worker.html?worker_url=parent_"
"worker_user_agent.js"}));
const GURL worker_url =
GURL(base::StrCat({kOriginUrl, "/parent_worker_user_agent.js"}));
const GURL user_agent_url =
GURL(base::StrCat({kOriginUrl, "/user_agent.js"}));
SetExpectedRequestURLs({main_page_url, worker_url, user_agent_url});
std::unique_ptr<content::URLLoaderInterceptor> interceptor =
CreateUrlLoaderInterceptor();
// Navigate to the page that has the scripts for registering the worker.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_page_url));
// Check the result of navigator.userAgent called from the worker.
CheckUserAgentString(
EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
"waitForMessage()")
.ExtractString(),
/*expected_user_agent_reduced=*/GetParam() ==
UserAgentOriginTrialTestType::UAReduction);
}
INSTANTIATE_TEST_SUITE_P(
All,
ChromeWorkerUserAgentBrowserTest,
testing::Values(UserAgentOriginTrialTestType::UAReduction,
UserAgentOriginTrialTestType::UADeprecation,
UserAgentOriginTrialTestType::UAReductionAndDeprecation));