blob: a55b8f380c11c313031e6566e897b6bfa8c22b37 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <utility>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.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_notification_types.h"
#include "chrome/browser/devtools/url_constants.h"
#include "chrome/browser/extensions/active_tab_permission_granter.h"
#include "chrome/browser/extensions/extension_action_runner.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_with_management_policy_apitest.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/net/profile_network_context_service.h"
#include "chrome/browser/net/profile_network_context_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/one_google_bar/one_google_bar_fetcher.h"
#include "chrome/browser/search/one_google_bar/one_google_bar_service.h"
#include "chrome/browser/search/one_google_bar/one_google_bar_service_factory.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/login/login_handler.h"
#include "chrome/browser/ui/search/local_ntp_test_utils.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/search_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/login/scoped_test_public_session_login_state.h"
#include "components/google/core/browser/google_switches.h"
#include "components/prefs/pref_service.h"
#include "components/proxy_config/proxy_config_dictionary.h"
#include "components/proxy_config/proxy_config_pref_names.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/page_type.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/simple_url_loader_test_helper.h"
#include "content/public/test/url_loader_interceptor.h"
#include "extensions/browser/api/web_request/web_request_api.h"
#include "extensions/browser/blocked_action_type.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/features/feature.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h"
#include "extensions/test/test_extension_dir.h"
#include "google_apis/gaia/gaia_switches.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/test_data_directory.h"
#include "net/test/url_request/url_request_mock_http_job.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_filter.h"
#include "net/url_request/url_request_interceptor.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "third_party/blink/public/platform/web_input_event.h"
#if defined(OS_CHROMEOS)
#include "chromeos/login/login_state.h"
#endif // defined(OS_CHROMEOS)
using content::WebContents;
namespace extensions {
namespace {
class CancelLoginDialog : public content::NotificationObserver {
public:
CancelLoginDialog() {
registrar_.Add(this,
chrome::NOTIFICATION_AUTH_NEEDED,
content::NotificationService::AllSources());
}
~CancelLoginDialog() override {}
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {
LoginHandler* handler =
content::Details<LoginNotificationDetails>(details).ptr()->handler();
handler->CancelAuth();
}
private:
content::NotificationRegistrar registrar_;
DISALLOW_COPY_AND_ASSIGN(CancelLoginDialog);
};
// Sends an XHR request to the provided host, port, and path, and responds when
// the request was sent.
const char kPerformXhrJs[] =
"var url = 'http://%s:%d/%s';\n"
"var xhr = new XMLHttpRequest();\n"
"xhr.open('GET', url);\n"
"xhr.onload = function() {\n"
" window.domAutomationController.send(true);\n"
"};\n"
"xhr.onerror = function() {\n"
" window.domAutomationController.send(false);\n"
"};\n"
"xhr.send();\n";
// Header values set by the server and by the extension.
const char kHeaderValueFromExtension[] = "ValueFromExtension";
const char kHeaderValueFromServer[] = "ValueFromServer";
// Performs an XHR in the given |frame|, replying when complete.
void PerformXhrInFrame(content::RenderFrameHost* frame,
const std::string& host,
int port,
const std::string& page) {
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
frame,
base::StringPrintf(kPerformXhrJs, host.c_str(), port, page.c_str()),
&success));
EXPECT_TRUE(success);
}
// Returns the current count of a variable stored in the |extension| background
// page. Returns -1 if something goes awry.
int GetCountFromBackgroundPage(const Extension* extension,
content::BrowserContext* context,
const std::string& variable_name) {
ExtensionHost* host =
ProcessManager::Get(context)->GetBackgroundHostForExtension(
extension->id());
if (!host || !host->host_contents())
return -1;
int count = -1;
if (!ExecuteScriptAndExtractInt(
host->host_contents(),
"window.domAutomationController.send(" + variable_name + ")", &count))
return -1;
return count;
}
// Returns the current count of webRequests received by the |extension| in
// the background page (assumes the extension stores a value on the window
// object). Returns -1 if something goes awry.
int GetWebRequestCountFromBackgroundPage(const Extension* extension,
content::BrowserContext* context) {
return GetCountFromBackgroundPage(extension, context,
"window.webRequestCount");
}
// A test delegate to wait allow waiting for responses to complete with an
// expected status and given content.
// TODO(devlin): Other similar classes exist elsewhere. Pull this into a common
// test class.
class TestURLFetcherDelegate : public net::URLFetcherDelegate {
public:
// Creating the TestURLFetcherDelegate automatically creates and starts a
// URLFetcher.
TestURLFetcherDelegate(
scoped_refptr<net::URLRequestContextGetter> context_getter,
const GURL& url,
net::URLRequestStatus expected_request_status)
: expected_request_status_(expected_request_status),
fetcher_(net::URLFetcher::Create(url,
net::URLFetcher::GET,
this,
TRAFFIC_ANNOTATION_FOR_TESTS)) {
fetcher_->SetRequestContext(context_getter.get());
fetcher_->Start();
}
~TestURLFetcherDelegate() override {}
void SetExpectedResponse(const std::string& expected_response) {
expected_response_ = expected_response;
}
void WaitForCompletion() { run_loop_.Run(); }
// net::URLFetcherDelegate:
void OnURLFetchComplete(const net::URLFetcher* source) override {
EXPECT_EQ(expected_request_status_.status(), source->GetStatus().status());
EXPECT_EQ(expected_request_status_.error(), source->GetStatus().error());
if (expected_response_) {
std::string response;
ASSERT_TRUE(fetcher_->GetResponseAsString(&response));
EXPECT_EQ(*expected_response_, response);
}
run_loop_.Quit();
}
private:
const net::URLRequestStatus expected_request_status_;
base::Optional<std::string> expected_response_;
std::unique_ptr<net::URLFetcher> fetcher_;
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(TestURLFetcherDelegate);
};
// The DevTool's remote front-end is hardcoded to a URL with a fixed port.
// Redirect all responses to a URL with port.
class DevToolsFrontendInterceptor : public net::URLRequestInterceptor {
public:
DevToolsFrontendInterceptor(int port, const base::FilePath& root_dir)
: port_(port), test_root_dir_(root_dir) {}
net::URLRequestJob* MaybeInterceptRequest(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const override {
// The DevTools front-end has a hard-coded scheme (and implicit port 443).
// We simulate a response for it.
// net::URLRequestRedirectJob cannot be used because DevToolsUIBindings
// rejects URLs whose base URL is not the hard-coded URL.
if (request->url().EffectiveIntPort() != port_) {
return new net::URLRequestMockHTTPJob(
request, network_delegate,
test_root_dir_.AppendASCII(request->url().path().substr(1)));
}
return nullptr;
}
private:
int port_;
base::FilePath test_root_dir_;
};
void SetUpDevToolsFrontendInterceptorOnIO(int port,
const base::FilePath& root_dir) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
net::URLRequestFilter::GetInstance()->AddHostnameInterceptor(
"https", kRemoteFrontendDomain,
std::make_unique<DevToolsFrontendInterceptor>(port, root_dir));
}
void TearDownDevToolsFrontendInterceptorOnIO() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
net::URLRequestFilter::GetInstance()->ClearHandlers();
}
} // namespace
class ExtensionWebRequestApiTest : public ExtensionApiTest {
public:
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
}
void SetUpCommandLine(base::CommandLine* command_line) override {
ExtensionApiTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(switches::kGaiaUrl, "http://gaia.com");
}
void RunPermissionTest(
const char* extension_directory,
bool load_extension_with_incognito_permission,
bool wait_for_extension_loaded_in_incognito,
const char* expected_content_regular_window,
const char* exptected_content_incognito_window);
};
class DevToolsFrontendInWebRequestApiTest : public ExtensionApiTest {
public:
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
int port = embedded_test_server()->port();
if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
url_loader_interceptor_ = std::make_unique<content::URLLoaderInterceptor>(
base::BindRepeating(&DevToolsFrontendInWebRequestApiTest::OnIntercept,
base::Unretained(this), port));
} else {
base::RunLoop run_loop;
content::BrowserThread::PostTaskAndReply(
content::BrowserThread::IO, FROM_HERE,
base::BindOnce(&SetUpDevToolsFrontendInterceptorOnIO, port,
test_root_dir_),
run_loop.QuitClosure());
run_loop.Run();
}
}
void TearDownOnMainThread() override {
if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
url_loader_interceptor_.reset();
} else {
base::RunLoop run_loop;
content::BrowserThread::PostTaskAndReply(
content::BrowserThread::IO, FROM_HERE,
base::BindOnce(&TearDownDevToolsFrontendInterceptorOnIO),
run_loop.QuitClosure());
run_loop.Run();
}
ExtensionApiTest::TearDownOnMainThread();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
ExtensionApiTest::SetUpCommandLine(command_line);
test_root_dir_ = test_data_dir_.AppendASCII("webrequest");
embedded_test_server()->ServeFilesFromDirectory(test_root_dir_);
ASSERT_TRUE(StartEmbeddedTestServer());
command_line->AppendSwitchASCII(
switches::kCustomDevtoolsFrontend,
embedded_test_server()
->GetURL("customfrontend.example.com", "/devtoolsfrontend/")
.spec());
}
private:
bool OnIntercept(int test_server_port,
content::URLLoaderInterceptor::RequestParams* params) {
// See comments in DevToolsFrontendInterceptor above. The devtools remote
// frontend URLs are hardcoded into Chrome and are requested by some of the
// tests here to exercise their behavior with respect to WebRequest.
//
// We treat any URL request not targeting the test server as targeting the
// remote frontend, and we intercept them to fulfill from test data rather
// than hitting the network.
if (params->url_request.url.EffectiveIntPort() == test_server_port)
return false;
std::string status_line;
std::string contents;
GetFileContents(
test_root_dir_.AppendASCII(params->url_request.url.path().substr(1)),
&status_line, &contents);
content::URLLoaderInterceptor::WriteResponse(status_line, contents,
params->client.get());
return true;
}
static void GetFileContents(const base::FilePath& path,
std::string* status_line,
std::string* contents) {
base::ScopedAllowBlockingForTesting allow_io;
if (!base::ReadFileToString(path, contents)) {
*status_line = "HTTP/1.0 404 Not Found\n\n";
return;
}
std::string content_type;
if (path.Extension() == FILE_PATH_LITERAL(".html"))
content_type = "Content-type: text/html\n";
else if (path.Extension() == FILE_PATH_LITERAL(".js"))
content_type = "Content-type: application/javascript\n";
*status_line =
base::StringPrintf("HTTP/1.0 200 OK\n%s\n", content_type.c_str());
}
base::FilePath test_root_dir_;
std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
};
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestApi) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_api.html")) << message_;
}
// Fails often on Windows dbg bots. http://crbug.com/177163
#if defined(OS_WIN)
#define MAYBE_WebRequestSimple DISABLED_WebRequestSimple
#else
#define MAYBE_WebRequestSimple WebRequestSimple
#endif // defined(OS_WIN)
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, MAYBE_WebRequestSimple) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_simple.html")) <<
message_;
}
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestComplex) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_complex.html")) <<
message_;
}
// This test times out regularly on MSAN trybots. See https://crbug.com/733395.
#if defined(MEMORY_SANITIZER)
#define MAYBE_WebRequestTypes DISABLED_WebRequestTypes
#else
#define MAYBE_WebRequestTypes WebRequestTypes
#endif
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, MAYBE_WebRequestTypes) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_types.html")) << message_;
}
#if defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestPublicSession) {
ASSERT_TRUE(StartEmbeddedTestServer());
chromeos::ScopedTestPublicSessionLoginState login_state;
// Disable a CHECK while doing api tests.
WebRequestPermissions::AllowAllExtensionLocationsInPublicSessionForTesting(
true);
ASSERT_TRUE(RunExtensionSubtest("webrequest_public_session", "test.html")) <<
message_;
WebRequestPermissions::AllowAllExtensionLocationsInPublicSessionForTesting(
false);
}
#endif // defined(OS_CHROMEOS)
// Test that a request to an OpenSearch description document (OSDD) generates
// an event with the expected details.
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestTestOSDD) {
// An OSDD request is only generated when a main frame at is loaded at /, so
// serve osdd/index.html from the root of the test server:
embedded_test_server()->ServeFilesFromDirectory(
test_data_dir_.AppendASCII("webrequest/osdd"));
ASSERT_TRUE(StartEmbeddedTestServer());
search_test_utils::WaitForTemplateURLServiceToLoad(
TemplateURLServiceFactory::GetForProfile(profile()));
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_osdd.html")) << message_;
}
// Test that the webRequest events are dispatched with the expected details when
// a frame or tab is removed while a response is being received.
// Flaky: https://crbug.com/617865
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
DISABLED_WebRequestUnloadAfterRequest) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_unload.html?1")) <<
message_;
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_unload.html?2")) <<
message_;
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_unload.html?3")) <<
message_;
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_unload.html?4")) <<
message_;
}
// Test that the webRequest events are dispatched with the expected details when
// a frame or tab is immediately removed after starting a request.
// Flaky on Linux/Mac. See crbug.com/780369 for detail.
#if defined(OS_MACOSX) || defined(OS_LINUX)
#define MAYBE_WebRequestUnloadImmediately DISABLED_WebRequestUnloadImmediately
#else
#define MAYBE_WebRequestUnloadImmediately WebRequestUnloadImmediately
#endif
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
MAYBE_WebRequestUnloadImmediately) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_unload.html?5")) <<
message_;
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_unload.html?6")) <<
message_;
}
// Flaky (sometimes crash): http://crbug.com/140976
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
DISABLED_WebRequestAuthRequired) {
CancelLoginDialog login_dialog_helper;
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_auth_required.html")) <<
message_;
}
// This test times out regularly on win_rel trybots. See http://crbug.com/122178
// Also on Linux/ChromiumOS debug, ASAN and MSAN builds.
// https://crbug.com/670415
#if defined(OS_WIN) || !defined(NDEBUG) || defined(ADDRESS_SANITIZER) || \
defined(MEMORY_SANITIZER)
#define MAYBE_WebRequestBlocking DISABLED_WebRequestBlocking
#else
#define MAYBE_WebRequestBlocking WebRequestBlocking
#endif
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, MAYBE_WebRequestBlocking) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_blocking.html")) <<
message_;
}
// Fails often on Windows dbg bots. http://crbug.com/177163
#if defined(OS_WIN)
#define MAYBE_WebRequestNewTab DISABLED_WebRequestNewTab
#else
#define MAYBE_WebRequestNewTab WebRequestNewTab
#endif // defined(OS_WIN)
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, MAYBE_WebRequestNewTab) {
ASSERT_TRUE(StartEmbeddedTestServer());
// Wait for the extension to set itself up and return control to us.
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_newTab.html"))
<< message_;
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::WaitForLoadStop(tab);
ResultCatcher catcher;
ExtensionService* service =
ExtensionSystem::Get(browser()->profile())->extension_service();
const Extension* extension =
service->GetExtensionById(last_loaded_extension_id(), false);
GURL url = extension->GetResourceURL("newTab/a.html");
ui_test_utils::NavigateToURL(browser(), url);
// There's a link on a.html with target=_blank. Click on it to open it in a
// new tab.
blink::WebMouseEvent mouse_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
mouse_event.button = blink::WebMouseEvent::Button::kLeft;
mouse_event.SetPositionInWidget(7, 7);
mouse_event.click_count = 1;
tab->GetRenderViewHost()->GetWidget()->ForwardMouseEvent(mouse_event);
mouse_event.SetType(blink::WebInputEvent::kMouseUp);
tab->GetRenderViewHost()->GetWidget()->ForwardMouseEvent(mouse_event);
ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
}
// This test times out regularly on MSAN trybots. See http://crbug.com/733395.
#if defined(MEMORY_SANITIZER)
#define MAYBE_WebRequestDeclarative1 DISABLED_WebRequestDeclarative1
#else
#define MAYBE_WebRequestDeclarative1 WebRequestDeclarative1
#endif
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
MAYBE_WebRequestDeclarative1) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_declarative1.html"))
<< message_;
}
// This test times out regularly on MSAN trybots. See https://crbug.com/733395.
#if defined(MEMORY_SANITIZER)
#define MAYBE_WebRequestDeclarative2 DISABLED_WebRequestDeclarative2
#else
#define MAYBE_WebRequestDeclarative2 WebRequestDeclarative2
#endif
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
MAYBE_WebRequestDeclarative2) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_declarative2.html"))
<< message_;
}
void ExtensionWebRequestApiTest::RunPermissionTest(
const char* extension_directory,
bool load_extension_with_incognito_permission,
bool wait_for_extension_loaded_in_incognito,
const char* expected_content_regular_window,
const char* exptected_content_incognito_window) {
ResultCatcher catcher;
catcher.RestrictToBrowserContext(browser()->profile());
ResultCatcher catcher_incognito;
catcher_incognito.RestrictToBrowserContext(
browser()->profile()->GetOffTheRecordProfile());
ExtensionTestMessageListener listener("done", false);
ExtensionTestMessageListener listener_incognito("done_incognito", false);
int load_extension_flags = kFlagNone;
if (load_extension_with_incognito_permission)
load_extension_flags |= kFlagEnableIncognito;
ASSERT_TRUE(LoadExtensionWithFlags(
test_data_dir_.AppendASCII("webrequest_permissions")
.AppendASCII(extension_directory),
load_extension_flags));
// Test that navigation in regular window is properly redirected.
EXPECT_TRUE(listener.WaitUntilSatisfied());
// This navigation should be redirected.
ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/extensions/test_file.html"));
std::string body;
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
tab,
"window.domAutomationController.send(document.body.textContent)",
&body));
EXPECT_EQ(expected_content_regular_window, body);
// Test that navigation in OTR window is properly redirected.
Browser* otr_browser =
OpenURLOffTheRecord(browser()->profile(), GURL("about:blank"));
if (wait_for_extension_loaded_in_incognito)
EXPECT_TRUE(listener_incognito.WaitUntilSatisfied());
// This navigation should be redirected if
// load_extension_with_incognito_permission is true.
ui_test_utils::NavigateToURL(
otr_browser,
embedded_test_server()->GetURL("/extensions/test_file.html"));
body.clear();
WebContents* otr_tab = otr_browser->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
otr_tab,
"window.domAutomationController.send(document.body.textContent)",
&body));
EXPECT_EQ(exptected_content_incognito_window, body);
}
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
WebRequestDeclarativePermissionSpanning1) {
// Test spanning with incognito permission.
ASSERT_TRUE(StartEmbeddedTestServer());
RunPermissionTest("spanning", true, false, "redirected1", "redirected1");
}
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
WebRequestDeclarativePermissionSpanning2) {
// Test spanning without incognito permission.
ASSERT_TRUE(StartEmbeddedTestServer());
RunPermissionTest("spanning", false, false, "redirected1", "");
}
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
WebRequestDeclarativePermissionSplit1) {
// Test split with incognito permission.
ASSERT_TRUE(StartEmbeddedTestServer());
RunPermissionTest("split", true, true, "redirected1", "redirected2");
}
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
WebRequestDeclarativePermissionSplit2) {
// Test split without incognito permission.
ASSERT_TRUE(StartEmbeddedTestServer());
RunPermissionTest("split", false, false, "redirected1", "");
}
// TODO(vabr): Cure these flaky tests, http://crbug.com/238179.
#if !defined(NDEBUG)
#define MAYBE_PostData1 DISABLED_PostData1
#define MAYBE_PostData2 DISABLED_PostData2
#else
#define MAYBE_PostData1 PostData1
#define MAYBE_PostData2 PostData2
#endif
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, MAYBE_PostData1) {
// Test HTML form POST data access with the default and "url" encoding.
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_post1.html")) <<
message_;
}
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, MAYBE_PostData2) {
// Test HTML form POST data access with the multipart and plaintext encoding.
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_post2.html")) <<
message_;
}
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
DeclarativeSendMessage) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionTest("webrequest_sendmessage")) << message_;
}
// Check that reloading an extension that runs in incognito split mode and
// has two active background pages with registered events does not crash the
// browser. Regression test for http://crbug.com/224094
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, IncognitoSplitModeReload) {
ASSERT_TRUE(StartEmbeddedTestServer());
// Wait for rules to be set up.
ExtensionTestMessageListener listener("done", false);
ExtensionTestMessageListener listener_incognito("done_incognito", false);
const Extension* extension = LoadExtensionWithFlags(
test_data_dir_.AppendASCII("webrequest_reload"), kFlagEnableIncognito);
ASSERT_TRUE(extension);
OpenURLOffTheRecord(browser()->profile(), GURL("about:blank"));
EXPECT_TRUE(listener.WaitUntilSatisfied());
EXPECT_TRUE(listener_incognito.WaitUntilSatisfied());
// Reload extension and wait for rules to be set up again. This should not
// crash the browser.
ExtensionTestMessageListener listener2("done", false);
ExtensionTestMessageListener listener_incognito2("done_incognito", false);
ReloadExtension(extension->id());
EXPECT_TRUE(listener2.WaitUntilSatisfied());
EXPECT_TRUE(listener_incognito2.WaitUntilSatisfied());
}
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, ExtensionRequests) {
ASSERT_TRUE(StartEmbeddedTestServer());
ExtensionTestMessageListener listener_main1("web_request_status1", true);
ExtensionTestMessageListener listener_main2("web_request_status2", true);
ExtensionTestMessageListener listener_app("app_done", false);
ExtensionTestMessageListener listener_extension("extension_done", false);
// Set up webRequest listener
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("webrequest_extensions/main")));
EXPECT_TRUE(listener_main1.WaitUntilSatisfied());
EXPECT_TRUE(listener_main2.WaitUntilSatisfied());
// Perform some network activity in an app and another extension.
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("webrequest_extensions/app")));
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("webrequest_extensions/extension")));
EXPECT_TRUE(listener_app.WaitUntilSatisfied());
EXPECT_TRUE(listener_extension.WaitUntilSatisfied());
// Load a page, a content script will ping us when it is ready.
ExtensionTestMessageListener listener_pageready("contentscript_ready", true);
ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL(
"/extensions/test_file.html?match_webrequest_test"));
EXPECT_TRUE(listener_pageready.WaitUntilSatisfied());
// The extension and app-generated requests should not have triggered any
// webRequest event filtered by type 'xmlhttprequest'.
// (check this here instead of before the navigation, in case the webRequest
// event routing is slow for some reason).
ExtensionTestMessageListener listener_result(false);
listener_main1.Reply("");
EXPECT_TRUE(listener_result.WaitUntilSatisfied());
EXPECT_EQ("Did not intercept any requests.", listener_result.message());
ExtensionTestMessageListener listener_contentscript("contentscript_done",
false);
ExtensionTestMessageListener listener_framescript("framescript_done", false);
// Proceed with the final tests: Let the content script fire a request and
// then load an iframe which also fires a XHR request.
listener_pageready.Reply("");
EXPECT_TRUE(listener_contentscript.WaitUntilSatisfied());
EXPECT_TRUE(listener_framescript.WaitUntilSatisfied());
// Collect the visited URLs. The content script and subframe does not run in
// the extension's process, so the requests should be visible to the main
// extension.
listener_result.Reset();
listener_main2.Reply("");
EXPECT_TRUE(listener_result.WaitUntilSatisfied());
// The extension frame does run in the extension's process.
EXPECT_EQ("Intercepted requests: ?contentscript", listener_result.message());
}
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, HostedAppRequest) {
ASSERT_TRUE(StartEmbeddedTestServer());
GURL hosted_app_url(
embedded_test_server()->GetURL(
"/extensions/api_test/webrequest_hosted_app/index.html"));
scoped_refptr<Extension> hosted_app =
ExtensionBuilder()
.SetManifest(
DictionaryBuilder()
.Set("name", "Some hosted app")
.Set("version", "1")
.Set("manifest_version", 2)
.Set("app", DictionaryBuilder()
.Set("launch", DictionaryBuilder()
.Set("web_url",
hosted_app_url.spec())
.Build())
.Build())
.Build())
.Build();
ExtensionSystem::Get(browser()->profile())
->extension_service()
->AddExtension(hosted_app.get());
ExtensionTestMessageListener listener1("main_frame", false);
ExtensionTestMessageListener listener2("xmlhttprequest", false);
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("webrequest_hosted_app")));
ui_test_utils::NavigateToURL(browser(), hosted_app_url);
EXPECT_TRUE(listener1.WaitUntilSatisfied());
EXPECT_TRUE(listener2.WaitUntilSatisfied());
}
// Tests that webRequest works with features::kRuntimeHostPermissions.
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
WebRequestWithWithheldPermissions) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(features::kRuntimeHostPermissions);
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
// Load an extension that registers a listener for webRequest events, and
// wait until it's initialized.
ExtensionTestMessageListener listener("ready", false);
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("webrequest_activetab"));
ASSERT_TRUE(extension) << message_;
EXPECT_TRUE(listener.WaitUntilSatisfied());
// Navigate the browser to a page in a new tab.
const std::string kHost = "a.com";
GURL url = embedded_test_server()->GetURL(kHost, "/iframe_cross_site.html");
NavigateParams params(browser(), url, ui::PAGE_TRANSITION_LINK);
params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
ui_test_utils::NavigateToURL(&params);
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
ExtensionActionRunner* runner =
ExtensionActionRunner::GetForWebContents(web_contents);
ASSERT_TRUE(runner);
int port = embedded_test_server()->port();
const std::string kXhrPath = "simple.html";
// The extension shouldn't have currently received any webRequest events,
// since it doesn't have permission (and shouldn't receive any from an XHR).
EXPECT_EQ(0, GetWebRequestCountFromBackgroundPage(extension, profile()));
content::RenderFrameHost* main_frame = nullptr;
content::RenderFrameHost* child_frame = nullptr;
auto get_main_and_child_frame = [](content::WebContents* web_contents,
content::RenderFrameHost** main_frame,
content::RenderFrameHost** child_frame) {
*child_frame = nullptr;
*main_frame = web_contents->GetMainFrame();
std::vector<content::RenderFrameHost*> all_frames =
web_contents->GetAllFrames();
ASSERT_EQ(3u, all_frames.size());
*child_frame = all_frames[0] == *main_frame ? all_frames[1] : all_frames[0];
ASSERT_TRUE(*child_frame);
};
get_main_and_child_frame(web_contents, &main_frame, &child_frame);
const std::string kMainHost = main_frame->GetLastCommittedURL().host();
const std::string kChildHost = child_frame->GetLastCommittedURL().host();
PerformXhrInFrame(main_frame, kHost, port, kXhrPath);
PerformXhrInFrame(child_frame, kChildHost, port, kXhrPath);
EXPECT_EQ(0, GetWebRequestCountFromBackgroundPage(extension, profile()));
EXPECT_EQ(BLOCKED_ACTION_WEB_REQUEST, runner->GetBlockedActions(extension));
// Grant activeTab permission, and perform another XHR. The extension should
// receive the event.
runner->set_default_bubble_close_action_for_testing(
base::WrapUnique(new ToolbarActionsBarBubbleDelegate::CloseAction(
ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE)));
runner->RunAction(extension, true);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
// The runner will have refreshed the page...
get_main_and_child_frame(web_contents, &main_frame, &child_frame);
EXPECT_EQ(BLOCKED_ACTION_NONE, runner->GetBlockedActions(extension));
int xhr_count = GetWebRequestCountFromBackgroundPage(extension, profile());
// ... which means that we should have a non-zero xhr count...
EXPECT_GT(xhr_count, 0);
// ... and the extension should receive future events.
PerformXhrInFrame(main_frame, kHost, port, kXhrPath);
++xhr_count;
EXPECT_EQ(xhr_count,
GetWebRequestCountFromBackgroundPage(extension, profile()));
// However, activeTab only grants access to the main frame, not to child
// frames. As such, trying to XHR in the child frame should still fail.
PerformXhrInFrame(child_frame, kChildHost, port, kXhrPath);
EXPECT_EQ(xhr_count,
GetWebRequestCountFromBackgroundPage(extension, profile()));
// But since there's no way for the user to currently grant access to child
// frames, this shouldn't show up as a blocked action.
EXPECT_EQ(BLOCKED_ACTION_NONE, runner->GetBlockedActions(extension));
// If we revoke the extension's tab permissions, it should no longer receive
// webRequest events.
ActiveTabPermissionGranter* granter =
TabHelper::FromWebContents(web_contents)->active_tab_permission_granter();
ASSERT_TRUE(granter);
granter->RevokeForTesting();
base::RunLoop().RunUntilIdle();
PerformXhrInFrame(main_frame, kHost, port, kXhrPath);
EXPECT_EQ(xhr_count,
GetWebRequestCountFromBackgroundPage(extension, profile()));
EXPECT_EQ(BLOCKED_ACTION_WEB_REQUEST, runner->GetBlockedActions(extension));
}
// Verify that requests to clientsX.google.com are protected properly.
// First test requests from a standard renderer and then a request from the
// browser process.
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
WebRequestClientsGoogleComProtection) {
ASSERT_TRUE(embedded_test_server()->Start());
// Load an extension that registers a listener for webRequest events, and
// wait until it's initialized.
ExtensionTestMessageListener listener("ready", false);
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("webrequest_clients_google_com"));
ASSERT_TRUE(extension) << message_;
EXPECT_TRUE(listener.WaitUntilSatisfied());
EXPECT_EQ(0, GetWebRequestCountFromBackgroundPage(extension, profile()));
GURL main_frame_url =
embedded_test_server()->GetURL("www.example.com", "/simple.html");
NavigateParams params(browser(), main_frame_url, ui::PAGE_TRANSITION_TYPED);
ui_test_utils::NavigateToURL(&params);
EXPECT_EQ(0, GetWebRequestCountFromBackgroundPage(extension, profile()));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
// Attempt to issue a request to clients1.google.com from the renderer. This
// will fail, but should still be visible to the WebRequest API.
const char kRequest[] = R"(
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://clients1.google.com');
xhr.onload = () => {window.domAutomationController.send(true);};
xhr.onerror = () => {window.domAutomationController.send(false);};
xhr.send();)";
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(web_contents->GetMainFrame(),
kRequest, &success));
// Requests always fail due to cross origin nature.
EXPECT_FALSE(success);
EXPECT_EQ(1, GetWebRequestCountFromBackgroundPage(extension, profile()));
// Now perform a request to client1.google.com from the browser process. This
// should *not* be visible to the WebRequest API.
auto request = std::make_unique<network::ResourceRequest>();
request->url =
embedded_test_server()->GetURL("clients1.google.com", "/simple.html");
auto* url_loader_factory =
content::BrowserContext::GetDefaultStoragePartition(profile())
->GetURLLoaderFactoryForBrowserProcess()
.get();
content::SimpleURLLoaderTestHelper loader_helper;
auto loader = network::SimpleURLLoader::Create(std::move(request),
TRAFFIC_ANNOTATION_FOR_TESTS);
loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory, loader_helper.GetCallback());
// Wait for the response to complete.
loader_helper.WaitForCallback();
EXPECT_TRUE(loader_helper.response_body());
EXPECT_EQ(200, loader->ResponseInfo()->headers->response_code());
// We should still have only seen the single render-initiated request from the
// first half of the test.
EXPECT_EQ(1, GetWebRequestCountFromBackgroundPage(extension, profile()));
}
// Verify that requests for PAC scripts are protected properly.
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
WebRequestPacRequestProtection) {
ASSERT_TRUE(embedded_test_server()->Start());
// Load an extension that registers a listener for webRequest events, and
// wait until it's initialized.
ExtensionTestMessageListener listener("ready", false);
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("webrequest_pac_request"));
ASSERT_TRUE(extension) << message_;
EXPECT_TRUE(listener.WaitUntilSatisfied());
// Configure a PAC script. Need to do this after the extension is loaded, so
// that the PAC isn't already loaded by the time the extension starts
// affecting requests.
PrefService* pref_service = browser()->profile()->GetPrefs();
pref_service->Set(proxy_config::prefs::kProxy,
*ProxyConfigDictionary::CreatePacScript(
embedded_test_server()->GetURL("/self.pac").spec(),
true /* pac_mandatory */));
// Flush the proxy configuration change over the Mojo pipe to avoid any races.
ProfileNetworkContextServiceFactory::GetForContext(browser()->profile())
->FlushProxyConfigMonitorForTesting();
// Navigate to a page. The URL doesn't matter.
ui_test_utils::NavigateToURL(browser(),
embedded_test_server()->GetURL("/title2.html"));
// The extension should not have seen the PAC request.
EXPECT_EQ(0, GetCountFromBackgroundPage(extension, profile(),
"window.pacRequestCount"));
// The extension should have seen the request for the main frame.
EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(),
"window.title2RequestCount"));
// The PAC request should have succeeded, as should the subsequent URL
// request.
EXPECT_EQ(content::PAGE_TYPE_NORMAL, browser()
->tab_strip_model()
->GetActiveWebContents()
->GetController()
.GetLastCommittedEntry()
->GetPageType());
}
// Checks that the Dice response header is protected for Gaia URLs, but not
// other URLs.
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
WebRequestDiceHeaderProtection) {
// Load an extension that registers a listener for webRequest events, and
// wait until it is initialized.
ExtensionTestMessageListener listener("ready", false);
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("webrequest_dice_header"));
ASSERT_TRUE(extension) << message_;
EXPECT_TRUE(listener.WaitUntilSatisfied());
ASSERT_TRUE(embedded_test_server()->Start());
// Setup a web contents observer to inspect the response headers after the
// extension was run.
class TestWebContentsObserver : public content::WebContentsObserver {
public:
explicit TestWebContentsObserver(content::WebContents* contents)
: WebContentsObserver(contents) {}
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override {
// Check that the extension cannot add a Dice header.
const net::HttpResponseHeaders* headers =
navigation_handle->GetResponseHeaders();
EXPECT_TRUE(headers->GetNormalizedHeader(
"X-Chrome-ID-Consistency-Response", &dice_header_value_));
EXPECT_TRUE(
headers->GetNormalizedHeader("X-New-Header", &new_header_value_));
EXPECT_TRUE(
headers->GetNormalizedHeader("X-Control", &control_header_value_));
did_finish_navigation_called_ = true;
}
bool did_finish_navigation_called() const {
return did_finish_navigation_called_;
}
const std::string& dice_header_value() const { return dice_header_value_; }
const std::string& new_header_value() const { return new_header_value_; }
const std::string& control_header_value() const {
return control_header_value_;
}
void Clear() {
did_finish_navigation_called_ = false;
dice_header_value_.clear();
new_header_value_.clear();
control_header_value_.clear();
}
private:
bool did_finish_navigation_called_ = false;
std::string dice_header_value_;
std::string new_header_value_;
std::string control_header_value_;
};
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
TestWebContentsObserver test_webcontents_observer(web_contents);
// Navigate to the Gaia URL intercepted by the extension.
GURL url =
embedded_test_server()->GetURL("gaia.com", "/extensions/dice.html");
ui_test_utils::NavigateToURL(browser(), url);
// Check that the Dice header was not changed by the extension.
EXPECT_TRUE(test_webcontents_observer.did_finish_navigation_called());
EXPECT_EQ(kHeaderValueFromServer,
test_webcontents_observer.dice_header_value());
EXPECT_EQ(kHeaderValueFromExtension,
test_webcontents_observer.new_header_value());
EXPECT_EQ(kHeaderValueFromExtension,
test_webcontents_observer.control_header_value());
// Check that the Dice header cannot be read by the extension.
EXPECT_EQ(0, GetCountFromBackgroundPage(extension, profile(),
"window.diceResponseHeaderCount"));
EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(),
"window.controlResponseHeaderCount"));
// Navigate to a non-Gaia URL intercepted by the extension.
test_webcontents_observer.Clear();
url = embedded_test_server()->GetURL("example.com", "/extensions/dice.html");
ui_test_utils::NavigateToURL(browser(), url);
// Check that the Dice header was changed by the extension.
EXPECT_TRUE(test_webcontents_observer.did_finish_navigation_called());
EXPECT_EQ(kHeaderValueFromExtension,
test_webcontents_observer.dice_header_value());
EXPECT_EQ(kHeaderValueFromExtension,
test_webcontents_observer.new_header_value());
EXPECT_EQ(kHeaderValueFromExtension,
test_webcontents_observer.control_header_value());
// Check that the Dice header can be read by the extension.
EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(),
"window.diceResponseHeaderCount"));
EXPECT_EQ(2, GetCountFromBackgroundPage(extension, profile(),
"window.controlResponseHeaderCount"));
}
// Test that the webRequest events are dispatched for the WebSocket handshake
// requests.
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebSocketRequest) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(StartWebSocketServer(net::GetWebSocketTestDataDirectory()));
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_websocket.html"))
<< message_;
}
// Test that the webRequest events are dispatched for the WebSocket handshake
// requests when authenrication is requested by server.
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
WebSocketRequestAuthRequired) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(StartWebSocketServer(net::GetWebSocketTestDataDirectory(), true));
ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_websocket_auth.html"))
<< message_;
}
// Test behavior when intercepting requests from a browser-initiated url fetch.
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
WebRequestURLFetcherInterception) {
// Create an extension that intercepts (and blocks) requests to example.com.
TestExtensionDir test_dir;
test_dir.WriteManifest(
R"({
"name": "web_request_browser_interception",
"description": "tests that browser requests aren't intercepted",
"version": "0.1",
"permissions": ["webRequest", "webRequestBlocking", "*://*/*"],
"manifest_version": 2,
"background": { "scripts": ["background.js"] }
})");
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"),
R"(chrome.webRequest.onBeforeRequest.addListener(
function(details) {
return {cancel: details.url.indexOf('example.com') != -1};
},
{urls: ["<all_urls>"]},
["blocking"]);
chrome.test.sendMessage('ready');)");
ASSERT_TRUE(StartEmbeddedTestServer());
const Extension* extension = nullptr;
{
ExtensionTestMessageListener listener("ready", false);
extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
EXPECT_TRUE(listener.WaitUntilSatisfied());
}
GURL google_url =
embedded_test_server()->GetURL("google.com", "/extensions/body1.html");
// Taken from test/data/extensions/body1.html.
const char kGoogleBodyContent[] = "dog";
const char kGoogleFullContent[] = "<html>\n<body>dog</body>\n</html>\n";
// First, check normal requets (e.g., navigations) to verify the extension
// is working correctly.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(), google_url);
EXPECT_EQ(google_url, web_contents->GetLastCommittedURL());
{
// google.com should succeed.
std::string content;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
web_contents,
"domAutomationController.send(document.body.textContent.trim());",
&content));
EXPECT_EQ(kGoogleBodyContent, content);
}
GURL example_url =
embedded_test_server()->GetURL("example.com", "/extensions/body2.html");
// Taken from test/data/extensions/body2.html.
const char kExampleBodyContent[] = "cat";
const char kExampleFullContent[] = "<html>\n<body>cat</body>\n</html>\n";
ui_test_utils::NavigateToURL(browser(), example_url);
{
// example.com should fail.
content::NavigationEntry* nav_entry =
web_contents->GetController().GetLastCommittedEntry();
ASSERT_TRUE(nav_entry);
EXPECT_EQ(content::PAGE_TYPE_ERROR, nav_entry->GetPageType());
std::string content;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
web_contents,
"domAutomationController.send(document.body.textContent.trim());",
&content));
EXPECT_NE(kExampleBodyContent, content);
}
// Next, try a series of requests through URLRequestFetchers (rather than a
// renderer).
net::URLRequestContextGetter* profile_context =
browser()->profile()->GetRequestContext();
{
// google.com should be unaffected by the extension and should succeed.
SCOPED_TRACE("google.com with Profile's request context");
TestURLFetcherDelegate url_fetcher(profile_context, google_url,
net::URLRequestStatus());
url_fetcher.SetExpectedResponse(kGoogleFullContent);
url_fetcher.WaitForCompletion();
}
{
// example.com should fail.
SCOPED_TRACE("example.com with Profile's request context");
TestURLFetcherDelegate url_fetcher(
profile_context, example_url,
net::URLRequestStatus(net::URLRequestStatus::FAILED,
net::ERR_BLOCKED_BY_CLIENT));
url_fetcher.WaitForCompletion();
}
// Requests going through the system request context should always succeed.
net::URLRequestContextGetter* system_context =
g_browser_process->system_request_context();
{
// google.com should succeed (again).
SCOPED_TRACE("google.com with System's request context");
TestURLFetcherDelegate url_fetcher(system_context, google_url,
net::URLRequestStatus());
url_fetcher.SetExpectedResponse(kGoogleFullContent);
url_fetcher.WaitForCompletion();
}
{
// example.com should also succeed, since it's not through the profile's
// request context.
SCOPED_TRACE("example.com with System's request context");
TestURLFetcherDelegate url_fetcher(system_context, example_url,
net::URLRequestStatus());
url_fetcher.SetExpectedResponse(kExampleFullContent);
url_fetcher.WaitForCompletion();
}
}
// Test that initiator is only included as part of event details when the
// extension has a permission matching the initiator.
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, MinimumAccessInitiator) {
ASSERT_TRUE(StartEmbeddedTestServer());
ExtensionTestMessageListener listener("ready", false);
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("webrequest_permissions/initiator"));
ASSERT_TRUE(extension) << message_;
EXPECT_TRUE(listener.WaitUntilSatisfied());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
struct TestCase {
std::string navigate_before_start;
std::string xhr_domain;
std::string expected_initiator;
} testcases[] = {{"example.com", "example.com", "example.com"},
{"example2.com", "example3.com", "example2.com"},
{"no-permission.com", "example4.com", ""}};
int port = embedded_test_server()->port();
for (const auto& testcase : testcases) {
SCOPED_TRACE(testcase.navigate_before_start + ":" + testcase.xhr_domain +
":" + testcase.expected_initiator);
ExtensionTestMessageListener initiator_listener(false);
initiator_listener.set_extension_id(extension->id());
ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL(
testcase.navigate_before_start,
"/extensions/body1.html"));
PerformXhrInFrame(web_contents->GetMainFrame(), testcase.xhr_domain, port,
"extensions/api_test/webrequest/xhr/data.json");
EXPECT_TRUE(initiator_listener.WaitUntilSatisfied());
if (testcase.expected_initiator.empty()) {
ASSERT_EQ("NO_INITIATOR", initiator_listener.message());
} else {
ASSERT_EQ(
"http://" + testcase.expected_initiator + ":" + std::to_string(port),
initiator_listener.message());
}
}
}
// Test fixture which sets a custom NTP Page.
class NTPInterceptionWebRequestAPITest : public ExtensionApiTest {
public:
NTPInterceptionWebRequestAPITest()
: https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
// ExtensionApiTest override:
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
test_data_dir_ = test_data_dir_.AppendASCII("webrequest")
.AppendASCII("ntp_request_interception");
https_test_server_.ServeFilesFromDirectory(test_data_dir_);
ASSERT_TRUE(https_test_server_.Start());
GURL ntp_url = https_test_server_.GetURL("/fake_ntp.html");
local_ntp_test_utils::SetUserSelectedDefaultSearchProvider(
profile(), https_test_server_.base_url().spec(), ntp_url.spec());
}
const net::EmbeddedTestServer* https_test_server() const {
return &https_test_server_;
}
private:
net::EmbeddedTestServer https_test_server_;
DISALLOW_COPY_AND_ASSIGN(NTPInterceptionWebRequestAPITest);
};
// Ensures that requests made by the NTP Instant renderer are hidden from the
// Web Request API. Regression test for crbug.com/797461.
IN_PROC_BROWSER_TEST_F(NTPInterceptionWebRequestAPITest,
NTPRendererRequestsHidden) {
// Loads an extension which tries to intercept requests to
// "fake_ntp_script.js", which will be loaded as part of the NTP renderer.
ExtensionTestMessageListener listener("ready", true /*will_reply*/);
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("extension"));
ASSERT_TRUE(extension);
EXPECT_TRUE(listener.WaitUntilSatisfied());
// Have the extension listen for requests to |fake_ntp_script.js|.
listener.Reply(https_test_server()->GetURL("/fake_ntp_script.js").spec());
// Returns true if the given extension was able to intercept the request to
// "fake_ntp_script.js".
auto was_script_request_intercepted =
[this](const std::string& extension_id) {
const std::string result = ExecuteScriptInBackgroundPage(
extension_id, "getAndResetRequestIntercepted();");
EXPECT_TRUE(result == "true" || result == "false")
<< "Unexpected result " << result;
return result == "true";
};
// Returns true if the given |web_contents| has window.scriptExecuted set to
// true;
auto was_ntp_script_loaded = [](content::WebContents* web_contents) {
bool was_ntp_script_loaded = false;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
web_contents, "domAutomationController.send(!!window.scriptExecuted);",
&was_ntp_script_loaded));
return was_ntp_script_loaded;
};
WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Navigate to the NTP. The request for "fake_ntp_script.js" should not have
// reached the extension, since it was made by the instant NTP renderer, which
// is semi-privileged.
ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
EXPECT_TRUE(was_ntp_script_loaded(web_contents));
ASSERT_TRUE(search::IsInstantNTP(web_contents));
EXPECT_FALSE(was_script_request_intercepted(extension->id()));
// However, when a normal webpage requests the same script, the request should
// be seen by the extension.
ui_test_utils::NavigateToURL(
browser(), https_test_server()->GetURL("/page_with_ntp_script.html"));
EXPECT_TRUE(was_ntp_script_loaded(web_contents));
ASSERT_FALSE(search::IsInstantNTP(web_contents));
EXPECT_TRUE(was_script_request_intercepted(extension->id()));
}
// Test fixture testing that requests made for the OneGoogleBar on behalf of
// the local NTP can't be intercepted by extensions.
class LocalNTPInterceptionWebRequestAPITest
: public ExtensionApiTest,
public OneGoogleBarServiceObserver {
public:
LocalNTPInterceptionWebRequestAPITest()
: https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
// ExtensionApiTest override:
void SetUp() override {
https_test_server_.RegisterRequestMonitor(base::BindRepeating(
&LocalNTPInterceptionWebRequestAPITest::MonitorRequest,
base::Unretained(this)));
ASSERT_TRUE(https_test_server_.InitializeAndListen());
ExtensionApiTest::SetUp();
feature_list_.InitWithFeatures(
{::features::kUseGoogleLocalNtp, ::features::kOneGoogleBarOnLocalNtp},
{});
}
void SetUpCommandLine(base::CommandLine* command_line) override {
ExtensionApiTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(switches::kGoogleBaseURL,
https_test_server_.base_url().spec());
}
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
https_test_server_.StartAcceptingConnections();
one_google_bar_url_ = one_google_bar_service()
->fetcher_for_testing()
->GetFetchURLForTesting();
// Can't declare |runloop_| as a data member on the stack since it needs to
// be be constructed from a single-threaded context.
runloop_ = std::make_unique<base::RunLoop>();
one_google_bar_service()->AddObserver(this);
}
// OneGoogleBarServiceObserver overrides:
void OnOneGoogleBarDataUpdated() override { runloop_->Quit(); }
void OnOneGoogleBarServiceShuttingDown() override {
one_google_bar_service()->RemoveObserver(this);
}
GURL one_google_bar_url() const { return one_google_bar_url_; }
// Waits for OneGoogleBar data to be updated. Should only be used once.
void WaitForOneGoogleBarDataUpdate() { runloop_->Run(); }
bool GetAndResetOneGoogleBarRequestSeen() {
base::AutoLock lock(lock_);
bool result = one_google_bar_request_seen_;
one_google_bar_request_seen_ = false;
return result;
}
private:
OneGoogleBarService* one_google_bar_service() {
return OneGoogleBarServiceFactory::GetForProfile(profile());
}
void MonitorRequest(const net::test_server::HttpRequest& request) {
if (request.GetURL() == one_google_bar_url_) {
base::AutoLock lock(lock_);
one_google_bar_request_seen_ = true;
}
}
net::EmbeddedTestServer https_test_server_;
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<base::RunLoop> runloop_;
// Initialized on the UI thread in SetUpOnMainThread. Read on UI and Embedded
// Test Server IO thread thereafter.
GURL one_google_bar_url_;
// Accessed on multiple threads- UI and Embedded Test Server IO thread. Access
// requires acquiring |lock_|.
bool one_google_bar_request_seen_ = false;
base::Lock lock_;
DISALLOW_COPY_AND_ASSIGN(LocalNTPInterceptionWebRequestAPITest);
};
IN_PROC_BROWSER_TEST_F(LocalNTPInterceptionWebRequestAPITest,
OneGoogleBarRequestsHidden) {
// Loads an extension which tries to intercept requests to the OneGoogleBar.
ExtensionTestMessageListener listener("ready", true /*will_reply*/);
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("webrequest")
.AppendASCII("ntp_request_interception")
.AppendASCII("extension"));
ASSERT_TRUE(extension);
EXPECT_TRUE(listener.WaitUntilSatisfied());
// Have the extension listen for requests to |one_google_bar_url()|.
listener.Reply(one_google_bar_url().spec());
// Returns true if the given extension was able to intercept the request to
// |one_google_bar_url()|.
auto was_script_request_intercepted =
[this](const std::string& extension_id) {
const std::string result = ExecuteScriptInBackgroundPage(
extension_id, "getAndResetRequestIntercepted();");
EXPECT_TRUE(result == "true" || result == "false")
<< "Unexpected result " << result;
return result == "true";
};
WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_FALSE(GetAndResetOneGoogleBarRequestSeen());
ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
ASSERT_TRUE(search::IsInstantNTP(web_contents));
ASSERT_EQ(GURL(chrome::kChromeSearchLocalNtpUrl),
web_contents->GetController().GetVisibleEntry()->GetURL());
WaitForOneGoogleBarDataUpdate();
ASSERT_TRUE(GetAndResetOneGoogleBarRequestSeen());
// Ensure that the extension wasn't able to intercept the request.
EXPECT_FALSE(was_script_request_intercepted(extension->id()));
// A normal request to |one_google_bar_url()| (i.e. not made by
// OneGoogleBarFetcher) should be intercepted by extensions.
ui_test_utils::NavigateToURL(browser(), one_google_bar_url());
EXPECT_TRUE(was_script_request_intercepted(extension->id()));
ASSERT_TRUE(GetAndResetOneGoogleBarRequestSeen());
}
// Ensure that devtools frontend requests are hidden from the webRequest API.
IN_PROC_BROWSER_TEST_F(DevToolsFrontendInWebRequestApiTest, HiddenRequests) {
// Test expectations differ with the Network Service because of the way
// request interception is done for the test. In the legacy networking path a
// URLRequestMockHTTPJob is used, which does not generate
// |onBeforeHeadersSent| events. With the Network Service enabled, requests
// issued to HTTP URLs by these tests look like real HTTP requests and
// therefore do generate |onBeforeHeadersSent| events.
//
// These tests adjust their expectations accordingly based on whether or not
// the Network Service is enabled.
const char* network_service_arg =
base::FeatureList::IsEnabled(network::features::kNetworkService)
? "NetworkServiceEnabled"
: "NetworkServiceDisabled";
ASSERT_TRUE(RunExtensionSubtestWithArg("webrequest", "test_devtools.html",
network_service_arg))
<< message_;
}
// Tests that the webRequest events aren't dispatched when the request initiator
// is protected by policy.
IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy,
InitiatorProtectedByPolicy) {
// We expect that no request will be hidden or modification blocked. This
// means that the request to example.com will be seen by the extension.
{
ExtensionManagementPolicyUpdater pref(&policy_provider_);
pref.AddRuntimeBlockedHost("*", "*://notexample.com");
}
ASSERT_TRUE(StartEmbeddedTestServer());
// Host navigated to.
const std::string example_com = "example.com";
// URL of a page that initiates a cross domain requests when navigated to.
const GURL extension_test_url = embedded_test_server()->GetURL(
example_com,
"/extensions/api_test/webrequest/policy_blocked/ref_remote_js.html");
ExtensionTestMessageListener listener("ready", false);
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("webrequest/policy_blocked"));
ASSERT_TRUE(extension) << message_;
EXPECT_TRUE(listener.WaitUntilSatisfied());
// Extension communicates back using this listener name.
const std::string listener_message = "protected_origin";
// The number of requests initiated by a protected origin is tracked in
// the extension's background page under this variable name.
const std::string request_counter_name = "window.protectedOriginCount";
EXPECT_EQ(0, GetCountFromBackgroundPage(extension, profile(),
request_counter_name));
// Wait until all remote Javascript files have been blocked / pulled down.
ui_test_utils::NavigateToURLWithDisposition(
browser(), extension_test_url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
// Domain that hosts javascript file referenced by example_com.
const std::string example2_com = "example2.com";
// The server saw a request for the remote Javascript file.
EXPECT_TRUE(BrowsedTo(example2_com));
// The request was seen by the extension.
EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(),
request_counter_name));
// Clear the list of domains the server has seen.
ClearRequestLog();
// Make sure we've cleared the embedded server history.
EXPECT_FALSE(BrowsedTo(example2_com));
// Set the policy to hide requests to example.com or any resource
// it includes. We expect that in this test, the request to example2.com
// will not be seen by the extension.
{
ExtensionManagementPolicyUpdater pref(&policy_provider_);
pref.AddRuntimeBlockedHost("*", "*://" + example_com);
}
// Wait until all remote Javascript files have been pulled down.
ui_test_utils::NavigateToURLWithDisposition(
browser(), extension_test_url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
// The server saw a request for the remote Javascript file.
EXPECT_TRUE(BrowsedTo(example2_com));
// The request was hidden from the extension.
EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(),
request_counter_name));
}
// Tests that the webRequest events aren't dispatched when the URL of the
// request is protected by policy.
// Disabled because it is flaky. See https://crbug.com/835155
IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy,
DISABLED_UrlProtectedByPolicy) {
// Host protected by policy.
const std::string protected_domain = "example.com";
{
ExtensionManagementPolicyUpdater pref(&policy_provider_);
pref.AddRuntimeBlockedHost("*", "*://" + protected_domain);
}
ASSERT_TRUE(StartEmbeddedTestServer());
LoadExtension(test_data_dir_.AppendASCII("webrequest/policy_blocked"));
// Listen in case extension sees the requst.
ExtensionTestMessageListener before_request_listener("protected_url", false);
// Path to resolve during test navigations.
const std::string test_path = "/defaultresponse?protected_url";
// Navigate to the protected domain and wait until page fully loads.
ui_test_utils::NavigateToURLWithDisposition(
browser(), embedded_test_server()->GetURL(protected_domain, test_path),
WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
// The server saw a request for the protected site.
EXPECT_TRUE(BrowsedTo(protected_domain));
// The request was hidden from the extension.
EXPECT_FALSE(before_request_listener.was_satisfied());
// Host not protected by policy.
const std::string unprotected_domain = "notblockedexample.com";
// Now we'll test browsing to a non-protected website where we expect the
// extension to see the request.
ui_test_utils::NavigateToURLWithDisposition(
browser(), embedded_test_server()->GetURL(unprotected_domain, test_path),
WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
// The server saw a request for the non-protected site.
EXPECT_TRUE(BrowsedTo(unprotected_domain));
// The request was visible from the extension.
EXPECT_TRUE(before_request_listener.was_satisfied());
}
// Test that no webRequest events are seen for a protected host during normal
// navigation. This replicates most of the tests from
// WebRequestWithWithheldPermissions with a protected host. Granting a tab
// specific permission shouldn't bypass our policy.
IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy,
WebRequestProtectedByPolicy) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(features::kRuntimeHostPermissions);
// Host protected by policy.
const std::string protected_domain = "example.com";
{
ExtensionManagementPolicyUpdater pref(&policy_provider_);
pref.AddRuntimeBlockedHost("*", "*://" + protected_domain);
}
ASSERT_TRUE(StartEmbeddedTestServer());
ExtensionTestMessageListener listener("ready", false);
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("webrequest_activetab"));
ASSERT_TRUE(extension) << message_;
EXPECT_TRUE(listener.WaitUntilSatisfied());
// Navigate the browser to a page in a new tab.
GURL url = embedded_test_server()->GetURL(protected_domain, "/empty.html");
NavigateParams params(browser(), url, ui::PAGE_TRANSITION_LINK);
params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
ui_test_utils::NavigateToURL(&params);
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
ExtensionActionRunner* runner =
ExtensionActionRunner::GetForWebContents(web_contents);
ASSERT_TRUE(runner);
int port = embedded_test_server()->port();
const std::string kXhrPath = "simple.html";
// The extension shouldn't have currently received any webRequest events,
// since it doesn't have permission (and shouldn't receive any from an XHR).
EXPECT_EQ(0, GetWebRequestCountFromBackgroundPage(extension, profile()));
PerformXhrInFrame(web_contents->GetMainFrame(), protected_domain, port,
kXhrPath);
EXPECT_EQ(0, GetWebRequestCountFromBackgroundPage(extension, profile()));
// Grant activeTab permission, and perform another XHR. The extension should
// still be blocked due to ExtensionSettings policy on example.com.
// Only records ACCESS_WITHHELD, not ACCESS_DENIED, this is why it matches
// BLOCKED_ACTION_NONE.
EXPECT_EQ(BLOCKED_ACTION_NONE, runner->GetBlockedActions(extension));
runner->set_default_bubble_close_action_for_testing(
base::WrapUnique(new ToolbarActionsBarBubbleDelegate::CloseAction(
ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE)));
runner->RunAction(extension, true);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
EXPECT_EQ(BLOCKED_ACTION_NONE, runner->GetBlockedActions(extension));
int xhr_count = GetWebRequestCountFromBackgroundPage(extension, profile());
// ... which means that we should have a non-zero xhr count if the policy
// didn't block the events.
EXPECT_EQ(0, xhr_count);
// And the extension should also block future events.
PerformXhrInFrame(web_contents->GetMainFrame(), protected_domain, port,
kXhrPath);
EXPECT_EQ(0, GetWebRequestCountFromBackgroundPage(extension, profile()));
}
} // namespace extensions