| // 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 <vector> |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/json/json_reader.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/synchronization/lock.h" |
| #include "base/task/post_task.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/time/time.h" |
| #include "base/time/time_override.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.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/api/extension_action/test_extension_action_api_observer.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_tab_util.h" |
| #include "chrome/browser/extensions/extension_util.h" |
| #include "chrome/browser/extensions/extension_with_management_policy_apitest.h" |
| #include "chrome/browser/extensions/identifiability_metrics_test_util.h" |
| #include "chrome/browser/extensions/scripting_permissions_modifier.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/net/system_network_context_manager.h" |
| #include "chrome/browser/new_tab_page/one_google_bar/one_google_bar_loader.h" |
| #include "chrome/browser/new_tab_page/one_google_bar/one_google_bar_service.h" |
| #include "chrome/browser/new_tab_page/one_google_bar/one_google_bar_service_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_destroyer.h" |
| #include "chrome/browser/profiles/profile_manager.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/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/login_state/scoped_test_public_session_login_state.h" |
| #include "components/embedder_support/switches.h" |
| #include "components/google/core/common/google_switches.h" |
| #include "components/policy/core/common/mock_configuration_policy_provider.h" |
| #include "components/policy/policy_constants.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 "components/web_package/test_support/web_bundle_builder.h" |
| #include "content/public/browser/browser_task_traits.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_process_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/content_features.h" |
| #include "content/public/common/page_type.h" |
| #include "content/public/test/browser_test.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 "content/public/test/url_loader_monitor.h" |
| #include "extensions/browser/api/web_request/web_request_api.h" |
| #include "extensions/browser/blocked_action_type.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/process_manager.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 "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "net/base/network_isolation_key.h" |
| #include "net/cookies/site_for_cookies.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/http/http_util.h" |
| #include "net/test/embedded_test_server/default_handlers.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/test/embedded_test_server/request_handler_util.h" |
| #include "net/test/test_data_directory.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "services/metrics/public/cpp/ukm_source_id.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 "services/network/public/mojom/fetch_api.mojom.h" |
| #include "services/network/test/test_url_loader_client.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/common/input/web_input_event.h" |
| #include "ui/base/ui_base_features.h" |
| #include "url/origin.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chromeos/login/login_state/login_state.h" |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| using content::WebContents; |
| |
| namespace extensions { |
| |
| namespace { |
| |
| // This is the public key of tools/origin_trials/eftest.key, used to validate |
| // origin trial tokens generated by tools/origin_trials/generate_token.py. |
| constexpr char kOriginTrialPublicKeyForTesting[] = |
| "dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNA="; |
| |
| 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); |
| }; |
| |
| // Observer that listens for messages from chrome.test.sendMessage to allow them |
| // to be used to trigger browser initiated naviagations from the javascript for |
| // testing purposes. |
| class NavigateTabMessageHandler : public content::NotificationObserver { |
| public: |
| explicit NavigateTabMessageHandler(Profile* profile) : profile_(profile) { |
| registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_TEST_MESSAGE, |
| content::NotificationService::AllSources()); |
| } |
| |
| ~NavigateTabMessageHandler() override {} |
| |
| void Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) override { |
| HandleNavigateTabMessage(type, source, details, profile_); |
| } |
| |
| private: |
| void HandleNavigateTabMessage(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details, |
| Profile* profile) { |
| DCHECK_EQ(NOTIFICATION_EXTENSION_TEST_MESSAGE, type); |
| const auto message = |
| content::Details<std::pair<std::string, bool*>>(details)->first; |
| absl::optional<base::Value> command = base::JSONReader::Read(message); |
| if (command && command->is_dict()) { // Check the message decoded from JSON |
| base::Value* data = command->FindDictKey("navigate"); |
| if (data && data->is_dict()) { |
| int tab_id = *data->FindIntKey("tabId"); |
| GURL url = GURL(*data->FindStringKey("url")); |
| ASSERT_TRUE(url.is_valid()); |
| |
| content::WebContents* contents = nullptr; |
| ExtensionTabUtil::GetTabById( |
| tab_id, profile, profile->HasPrimaryOTRProfile(), &contents); |
| ASSERT_NE(contents, nullptr) |
| << "Could not find tab with id: " << tab_id; |
| content::NavigationController::LoadURLParams params(url); |
| contents->GetController().LoadURLWithParams(params); |
| } |
| } |
| } |
| |
| content::NotificationRegistrar registrar_; |
| Profile* profile_; |
| }; |
| |
| // 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"; |
| |
| constexpr char kCORSUrl[] = "http://cors.test/cors"; |
| constexpr char kCORSProxyUser[] = "testuser"; |
| constexpr char kCORSProxyPass[] = "testpass"; |
| constexpr char kCustomPreflightHeader[] = "x-testheader"; |
| |
| // 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"); |
| } |
| |
| // Returns true if the |extension|'s background page saw an event for a request |
| // with the given |hostname| (|hostname| should exclude port). |
| bool HasSeenWebRequestInBackgroundPage(const Extension* extension, |
| content::BrowserContext* context, |
| const std::string& hostname) { |
| // TODO(devlin): Here and in Get*CountFromBackgroundPage(), we should leverage |
| // ExecuteScriptInBackgroundPage(). |
| ExtensionHost* host = |
| ProcessManager::Get(context)->GetBackgroundHostForExtension( |
| extension->id()); |
| if (!host || !host->host_contents()) |
| return false; |
| |
| bool seen = false; |
| std::string script = base::StringPrintf( |
| R"(domAutomationController.send( |
| window.requestedHostnames.includes('%s'));)", |
| hostname.c_str()); |
| EXPECT_TRUE( |
| ExecuteScriptAndExtractBool(host->host_contents(), script, &seen)); |
| return seen; |
| } |
| |
| void WaitForExtraHeadersListener(base::WaitableEvent* event, |
| content::BrowserContext* browser_context) { |
| if (BrowserContextKeyedAPIFactory<WebRequestAPI>::Get(browser_context) |
| ->HasExtraHeadersListenerForTesting()) { |
| event->Signal(); |
| return; |
| } |
| |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&WaitForExtraHeadersListener, event, browser_context)); |
| } |
| |
| } // namespace |
| |
| class ExtensionWebRequestApiTest : public ExtensionApiTest { |
| public: |
| void SetUpOnMainThread() override { |
| ExtensionApiTest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| navigationHandler_ = std::make_unique<NavigateTabMessageHandler>(profile()); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| ExtensionApiTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitchASCII(switches::kGaiaUrl, "http://gaia.com"); |
| command_line->AppendSwitchASCII(embedder_support::kOriginTrialPublicKey, |
| kOriginTrialPublicKeyForTesting); |
| } |
| |
| 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); |
| |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> |
| CreateURLLoaderFactory() { |
| network::mojom::URLLoaderFactoryParamsPtr params = |
| network::mojom::URLLoaderFactoryParams::New(); |
| params->process_id = network::mojom::kBrowserProcessId; |
| params->automatically_assign_isolation_info = true; |
| params->is_corb_enabled = false; |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> loader_factory; |
| profile() |
| ->GetDefaultStoragePartition() |
| ->GetNetworkContext() |
| ->CreateURLLoaderFactory( |
| loader_factory.InitWithNewPipeAndPassReceiver(), std::move(params)); |
| return loader_factory; |
| } |
| |
| void InstallWebRequestExtension(const std::string& name) { |
| constexpr char kManifest[] = R"({ |
| "name": "%s", |
| "version": "1", |
| "manifest_version": 2, |
| "permissions": [ |
| "webRequest" |
| ] |
| })"; |
| auto dir = std::make_unique<TestExtensionDir>(); |
| dir->WriteManifest(base::StringPrintf(kManifest, name.c_str())); |
| LoadExtension(dir->UnpackedPath()); |
| test_dirs_.push_back(std::move(dir)); |
| } |
| |
| private: |
| std::vector<std::unique_ptr<TestExtensionDir>> test_dirs_; |
| std::unique_ptr<NavigateTabMessageHandler> navigationHandler_; |
| }; |
| |
| class DevToolsFrontendInWebRequestApiTest : public ExtensionApiTest { |
| public: |
| void SetUpOnMainThread() override { |
| ExtensionApiTest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| |
| int port = embedded_test_server()->port(); |
| |
| url_loader_interceptor_ = std::make_unique<content::URLLoaderInterceptor>( |
| base::BindRepeating(&DevToolsFrontendInWebRequestApiTest::OnIntercept, |
| base::Unretained(this), port)); |
| |
| navigationHandler_ = std::make_unique<NavigateTabMessageHandler>(profile()); |
| } |
| |
| void TearDownOnMainThread() override { |
| url_loader_interceptor_.reset(); |
| 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) { |
| // 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_; |
| std::unique_ptr<NavigateTabMessageHandler> navigationHandler_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestApi) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_api.html"})) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestSimple) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_simple.html"})) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestComplex) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_complex.html"})) |
| << message_; |
| } |
| |
| // This test times out regularly on ASAN/MSAN trybots. See |
| // https://crbug.com/733395. |
| // TODO(crbug.com/1177120) Re-enable test |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, DISABLED_WebRequestTypes) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_types.html"})) |
| << message_; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| 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( |
| RunExtensionTest("webrequest_public_session", {.page_url = "test.html"})) |
| << message_; |
| WebRequestPermissions::AllowAllExtensionLocationsInPublicSessionForTesting( |
| false); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| // Test that a request to an OpenSearch description document (OSDD) generates |
| // an event with the expected details. |
| // Flaky on Windows: https://crbug.com/1218893 |
| #if defined(OS_WIN) |
| #define MAYBE_WebRequestTestOSDD DISABLED_WebRequestTestOSDD |
| #else |
| #define MAYBE_WebRequestTestOSDD WebRequestTestOSDD |
| #endif |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, MAYBE_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(RunExtensionTest("webrequest", {.page_url = "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( |
| RunExtensionTest("webrequest", {.page_url = "test_unload.html?1"})) |
| << message_; |
| ASSERT_TRUE( |
| RunExtensionTest("webrequest", {.page_url = "test_unload.html?2"})) |
| << message_; |
| ASSERT_TRUE( |
| RunExtensionTest("webrequest", {.page_url = "test_unload.html?3"})) |
| << message_; |
| ASSERT_TRUE( |
| RunExtensionTest("webrequest", {.page_url = "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_MAC) || defined(OS_LINUX) || defined(OS_CHROMEOS) |
| #define MAYBE_WebRequestUnloadImmediately DISABLED_WebRequestUnloadImmediately |
| #else |
| #define MAYBE_WebRequestUnloadImmediately WebRequestUnloadImmediately |
| #endif |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| MAYBE_WebRequestUnloadImmediately) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE( |
| RunExtensionTest("webrequest", {.page_url = "test_unload.html?5"})) |
| << message_; |
| ASSERT_TRUE( |
| RunExtensionTest("webrequest", {.page_url = "test_unload.html?6"})) |
| << message_; |
| } |
| |
| enum class ProfileMode { |
| kUserProfile, |
| kIncognito, |
| }; |
| |
| class ExtensionWebRequestApiAuthRequiredTest |
| : public ExtensionWebRequestApiTest, |
| public testing::WithParamInterface<ProfileMode> { |
| protected: |
| bool GetEnableIncognito() const { |
| return GetParam() == ProfileMode::kIncognito; |
| } |
| }; |
| |
| // Note: this is flaky on multiple platforms (crbug.com/1003598). |
| IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiAuthRequiredTest, |
| DISABLED_WebRequestAuthRequired) { |
| CancelLoginDialog login_dialog_helper; |
| |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| |
| // Pass "debug" as a custom arg to debug test flakiness. |
| ASSERT_TRUE(RunExtensionTest("webrequest", |
| {.page_url = "test_auth_required.html", |
| .custom_arg = "debug", |
| .open_in_incognito = GetEnableIncognito()}, |
| {.allow_in_incognito = GetEnableIncognito()})) |
| << message_; |
| } |
| |
| // Note: this is flaky on multiple platforms (crbug.com/1003598). Temporarily |
| // enabled to find flakiness cause. |
| IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiAuthRequiredTest, |
| DISABLED_WebRequestAuthRequiredAsync) { |
| CancelLoginDialog login_dialog_helper; |
| |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| |
| // Pass "debug" as a custom arg to debug test flakiness. |
| ASSERT_TRUE(RunExtensionTest("webrequest", |
| {.page_url = "test_auth_required_async.html", |
| .custom_arg = "debug", |
| .open_in_incognito = GetEnableIncognito()}, |
| {.allow_in_incognito = GetEnableIncognito()})) |
| << message_; |
| } |
| |
| // This is flaky on wide variety of platforms (beyond that tracked previously in |
| // https://crbug.com/998369). See https://crbug.com/1026001. |
| IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiAuthRequiredTest, |
| DISABLED_WebRequestAuthRequiredParallel) { |
| CancelLoginDialog login_dialog_helper; |
| |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest("webrequest", |
| {.page_url = "test_auth_required_parallel.html", |
| .open_in_incognito = GetEnableIncognito()}, |
| {.allow_in_incognito = GetEnableIncognito()})) |
| << message_; |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(UserProfile, |
| ExtensionWebRequestApiAuthRequiredTest, |
| ::testing::Values(ProfileMode::kUserProfile)); |
| INSTANTIATE_TEST_SUITE_P(Incognito, |
| ExtensionWebRequestApiAuthRequiredTest, |
| ::testing::Values(ProfileMode::kIncognito)); |
| |
| // 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( |
| RunExtensionTest("webrequest", {.page_url = "test_blocking.html"})) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| WebRequestBlockingSetCookieHeader) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE( |
| RunExtensionTest("webrequest", {.page_url = "test_blocking_cookie.html"})) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestExtraHeaders) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE( |
| RunExtensionTest("webrequest", {.page_url = "test_extra_headers.html"})) |
| << message_; |
| } |
| |
| // Flaky on all platforms: https://crbug.com/1003661 |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| DISABLED_WebRequestExtraHeaders_Auth) { |
| CancelLoginDialog login_dialog_helper; |
| |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest("webrequest", |
| {.page_url = "test_extra_headers_auth.html"})) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestChangeCSPHeaders) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest("webrequest", |
| {.page_url = "test_change_csp_headers.html"})) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| WebRequestCORSWithExtraHeaders) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_cors.html"})) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestRedirects) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE( |
| RunExtensionTest("webrequest", {.page_url = "test_redirects.html"})) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| WebRequestRedirectsWithExtraHeaders) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_redirects.html", |
| .custom_arg = "useExtraHeaders"})) |
| << message_; |
| } |
| |
| // Tests that redirects from secure to insecure don't send the referrer header. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| WebRequestRedirectsToInsecure) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| GURL insecure_destination = |
| embedded_test_server()->GetURL("/extensions/test_file.html"); |
| net::EmbeddedTestServer https_test_server( |
| net::EmbeddedTestServer::TYPE_HTTPS); |
| https_test_server.ServeFilesFromDirectory(test_data_dir_); |
| ASSERT_TRUE(https_test_server.Start()); |
| |
| GURL url = https_test_server.GetURL("/webrequest/simulate_click.html"); |
| |
| base::ListValue custom_args; |
| custom_args.AppendString(url.spec()); |
| custom_args.AppendString(insecure_destination.spec()); |
| |
| std::string config_string; |
| base::JSONWriter::Write(custom_args, &config_string); |
| ASSERT_TRUE(RunExtensionTest("webrequest", |
| {.page_url = "test_redirects_from_secure.html", |
| .custom_arg = config_string.c_str()})) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| WebRequestSubresourceRedirects) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest("webrequest", |
| {.page_url = "test_subresource_redirects.html"})) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| WebRequestSubresourceRedirectsWithExtraHeaders) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest("webrequest", |
| {.page_url = "test_subresource_redirects.html", |
| .custom_arg = "useExtraHeaders"})) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestNewTab) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| // Wait for the extension to set itself up and return control to us. |
| ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_newTab.html"})) |
| << message_; |
| |
| WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| EXPECT_TRUE(content::WaitForLoadStop(tab)); |
| |
| ResultCatcher catcher; |
| |
| ExtensionRegistry* registry = ExtensionRegistry::Get(browser()->profile()); |
| const Extension* extension = registry->GetExtensionById( |
| last_loaded_extension_id(), extensions::ExtensionRegistry::ENABLED); |
| 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::Type::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->GetMainFrame()->GetRenderViewHost()->GetWidget()->ForwardMouseEvent( |
| mouse_event); |
| mouse_event.SetType(blink::WebInputEvent::Type::kMouseUp); |
| tab->GetMainFrame()->GetRenderViewHost()->GetWidget()->ForwardMouseEvent( |
| mouse_event); |
| |
| ASSERT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| } |
| |
| // This test times out regularly on MSAN trybots. See https://crbug.com/733395. |
| // Also flaky. See https://crbug.com/846555. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| DISABLED_WebRequestDeclarative1) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE( |
| RunExtensionTest("webrequest", {.page_url = "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( |
| RunExtensionTest("webrequest", {.page_url = "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()->GetPrimaryOTRProfile(/*create_if_needed=*/true)); |
| |
| ExtensionTestMessageListener listener("done", false); |
| ExtensionTestMessageListener listener_incognito("done_incognito", false); |
| |
| ASSERT_TRUE(LoadExtension( |
| test_data_dir_.AppendASCII("webrequest_permissions") |
| .AppendASCII(extension_directory), |
| {.allow_in_incognito = load_extension_with_incognito_permission})); |
| |
| // 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(crbug.com/238179): Cure these flaky tests. |
| // TODO(crbug.com/1154345): Bulk-disabled as part of mac arm64 bot greening |
| // TODO(crbug.com/1222127): Further disabled due to ongoing flakiness. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, DISABLED_PostData1) { |
| // Test HTML form POST data access with the default and "url" encoding. |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_post1.html"})) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, DISABLED_PostData2) { |
| // Test HTML form POST data access with the multipart and plaintext encoding. |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "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 = |
| LoadExtension(test_data_dir_.AppendASCII("webrequest_reload"), |
| {.allow_in_incognito = true}); |
| 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 from "webrequest_extensions/extension" 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. Any requests made |
| // by it should not be visible to other extensions, since they won't have |
| // access to the request initiator. |
| // |
| // OTOH, the content script executes fetches/XHRs as-if they were initiated by |
| // the webpage that the content script got injected into. Here, the webpage |
| // has origin of http://127.0.0.1:<some port>, and so the webRequest API |
| // extension should have access to the request. |
| 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<const 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(); |
| 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 runtime host permissions. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| WebRequestWithWithheldPermissions) { |
| 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_; |
| ScriptingPermissionsModifier(profile(), base::WrapRefCounted(extension)) |
| .SetWithholdHostPermissions(true); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| // Navigate the browser to a page in a new tab. The page at "a.com" has two |
| // two cross-origin iframes to "b.com" and "c.com". |
| 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(¶ms); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| ExtensionActionRunner* runner = |
| ExtensionActionRunner::GetForWebContents(web_contents); |
| ASSERT_TRUE(runner); |
| |
| // The extension shouldn't have currently received any webRequest events, |
| // since it doesn't have any permissions. |
| EXPECT_EQ(0, GetWebRequestCountFromBackgroundPage(extension, profile())); |
| |
| 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); |
| }; |
| |
| content::RenderFrameHost* main_frame = nullptr; |
| content::RenderFrameHost* child_frame = nullptr; |
| 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(); |
| |
| int port = embedded_test_server()->port(); |
| const std::string kXhrPath = "simple.html"; |
| |
| // The extension shouldn't be able to intercept the xhr requests since it |
| // doesn't have any permissions. |
| 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. |
| 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, and the extension will have |
| // received access to the main-frame ("a.com"). It should still not be able to |
| // intercept the cross-origin sub-frame requests to "b.com" and "c.com". |
| get_main_and_child_frame(web_contents, &main_frame, &child_frame); |
| EXPECT_TRUE(HasSeenWebRequestInBackgroundPage(extension, profile(), "a.com")); |
| EXPECT_FALSE( |
| HasSeenWebRequestInBackgroundPage(extension, profile(), "b.com")); |
| EXPECT_FALSE( |
| HasSeenWebRequestInBackgroundPage(extension, profile(), "c.com")); |
| |
| // The withheld sub-frame requests should not show up as a blocked action. |
| EXPECT_EQ(BLOCKED_ACTION_NONE, runner->GetBlockedActions(extension)); |
| |
| int request_count = |
| GetWebRequestCountFromBackgroundPage(extension, profile()); |
| |
| // ... and the extension should receive future events. |
| PerformXhrInFrame(main_frame, kHost, port, kXhrPath); |
| ++request_count; |
| EXPECT_EQ(request_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(request_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)); |
| |
| // Revoke the extension's tab permissions. |
| ActiveTabPermissionGranter* granter = |
| TabHelper::FromWebContents(web_contents)->active_tab_permission_granter(); |
| ASSERT_TRUE(granter); |
| granter->RevokeForTesting(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // The extension should no longer receive webRequest events since they are |
| // withheld. The extension icon should get updated to show the wants-to-run |
| // badge UI. |
| TestExtensionActionAPIObserver action_updated_waiter(profile(), |
| extension->id()); |
| PerformXhrInFrame(main_frame, kHost, port, kXhrPath); |
| action_updated_waiter.Wait(); |
| EXPECT_EQ(web_contents, action_updated_waiter.last_web_contents()); |
| |
| EXPECT_EQ(request_count, |
| GetWebRequestCountFromBackgroundPage(extension, profile())); |
| EXPECT_EQ(BLOCKED_ACTION_WEB_REQUEST, runner->GetBlockedActions(extension)); |
| } |
| |
| // Test that extensions with granted runtime host permissions to a tab can |
| // intercept cross-origin requests from that tab. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| WebRequestWithheldPermissionsCrossOriginRequests) { |
| 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_; |
| ScriptingPermissionsModifier(profile(), base::WrapRefCounted(extension)) |
| .SetWithholdHostPermissions(true); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| ui_test_utils::NavigateToURL( |
| browser(), embedded_test_server()->GetURL( |
| "a.com", "/extensions/cross_site_script.html")); |
| |
| const std::string kCrossSiteHost("b.com"); |
| EXPECT_FALSE( |
| HasSeenWebRequestInBackgroundPage(extension, profile(), kCrossSiteHost)); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ExtensionActionRunner* runner = |
| ExtensionActionRunner::GetForWebContents(web_contents); |
| ASSERT_TRUE(runner); |
| |
| EXPECT_EQ(BLOCKED_ACTION_WEB_REQUEST, runner->GetBlockedActions(extension)); |
| |
| // Grant runtime host permission to the page. The page should refresh. Even |
| // though the request is for b.com (and the extension only has access to |
| // a.com), it should still see the request. This is necessary for extensions |
| // with webRequest to work with runtime host permissions. |
| // https://crbug.com/851722. |
| runner->set_default_bubble_close_action_for_testing( |
| base::WrapUnique(new ToolbarActionsBarBubbleDelegate::CloseAction( |
| ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE))); |
| runner->RunAction(extension, true /* grant tab permissions */); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(content::WaitForLoadStop(web_contents)); |
| EXPECT_EQ(BLOCKED_ACTION_NONE, runner->GetBlockedActions(extension)); |
| |
| EXPECT_TRUE( |
| HasSeenWebRequestInBackgroundPage(extension, profile(), kCrossSiteHost)); |
| } |
| |
| // Tests behavior when an extension has withheld access to a request's URL, but |
| // not the initiator's (tab's) URL. Regression test for |
| // https://crbug.com/891586. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| WithheldHostPermissionsForCrossOriginWithoutInitiator) { |
| content::SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // TODO(devlin): This is essentially copied from the webrequest_activetab |
| // API test extension, but has different permissions. Maybe it's worth having |
| // all tests use a common pattern? |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest( |
| R"({ |
| "name": "Web Request Withheld Hosts", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["*://b.com:*/*", "webRequest"] |
| })"); |
| test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), |
| R"(window.webRequestCount = 0; |
| window.requestedHostnames = []; |
| |
| chrome.webRequest.onBeforeRequest.addListener(function(details) { |
| ++window.webRequestCount; |
| window.requestedHostnames.push((new URL(details.url)).hostname); |
| }, {urls:['<all_urls>']}); |
| chrome.test.sendMessage('ready');)"); |
| |
| // 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_dir.UnpackedPath()); |
| ASSERT_TRUE(extension) << message_; |
| ScriptingPermissionsModifier(profile(), base::WrapRefCounted(extension)) |
| .SetWithholdHostPermissions(true); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| // Navigate to example.com, which has a cross-site script to b.com. |
| ui_test_utils::NavigateToURL( |
| browser(), embedded_test_server()->GetURL( |
| "example.com", "/extensions/cross_site_script.html")); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| |
| ExtensionActionRunner* runner = |
| ExtensionActionRunner::GetForWebContents(web_contents); |
| ASSERT_TRUE(runner); |
| |
| // Even though the extension has access to b.com, it shouldn't show that it |
| // wants to run, because example.com is not a requested host. |
| EXPECT_EQ(BLOCKED_ACTION_NONE, runner->GetBlockedActions(extension)); |
| EXPECT_FALSE( |
| HasSeenWebRequestInBackgroundPage(extension, profile(), "b.com")); |
| |
| // Navigating to b.com (so that the script is hosted on the same origin as |
| // the WebContents) should show the extension wants to run. |
| ui_test_utils::NavigateToURL( |
| browser(), embedded_test_server()->GetURL( |
| "b.com", "/extensions/cross_site_script.html")); |
| 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()); |
| |
| auto get_clients_google_request_count = [this, extension]() { |
| return GetCountFromBackgroundPage(extension, profile(), |
| "window.clientsGoogleWebRequestCount"); |
| }; |
| auto get_yahoo_request_count = [this, extension]() { |
| return GetCountFromBackgroundPage(extension, profile(), |
| "window.yahooWebRequestCount"); |
| }; |
| |
| EXPECT_EQ(0, get_clients_google_request_count()); |
| EXPECT_EQ(0, get_yahoo_request_count()); |
| |
| 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(¶ms); |
| |
| EXPECT_EQ(0, get_clients_google_request_count()); |
| EXPECT_EQ(0, get_yahoo_request_count()); |
| |
| 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, get_clients_google_request_count()); |
| EXPECT_EQ(0, get_yahoo_request_count()); |
| |
| auto make_browser_request = [this](const GURL& url) { |
| auto request = std::make_unique<network::ResourceRequest>(); |
| request->url = url; |
| request->destination = network::mojom::RequestDestination::kEmpty; |
| request->resource_type = |
| static_cast<int>(blink::mojom::ResourceType::kSubResource); |
| |
| auto* url_loader_factory = profile() |
| ->GetDefaultStoragePartition() |
| ->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()); |
| }; |
| |
| // Now perform a request to "client1.google.com" from the browser process. |
| // This should *not* be visible to the WebRequest API. We should still have |
| // only seen the single render-initiated request from the first half of the |
| // test. |
| make_browser_request( |
| embedded_test_server()->GetURL("clients1.google.com", "/simple.html")); |
| EXPECT_EQ(1, get_clients_google_request_count()); |
| |
| // Other non-navigation browser requests should also be hidden from |
| // extensions. |
| make_browser_request( |
| embedded_test_server()->GetURL("yahoo.com", "/simple.html")); |
| EXPECT_EQ(0, get_yahoo_request_count()); |
| } |
| |
| // 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(), GURL("http://does.not.resolve.test/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. |
| // TODO(crbug.com/1121727): Test is flaky on multiple platforms. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, DISABLED_WebSocketRequest) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(StartWebSocketServer(net::GetWebSocketTestDataDirectory())); |
| ASSERT_TRUE( |
| RunExtensionTest("webrequest", {.page_url = "test_websocket.html"})) |
| << message_; |
| } |
| |
| // Test that the webRequest events are dispatched for the WebSocket handshake |
| // requests when authenrication is requested by server. |
| // TODO(crbug.com/1177120) Re-enable test |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| DISABLED_WebSocketRequestAuthRequired) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(StartWebSocketServer(net::GetWebSocketTestDataDirectory(), true)); |
| ASSERT_TRUE( |
| RunExtensionTest("webrequest", {.page_url = "test_websocket_auth.html"})) |
| << message_; |
| } |
| |
| // Test that the webRequest events are dispatched for the WebSocket handshake |
| // requests. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebSocketRequestOnWorker) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(StartWebSocketServer(net::GetWebSocketTestDataDirectory())); |
| ASSERT_TRUE(RunExtensionTest("webrequest", |
| {.page_url = "test_websocket_worker.html"})) |
| << message_; |
| } |
| |
| // Tests that a clean close from the server is not reported as an error when |
| // there is a race between OnDropChannel and SendFrame. |
| // Regression test for https://crbug.com/937790. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebSocketCleanClose) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(StartWebSocketServer(net::GetWebSocketTestDataDirectory())); |
| ASSERT_TRUE(RunExtensionTest("webrequest", |
| {.page_url = "test_websocket_clean_close.html"})) |
| << message_; |
| } |
| |
| // Test behavior when intercepting requests from a browser-initiated url fetch. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| WebRequestURLLoaderInterception) { |
| // 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');)"); |
| |
| const Extension* extension = nullptr; |
| { |
| ExtensionTestMessageListener listener("ready", false); |
| extension = LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| } |
| |
| // Taken from test/data/extensions/body1.html. |
| const char kGoogleBodyContent[] = "dog"; |
| const char kGoogleFullContent[] = "<html>\n<body>dog</body>\n</html>\n"; |
| |
| // Taken from test/data/extensions/body2.html. |
| const char kExampleBodyContent[] = "cat"; |
| const char kExampleFullContent[] = "<html>\n<body>cat</body>\n</html>\n"; |
| |
| embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting( |
| [&](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| std::unique_ptr<net::test_server::BasicHttpResponse> response( |
| new net::test_server::BasicHttpResponse); |
| if (request.relative_url == "/extensions/body1.html") { |
| response->set_code(net::HTTP_OK); |
| response->set_content(kGoogleFullContent); |
| return std::move(response); |
| } else if (request.relative_url == "/extensions/body2.html") { |
| response->set_code(net::HTTP_OK); |
| response->set_content(kExampleFullContent); |
| return std::move(response); |
| } |
| |
| return nullptr; |
| })); |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| |
| GURL google_url = |
| embedded_test_server()->GetURL("google.com", "/extensions/body1.html"); |
| |
| // First, check normal requests (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"); |
| |
| 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); |
| } |
| |
| // A callback allow waiting for responses to complete with an expected status |
| // and given content. |
| auto make_browser_request = |
| [](network::mojom::URLLoaderFactory* url_loader_factory, const GURL& url, |
| const absl::optional<std::string>& expected_response, |
| int expected_net_code) { |
| auto request = std::make_unique<network::ResourceRequest>(); |
| request->url = url; |
| |
| content::SimpleURLLoaderTestHelper simple_loader_helper; |
| auto simple_loader = network::SimpleURLLoader::Create( |
| std::move(request), TRAFFIC_ANNOTATION_FOR_TESTS); |
| simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie( |
| url_loader_factory, simple_loader_helper.GetCallback()); |
| |
| simple_loader_helper.WaitForCallback(); |
| |
| if (expected_response.has_value()) { |
| EXPECT_TRUE(!!simple_loader_helper.response_body()); |
| EXPECT_EQ(*simple_loader_helper.response_body(), *expected_response); |
| EXPECT_EQ(200, |
| simple_loader->ResponseInfo()->headers->response_code()); |
| } else { |
| EXPECT_FALSE(!!simple_loader_helper.response_body()); |
| EXPECT_EQ(simple_loader->NetError(), expected_net_code); |
| } |
| }; |
| |
| // Next, try a series of requests through URLRequestFetchers (rather than a |
| // renderer). |
| auto* url_loader_factory = profile() |
| ->GetDefaultStoragePartition() |
| ->GetURLLoaderFactoryForBrowserProcess() |
| .get(); |
| |
| { |
| // google.com should be unaffected by the extension and should succeed. |
| SCOPED_TRACE("google.com with Profile's url loader"); |
| make_browser_request(url_loader_factory, google_url, kGoogleFullContent, |
| net::OK); |
| } |
| |
| { |
| // example.com should also succeed since non-navigation browser-initiated |
| // requests are hidden from extensions. See crbug.com/884932. |
| SCOPED_TRACE("example.com with Profile's url loader"); |
| make_browser_request(url_loader_factory, example_url, kExampleFullContent, |
| net::OK); |
| } |
| |
| // Requests going through the system network context manager should always |
| // succeed. |
| SystemNetworkContextManager* system_network_context_manager = |
| g_browser_process->system_network_context_manager(); |
| network::mojom::URLLoaderFactory* system_url_loader_factory = |
| system_network_context_manager->GetURLLoaderFactory(); |
| { |
| // google.com should succeed (again). |
| SCOPED_TRACE("google.com with System's network context manager"); |
| make_browser_request(system_url_loader_factory, google_url, |
| kGoogleFullContent, net::OK); |
| } |
| { |
| // example.com should also succeed, since it's not through the profile's |
| // request context. |
| SCOPED_TRACE("example.com with System's network context manager"); |
| make_browser_request(system_url_loader_factory, example_url, |
| kExampleFullContent, net::OK); |
| } |
| } |
| |
| // Test that extensions need host permissions to both the request url and |
| // initiator to intercept a request. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, InitiatorAccessRequired) { |
| 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 access to the initiator. |
| {"no-permission.com", "example4.com", ""}, |
| // No access to the request url. |
| {"example.com", "no-permission.com", ""}}; |
| |
| int port = embedded_test_server()->port(); |
| |
| int expected_requests_intercepted_count = 0; |
| 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"); |
| |
| // Ensure that the extension wasn't able to intercept the request if it |
| // didn't have permission to the initiator or the request url. |
| if (!testcase.expected_initiator.empty()) |
| ++expected_requests_intercepted_count; |
| |
| // Run a script in the extensions background page to ensure that we have |
| // received the initiator message from the extension. |
| ASSERT_EQ( |
| std::to_string(expected_requests_intercepted_count), |
| ExecuteScriptInBackgroundPage(extension->id(), |
| "window.domAutomationController.send(" |
| "requestsIntercepted.toString());")); |
| |
| if (testcase.expected_initiator.empty()) { |
| EXPECT_FALSE(initiator_listener.was_satisfied()); |
| } else { |
| ASSERT_TRUE(initiator_listener.was_satisfied()); |
| EXPECT_EQ( |
| "http://" + testcase.expected_initiator + ":" + std::to_string(port), |
| initiator_listener.message()); |
| } |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| WebRequestApiClearsBindingOnFirstListener) { |
| // Skip if the proxy is forced since the bindings will never be cleared in |
| // that case. |
| if (base::FeatureList::IsEnabled( |
| extensions_features::kForceWebRequestProxyForTest)) { |
| return; |
| } |
| |
| mojo::Remote<network::mojom::URLLoaderFactory> loader_factory( |
| CreateURLLoaderFactory()); |
| bool has_connection_error = false; |
| loader_factory.set_disconnect_handler( |
| base::BindLambdaForTesting([&]() { has_connection_error = true; })); |
| |
| InstallWebRequestExtension("extension1"); |
| profile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting(); |
| EXPECT_TRUE(has_connection_error); |
| loader_factory.reset(); |
| |
| // The second time there should be no connection error. |
| loader_factory.Bind(CreateURLLoaderFactory()); |
| has_connection_error = false; |
| loader_factory.set_disconnect_handler( |
| base::BindLambdaForTesting([&]() { has_connection_error = true; })); |
| InstallWebRequestExtension("extension2"); |
| profile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting(); |
| EXPECT_FALSE(has_connection_error); |
| } |
| |
| // Regression test for http://crbug.com/878366. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| WebRequestApiDoesNotCrashOnErrorAfterProfileDestroyed) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| |
| // Create a profile that will be destroyed later. |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| chromeos::ProfileHelper::SetAlwaysReturnPrimaryUserForTesting(true); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| ProfileManager* profile_manager = g_browser_process->profile_manager(); |
| Profile* temp_profile = |
| Profile::CreateProfile( |
| profile_manager->user_data_dir().AppendASCII("profile"), nullptr, |
| Profile::CreateMode::CREATE_MODE_SYNCHRONOUS) |
| .release(); |
| // Create a WebRequestAPI instance that we can control the lifetime of. |
| auto api = std::make_unique<WebRequestAPI>(temp_profile); |
| // Make sure we are proxying for |temp_profile|. |
| api->ForceProxyForTesting(); |
| temp_profile->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting(); |
| |
| mojo::Remote<network::mojom::URLLoaderFactory> factory; |
| auto pending_receiver = factory.BindNewPipeAndPassReceiver(); |
| auto temp_web_contents = |
| WebContents::Create(WebContents::CreateParams(temp_profile)); |
| content::RenderFrameHost* frame = temp_web_contents->GetMainFrame(); |
| EXPECT_TRUE(api->MaybeProxyURLLoaderFactory( |
| frame->GetProcess()->GetBrowserContext(), frame, |
| frame->GetProcess()->GetID(), |
| content::ContentBrowserClient::URLLoaderFactoryType::kDocumentSubResource, |
| absl::nullopt, ukm::kInvalidSourceIdObj, &pending_receiver, nullptr)); |
| temp_web_contents.reset(); |
| auto params = network::mojom::URLLoaderFactoryParams::New(); |
| params->process_id = 0; |
| temp_profile->GetDefaultStoragePartition() |
| ->GetNetworkContext() |
| ->CreateURLLoaderFactory(std::move(pending_receiver), std::move(params)); |
| |
| network::TestURLLoaderClient client; |
| mojo::PendingRemote<network::mojom::URLLoader> loader; |
| network::ResourceRequest resource_request; |
| resource_request.url = embedded_test_server()->GetURL("/hung"); |
| factory->CreateLoaderAndStart( |
| loader.InitWithNewPipeAndPassReceiver(), 0, |
| network::mojom::kURLLoadOptionNone, resource_request, |
| client.CreateRemote(), |
| net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| // Destroy profile, unbind client to cause a connection error, and delete the |
| // WebRequestAPI. This will cause the connection error that will reach the |
| // proxy before the ProxySet shutdown code runs on the IO thread. |
| api->Shutdown(); |
| |
| // We are about to destroy a profile. In production that will only happen |
| // as part of the destruction of BrowserProcess's ProfileManager. This |
| // happens in PostMainMessageLoopRun(). This means that to have this test |
| // represent production we have to make sure that no tasks are pending on the |
| // main thread before we destroy the profile. We also would need to prohibit |
| // the posting of new tasks on the main thread as in production the main |
| // thread's message loop will not be accepting them. We fallback on flushing |
| // the ThreadPool here to avoid the posts coming from it. |
| content::RunAllTasksUntilIdle(); |
| |
| ProfileDestroyer::DestroyProfileWhenAppropriate(temp_profile); |
| client.Unbind(); |
| api.reset(); |
| } |
| |
| // 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"); |
| 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()); |
| // Wait for webRequest listeners to be set up. |
| profile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting(); |
| |
| // 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 WebUI NTP can't be intercepted by extensions. |
| class WebUiNtpInterceptionWebRequestAPITest |
| : public ExtensionApiTest, |
| public OneGoogleBarServiceObserver { |
| public: |
| WebUiNtpInterceptionWebRequestAPITest() |
| : https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} |
| |
| // ExtensionApiTest override: |
| void SetUp() override { |
| https_test_server_.RegisterRequestMonitor(base::BindRepeating( |
| &WebUiNtpInterceptionWebRequestAPITest::MonitorRequest, |
| base::Unretained(this))); |
| ASSERT_TRUE(https_test_server_.InitializeAndListen()); |
| ExtensionApiTest::SetUp(); |
| } |
| 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()->loader_for_testing()->GetLoadURLForTesting(); |
| |
| // 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_; |
| 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(WebUiNtpInterceptionWebRequestAPITest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(WebUiNtpInterceptionWebRequestAPITest, |
| 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"; |
| }; |
| |
| ASSERT_FALSE(GetAndResetOneGoogleBarRequestSeen()); |
| ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL)); |
| ASSERT_EQ(ntp_test_utils::GetFinalNtpUrl(browser()->profile()), |
| browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetLastCommittedURL()); |
| 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) { |
| ASSERT_TRUE( |
| RunExtensionTest("webrequest", {.page_url = "test_devtools.html"})) |
| << 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.AddPolicyBlockedHost("*", "*://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_LOAD_STOP); |
| |
| // 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.AddPolicyBlockedHost("*", "*://" + 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_LOAD_STOP); |
| |
| // 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.AddPolicyBlockedHost("*", "*://" + 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_LOAD_STOP); |
| |
| // 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_LOAD_STOP); |
| |
| // 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) { |
| // Host protected by policy. |
| const std::string protected_domain = "example.com"; |
| |
| { |
| ExtensionManagementPolicyUpdater pref(&policy_provider_); |
| pref.AddPolicyBlockedHost("*", "*://" + protected_domain); |
| } |
| |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| |
| ExtensionTestMessageListener listener("ready", false); |
| const Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("webrequest_activetab")); |
| ASSERT_TRUE(extension) << message_; |
| ScriptingPermissionsModifier(profile(), base::WrapRefCounted(extension)) |
| .SetWithholdHostPermissions(true); |
| 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(¶ms); |
| |
| 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())); |
| } |
| |
| // A test fixture which mocks the Time::Now() function to ensure that the |
| // default clock returns monotonically increasing time. |
| class ExtensionWebRequestMockedClockTest : public ExtensionWebRequestApiTest { |
| public: |
| ExtensionWebRequestMockedClockTest() |
| : scoped_time_clock_override_(&ExtensionWebRequestMockedClockTest::Now, |
| nullptr, |
| nullptr) {} |
| |
| private: |
| static base::Time Now() { |
| static base::Time now_time = base::Time::UnixEpoch(); |
| now_time += base::TimeDelta::FromMilliseconds(1); |
| return now_time; |
| } |
| |
| base::subtle::ScopedTimeClockOverrides scoped_time_clock_override_; |
| DISALLOW_COPY_AND_ASSIGN(ExtensionWebRequestMockedClockTest); |
| }; |
| |
| // Tests that we correctly dispatch the OnActionIgnored event on an extension |
| // if the extension's proposed redirect is ignored. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestMockedClockTest, |
| OnActionIgnored_Redirect) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| |
| // Load the two extensions. They redirect "google.com" main-frame urls to |
| // the corresponding "example.com and "foo.com" urls. |
| base::FilePath test_dir = |
| test_data_dir_.AppendASCII("webrequest/on_action_ignored"); |
| |
| // Load the first extension. |
| ExtensionTestMessageListener ready_1_listener("ready_1", |
| false /*will_reply*/); |
| const Extension* extension_1 = |
| LoadExtension(test_dir.AppendASCII("extension_1")); |
| ASSERT_TRUE(extension_1); |
| ASSERT_TRUE(ready_1_listener.WaitUntilSatisfied()); |
| const std::string extension_id_1 = extension_1->id(); |
| |
| // Load the second extension. |
| ExtensionTestMessageListener ready_2_listener("ready_2", |
| false /*will_reply*/); |
| const Extension* extension_2 = |
| LoadExtension(test_dir.AppendASCII("extension_2")); |
| ASSERT_TRUE(extension_2); |
| ASSERT_TRUE(ready_2_listener.WaitUntilSatisfied()); |
| const std::string extension_id_2 = extension_2->id(); |
| |
| const ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); |
| EXPECT_LT(prefs->GetInstallTime(extension_id_1), |
| prefs->GetInstallTime(extension_id_2)); |
| |
| // The extensions will notify the browser if their proposed redirect was |
| // successful or not. |
| ExtensionTestMessageListener redirect_ignored_listener("redirect_ignored", |
| false /*will_reply*/); |
| ExtensionTestMessageListener redirect_successful_listener( |
| "redirect_successful", false /*will_reply*/); |
| |
| const GURL url = embedded_test_server()->GetURL("google.com", "/simple.html"); |
| const GURL expected_redirect_url_1 = |
| embedded_test_server()->GetURL("example.com", "/simple.html"); |
| const GURL expected_redirect_url_2 = |
| embedded_test_server()->GetURL("foo.com", "/simple.html"); |
| |
| ui_test_utils::NavigateToURL(browser(), url); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| |
| // The second extension is the latest installed, hence it's redirect url |
| // should take precedence. |
| EXPECT_EQ(expected_redirect_url_2, web_contents->GetLastCommittedURL()); |
| EXPECT_TRUE(redirect_ignored_listener.WaitUntilSatisfied()); |
| EXPECT_EQ(extension_id_1, |
| redirect_ignored_listener.extension_id_for_message()); |
| EXPECT_TRUE(redirect_successful_listener.WaitUntilSatisfied()); |
| EXPECT_EQ(extension_id_2, |
| redirect_successful_listener.extension_id_for_message()); |
| |
| // Now let |extension_1| be installed after |extension_2|. For an unpacked |
| // extension, reloading is equivalent to a reinstall. |
| ready_1_listener.Reset(); |
| ReloadExtension(extension_id_1); |
| ASSERT_TRUE(ready_1_listener.WaitUntilSatisfied()); |
| |
| EXPECT_LT(prefs->GetInstallTime(extension_id_2), |
| prefs->GetInstallTime(extension_id_1)); |
| |
| redirect_ignored_listener.Reset(); |
| redirect_successful_listener.Reset(); |
| ui_test_utils::NavigateToURL(browser(), url); |
| |
| // The first extension is the latest installed, hence it's redirect url |
| // should take precedence. |
| EXPECT_EQ(expected_redirect_url_1, web_contents->GetLastCommittedURL()); |
| EXPECT_TRUE(redirect_ignored_listener.WaitUntilSatisfied()); |
| EXPECT_EQ(extension_id_2, |
| redirect_ignored_listener.extension_id_for_message()); |
| EXPECT_TRUE(redirect_successful_listener.WaitUntilSatisfied()); |
| EXPECT_EQ(extension_id_1, |
| redirect_successful_listener.extension_id_for_message()); |
| } |
| |
| // Regression test for http://crbug.com/1074282. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, StaleHeadersAfterRedirect) { |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Web Request Stale Headers Test", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] |
| })"); |
| test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( |
| window.locationCount = 0; |
| window.requestCount = 0; |
| chrome.test.sendMessage('ready', function(reply) { |
| chrome.webRequest.onResponseStarted.addListener(function(details) { |
| window.requestCount++; |
| var headers = details.responseHeaders; |
| for (var i = 0; i < headers.length; i++) { |
| if (headers[i].name === 'Location') { |
| window.locationCount++; |
| } |
| } |
| }, |
| {urls: ['<all_urls>'], types: ['xmlhttprequest']}, |
| ['responseHeaders', 'extraHeaders'] |
| ); |
| }); |
| )"); |
| |
| ExtensionTestMessageListener listener("ready", true); |
| const Extension* extension = LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| auto task_runner = base::SequencedTaskRunnerHandle::Get(); |
| embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting( |
| [&](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| if (request.relative_url != "/redirect-and-wait") |
| return nullptr; |
| |
| // Wait for the listener to be installed before proceeding. |
| base::WaitableEvent unblock( |
| base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| // Post a task to the UI thread since the request handler runs on a |
| // background thread. |
| task_runner->PostTask(FROM_HERE, base::BindLambdaForTesting([&] { |
| listener.Reply(""); |
| WaitForExtraHeadersListener( |
| &unblock, browser()->profile()); |
| })); |
| unblock.Wait(); |
| |
| auto http_response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| http_response->set_code(net::HTTP_MOVED_PERMANENTLY); |
| http_response->AddCustomHeader( |
| "Location", embedded_test_server()->GetURL("/echo").spec()); |
| return http_response; |
| })); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Navigate to a basic page so XHR requests work. |
| ui_test_utils::NavigateToURL(browser(), |
| embedded_test_server()->GetURL("/echo")); |
| |
| // Make a XHR request which redirects. The final response should not include |
| // the Location header. |
| auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); |
| PerformXhrInFrame(web_contents->GetMainFrame(), |
| embedded_test_server()->host_port_pair().host(), |
| embedded_test_server()->port(), "redirect-and-wait"); |
| EXPECT_EQ( |
| GetCountFromBackgroundPage(extension, profile(), "window.requestCount"), |
| 1); |
| EXPECT_EQ( |
| GetCountFromBackgroundPage(extension, profile(), "window.locationCount"), |
| 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, ChangeHeaderUMAs) { |
| using RequestHeaderType = |
| extension_web_request_api_helpers::RequestHeaderType; |
| using ResponseHeaderType = |
| extension_web_request_api_helpers::ResponseHeaderType; |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Web Request UMA Test", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] |
| })"); |
| test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( |
| chrome.webRequest.onBeforeSendHeaders.addListener(function(details) { |
| var headers = details.requestHeaders; |
| for (var i = 0; i < headers.length; i++) { |
| if (headers[i].name.toLowerCase() == 'user-agent') { |
| headers[i].value = 'foo'; |
| break; |
| } |
| } |
| headers.push({name: 'Foo1', value: 'Bar1'}); |
| headers.push({name: 'Foo2', value: 'Bar2'}); |
| headers.push({name: 'DNT', value: '0'}); |
| return {requestHeaders: headers}; |
| }, {urls: ['*://*/set-cookie*']}, |
| ['blocking', 'requestHeaders', 'extraHeaders']); |
| |
| chrome.webRequest.onHeadersReceived.addListener(function(details) { |
| var headers = details.responseHeaders; |
| for (var i = 0; i < headers.length; i++) { |
| if (headers[i].name.toLowerCase() == 'set-cookie' && |
| headers[i].value == 'key1=val1') { |
| headers.splice(i, 1); |
| i--; |
| } else if (headers[i].name == 'Content-Length') { |
| headers[i].value = '0'; |
| } |
| } |
| headers.push({name: 'Foo3', value: 'Bar3'}); |
| headers.push({name: 'Foo4', value: 'Bar4'}); |
| return {responseHeaders: headers}; |
| }, {urls: ['*://*/set-cookie*']}, |
| ['blocking', 'responseHeaders', 'extraHeaders']); |
| |
| chrome.test.sendMessage('ready'); |
| )"); |
| |
| ExtensionTestMessageListener listener("ready", false); |
| ASSERT_TRUE(LoadExtension(test_dir.UnpackedPath())); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| base::HistogramTester tester; |
| ui_test_utils::NavigateToURL( |
| browser(), |
| embedded_test_server()->GetURL("/set-cookie?key1=val1&key2=val2")); |
| |
| // Changed histograms should record kUserAgent request header along with |
| // kSetCookie and kContentLength response headers. |
| tester.ExpectUniqueSample("Extensions.WebRequest.RequestHeaderChanged", |
| RequestHeaderType::kUserAgent, 1); |
| EXPECT_THAT( |
| tester.GetAllSamples("Extensions.WebRequest.ResponseHeaderChanged"), |
| ::testing::UnorderedElementsAre( |
| base::Bucket(static_cast<base::HistogramBase::Sample>( |
| ResponseHeaderType::kSetCookie), |
| 1), |
| base::Bucket(static_cast<base::HistogramBase::Sample>( |
| ResponseHeaderType::kContentLength), |
| 1))); |
| |
| // Added request header histogram should record kOther and kDNT. |
| EXPECT_THAT(tester.GetAllSamples("Extensions.WebRequest.RequestHeaderAdded"), |
| ::testing::UnorderedElementsAre( |
| base::Bucket(static_cast<base::HistogramBase::Sample>( |
| RequestHeaderType::kDnt), |
| 1), |
| base::Bucket(static_cast<base::HistogramBase::Sample>( |
| RequestHeaderType::kOther), |
| 2))); |
| |
| // Added response header histogram should record kOther. |
| tester.ExpectUniqueSample("Extensions.WebRequest.ResponseHeaderAdded", |
| ResponseHeaderType::kOther, 2); |
| |
| // Histograms for removed headers should record kNone. |
| tester.ExpectUniqueSample("Extensions.WebRequest.RequestHeaderRemoved", |
| RequestHeaderType::kNone, 1); |
| tester.ExpectUniqueSample("Extensions.WebRequest.ResponseHeaderRemoved", |
| ResponseHeaderType::kNone, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, RemoveHeaderUMAs) { |
| using RequestHeaderType = |
| extension_web_request_api_helpers::RequestHeaderType; |
| using ResponseHeaderType = |
| extension_web_request_api_helpers::ResponseHeaderType; |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Web Request UMA Test", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] |
| })"); |
| test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( |
| chrome.webRequest.onBeforeSendHeaders.addListener(function(details) { |
| var headers = details.requestHeaders; |
| for (var i = 0; i < headers.length; i++) { |
| if (headers[i].name.toLowerCase() == 'user-agent') { |
| headers.splice(i, 1); |
| break; |
| } |
| } |
| return {requestHeaders: headers}; |
| }, {urls: ['*://*/set-cookie*']}, |
| ['blocking', 'requestHeaders', 'extraHeaders']); |
| |
| chrome.webRequest.onHeadersReceived.addListener(function(details) { |
| var headers = details.responseHeaders; |
| for (var i = 0; i < headers.length; i++) { |
| if (headers[i].name.toLowerCase() == 'set-cookie') { |
| headers.splice(i, 1); |
| break; |
| } |
| } |
| return {responseHeaders: headers}; |
| }, {urls: ['*://*/set-cookie*']}, |
| ['blocking', 'responseHeaders', 'extraHeaders']); |
| |
| chrome.test.sendMessage('ready'); |
| )"); |
| |
| ExtensionTestMessageListener listener("ready", false); |
| ASSERT_TRUE(LoadExtension(test_dir.UnpackedPath())); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| base::HistogramTester tester; |
| ui_test_utils::NavigateToURL( |
| browser(), embedded_test_server()->GetURL("/set-cookie?Foo=Bar")); |
| |
| // Histograms for removed headers should record kUserAgent and kSetCookie. |
| tester.ExpectUniqueSample("Extensions.WebRequest.RequestHeaderRemoved", |
| RequestHeaderType::kUserAgent, 1); |
| tester.ExpectUniqueSample("Extensions.WebRequest.ResponseHeaderRemoved", |
| ResponseHeaderType::kSetCookie, 1); |
| |
| // Histograms for changed headers should record kNone. |
| tester.ExpectUniqueSample("Extensions.WebRequest.RequestHeaderChanged", |
| RequestHeaderType::kNone, 1); |
| tester.ExpectUniqueSample("Extensions.WebRequest.ResponseHeaderChanged", |
| ResponseHeaderType::kNone, 1); |
| |
| // Histograms for added headers should record kNone. |
| tester.ExpectUniqueSample("Extensions.WebRequest.RequestHeaderAdded", |
| RequestHeaderType::kNone, 1); |
| tester.ExpectUniqueSample("Extensions.WebRequest.ResponseHeaderAdded", |
| ResponseHeaderType::kNone, 1); |
| } |
| |
| // The parameter is for opt_extraInfoSpec passed to addEventListener. |
| // 'blocking' and 'requestHeaders' if it's false, and 'extraHeaders' in addition |
| // to them if it's true. |
| class ServiceWorkerWebRequestApiTest : public testing::WithParamInterface<bool>, |
| public ExtensionApiTest { |
| public: |
| // The options passed as opt_extraInfoSpec to addEventListener. |
| enum class ExtraInfoSpec { |
| // 'blocking', 'requestHeaders' |
| kDefault, |
| // kDefault + 'extraHeaders' |
| kExtraHeaders |
| }; |
| |
| static ExtraInfoSpec GetExtraInfoSpec() { |
| return GetParam() ? ExtraInfoSpec::kExtraHeaders : ExtraInfoSpec::kDefault; |
| } |
| |
| void InstallRequestHeaderModifyingExtension() { |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Web Request Header Modifying Extension", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] |
| })"); |
| |
| const char kBackgroundScript[] = R"( |
| chrome.webRequest.onBeforeSendHeaders.addListener(function(details) { |
| details.requestHeaders.push({name: 'foo', value: 'bar'}); |
| details.requestHeaders.push({ |
| name: 'frameId', |
| value: details.frameId.toString() |
| }); |
| details.requestHeaders.push({ |
| name: 'resourceType', |
| value: details.type |
| }); |
| return {requestHeaders: details.requestHeaders}; |
| }, |
| {urls: ['*://*/echoheader*']}, |
| [%s]); |
| |
| chrome.test.sendMessage('ready'); |
| )"; |
| std::string opt_extra_info_spec = "'blocking', 'requestHeaders'"; |
| if (GetExtraInfoSpec() == ExtraInfoSpec::kExtraHeaders) |
| opt_extra_info_spec += ", 'extraHeaders'"; |
| test_dir.WriteFile( |
| FILE_PATH_LITERAL("background.js"), |
| base::StringPrintf(kBackgroundScript, opt_extra_info_spec.c_str())); |
| |
| ExtensionTestMessageListener listener("ready", false); |
| ASSERT_TRUE(LoadExtension(test_dir.UnpackedPath())); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| } |
| |
| void RegisterServiceWorker(const std::string& worker_path, |
| const absl::optional<std::string>& scope) { |
| GURL url = embedded_test_server()->GetURL( |
| "/service_worker/create_service_worker.html"); |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| std::string script = content::JsReplace("register($1, $2);", worker_path, |
| scope ? *scope : std::string()); |
| EXPECT_EQ( |
| "DONE", |
| EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), script)); |
| } |
| |
| // Ensures requests made by the |worker_script_name| service worker can be |
| // intercepted by extensions. |
| void RunServiceWorkerFetchTest(const std::string& worker_script_name) { |
| embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Install the test extension. |
| InstallRequestHeaderModifyingExtension(); |
| |
| // Register a service worker and navigate to a page it controls. |
| RegisterServiceWorker(worker_script_name, absl::nullopt); |
| EXPECT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), embedded_test_server()->GetURL( |
| "/service_worker/fetch_from_page.html"))); |
| |
| // Make a fetch from the controlled page. Depending on the worker script, |
| // the fetch might go to the service worker and be re-issued, or might |
| // fallback to network, or skip the worker, etc. In any case, this function |
| // expects a network request to happen, and that the extension modify the |
| // headers of the request before it goes to network. Verify that it was able |
| // to inject a header of "foo=bar". |
| EXPECT_EQ("bar", |
| EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), |
| "fetch_from_page('/echoheader?foo');")); |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| ServiceWorkerWebRequestApiTest, |
| ::testing::Bool()); |
| |
| IN_PROC_BROWSER_TEST_P(ServiceWorkerWebRequestApiTest, ServiceWorkerFetch) { |
| RunServiceWorkerFetchTest("fetch_event_respond_with_fetch.js"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ServiceWorkerWebRequestApiTest, ServiceWorkerFallback) { |
| RunServiceWorkerFetchTest("fetch_event_pass_through.js"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ServiceWorkerWebRequestApiTest, |
| ServiceWorkerNoFetchHandler) { |
| RunServiceWorkerFetchTest("empty.js"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ServiceWorkerWebRequestApiTest, |
| ServiceWorkerFallbackAfterRedirect) { |
| embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| InstallRequestHeaderModifyingExtension(); |
| |
| RegisterServiceWorker("/fetch_event_passthrough.js", "/echoheader"); |
| |
| // Make sure the request is intercepted with no redirect. |
| ui_test_utils::NavigateToURL( |
| browser(), embedded_test_server()->GetURL("/echoheader?foo")); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| EXPECT_EQ("bar", EvalJs(web_contents, "document.body.textContent;")); |
| |
| // Make sure the request is intercepted with a redirect. |
| GURL redirect_url = embedded_test_server()->GetURL( |
| "/server-redirect?" + |
| embedded_test_server()->GetURL("/echoheader?foo").spec()); |
| ui_test_utils::NavigateToURL(browser(), redirect_url); |
| EXPECT_EQ("bar", EvalJs(web_contents, "document.body.textContent;")); |
| } |
| |
| // An extension should be able to modify the request header for service worker |
| // script by using WebRequest API. |
| IN_PROC_BROWSER_TEST_P(ServiceWorkerWebRequestApiTest, ServiceWorkerScript) { |
| // The extension to be used in this test adds foo=bar request header. |
| const char kScriptPath[] = "/echoheader_service_worker.js"; |
| // The request handler below will run on the EmbeddedTestServer's IO thread. |
| // Hence guard access to |served_service_worker_count| and |foo_header_value| |
| // using a lock. |
| base::Lock lock; |
| int served_service_worker_count = 0; |
| std::string foo_header_value; |
| |
| // Capture the value of a request header foo, which should be added if |
| // extension modifies the request header. |
| embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting( |
| [&](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| if (request.relative_url != kScriptPath) |
| return nullptr; |
| |
| base::AutoLock auto_lock(lock); |
| ++served_service_worker_count; |
| foo_header_value.clear(); |
| if (request.headers.find("foo") != request.headers.end()) |
| foo_header_value = request.headers.at("foo"); |
| |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("text/javascript"); |
| response->AddCustomHeader("Cache-Control", "no-cache"); |
| response->set_content("// empty"); |
| return response; |
| })); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| InstallRequestHeaderModifyingExtension(); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| GURL url = embedded_test_server()->GetURL( |
| "/service_worker/create_service_worker.html"); |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| // Register a service worker. The worker script should have "foo: bar" request |
| // header added by the extension. |
| std::string script = |
| content::JsReplace("register($1, './in-scope');", kScriptPath); |
| EXPECT_EQ("DONE", EvalJs(web_contents, script)); |
| { |
| base::AutoLock auto_lock(lock); |
| EXPECT_EQ(1, served_service_worker_count); |
| EXPECT_EQ("bar", foo_header_value); |
| } |
| |
| // Update the worker. The worker should have "foo: bar" request header in the |
| // request for update checking. |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| EXPECT_EQ("DONE", EvalJs(web_contents, "update('./in-scope');")); |
| { |
| base::AutoLock auto_lock(lock); |
| EXPECT_EQ(2, served_service_worker_count); |
| EXPECT_EQ("bar", foo_header_value); |
| } |
| } |
| |
| // An extension should be able to modify the request header for module service |
| // worker script by using WebRequest API. |
| IN_PROC_BROWSER_TEST_P(ServiceWorkerWebRequestApiTest, |
| ModuleServiceWorkerScript) { |
| // The extension to be used in this test adds foo=bar request header. |
| constexpr char kScriptPath[] = "/echoheader_service_worker.js"; |
| // The request handler below will run on the EmbeddedTestServer's IO thread. |
| // Hence guard access to |served_service_worker_count| and |foo_header_value| |
| // using a lock. |
| base::Lock lock; |
| int served_service_worker_count = 0; |
| std::string foo_header_value; |
| |
| // Capture the value of a request header foo, which should be added if |
| // extension modifies the request header. |
| embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting( |
| [&](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| if (request.relative_url != kScriptPath) |
| return nullptr; |
| |
| base::AutoLock auto_lock(lock); |
| ++served_service_worker_count; |
| foo_header_value.clear(); |
| if (base::Contains(request.headers, "foo")) |
| foo_header_value = request.headers.at("foo"); |
| |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("text/javascript"); |
| response->AddCustomHeader("Cache-Control", "no-cache"); |
| response->set_content("// empty"); |
| return response; |
| })); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| InstallRequestHeaderModifyingExtension(); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| GURL url = embedded_test_server()->GetURL( |
| "/service_worker/create_service_worker.html"); |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| // Register a service worker. `EvalJs` is blocked until the request handler |
| // serves the worker script. The worker script should have "foo: bar" request |
| // header added by the extension. |
| std::string script = |
| content::JsReplace("register($1, './in-scope', 'module');", kScriptPath); |
| EXPECT_EQ("DONE", EvalJs(web_contents, script)); |
| { |
| base::AutoLock auto_lock(lock); |
| EXPECT_EQ(1, served_service_worker_count); |
| EXPECT_EQ("bar", foo_header_value); |
| } |
| |
| // Update the worker. `EvalJs` is blocked until the request handler serves the |
| // worker script. The worker should have "foo: bar" request header in the |
| // request for update checking. |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| EXPECT_EQ("DONE", EvalJs(web_contents, "update('./in-scope');")); |
| { |
| base::AutoLock auto_lock(lock); |
| EXPECT_EQ(2, served_service_worker_count); |
| EXPECT_EQ("bar", foo_header_value); |
| } |
| } |
| |
| // An extension should be able to modify the request header for module service |
| // worker script with static import by using WebRequest API. |
| IN_PROC_BROWSER_TEST_P(ServiceWorkerWebRequestApiTest, |
| ModuleServiceWorkerScriptWithStaticImport) { |
| // The extension to be used in this test adds foo=bar request header. |
| constexpr char kScriptPath[] = "/static-import-worker.js"; |
| constexpr char kImportedScriptPath[] = "/echoheader_service_worker.js"; |
| // The request handler below will run on the EmbeddedTestServer's IO thread. |
| // Hence guard access to |served_service_worker_count| and |foo_header_value| |
| // using a lock. |
| base::Lock lock; |
| int served_service_worker_count = 0; |
| std::string foo_header_value; |
| |
| // Capture the value of a request header foo, which should be added if |
| // extension modifies the request header. |
| embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting( |
| [&](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| // Handle the top-level worker script. |
| if (request.relative_url == kScriptPath) { |
| base::AutoLock auto_lock(lock); |
| ++served_service_worker_count; |
| auto response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("text/javascript"); |
| response->AddCustomHeader("Cache-Control", "no-cache"); |
| response->set_content("import './echoheader_service_worker.js';"); |
| return response; |
| } |
| // Handle the static-imported script. |
| if (request.relative_url == kImportedScriptPath) { |
| base::AutoLock auto_lock(lock); |
| ++served_service_worker_count; |
| foo_header_value.clear(); |
| if (base::Contains(request.headers, "foo")) |
| foo_header_value = request.headers.at("foo"); |
| auto response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("text/javascript"); |
| response->AddCustomHeader("Cache-Control", "no-cache"); |
| response->set_content("// empty"); |
| return response; |
| } |
| return nullptr; |
| })); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| InstallRequestHeaderModifyingExtension(); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| GURL url = embedded_test_server()->GetURL( |
| "/service_worker/create_service_worker.html"); |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| // Register a service worker. The worker script should have "foo: bar" request |
| // header added by the extension. |
| std::string script = |
| content::JsReplace("register($1, './in-scope', 'module');", kScriptPath); |
| EXPECT_EQ("DONE", EvalJs(web_contents, script)); |
| { |
| base::AutoLock auto_lock(lock); |
| EXPECT_EQ(2, served_service_worker_count); |
| EXPECT_EQ("bar", foo_header_value); |
| } |
| |
| // Update the worker. The worker should have "foo: bar" request header in the |
| // request for update checking. |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| EXPECT_EQ("DONE", EvalJs(web_contents, "update('./in-scope');")); |
| { |
| base::AutoLock auto_lock(lock); |
| EXPECT_EQ(4, served_service_worker_count); |
| EXPECT_EQ("bar", foo_header_value); |
| } |
| } |
| |
| // Ensure that extensions can intercept service worker navigation preload |
| // requests. |
| IN_PROC_BROWSER_TEST_P(ServiceWorkerWebRequestApiTest, |
| ServiceWorkerNavigationPreload) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Install the test extension. |
| InstallRequestHeaderModifyingExtension(); |
| |
| // Register a service worker that uses navigation preload. |
| RegisterServiceWorker("/service_worker/navigation_preload_worker.js", |
| "/echoheader"); |
| |
| // Navigate to "/echoheader". The browser will detect that the service worker |
| // above is registered with this scope and has navigation preload enabled. |
| // So it will send the navigation preload request to network while at the same |
| // time starting up the service worker. The service worker will get the |
| // response for the navigation preload request, and respond with it to create |
| // the page. |
| GURL url = embedded_test_server()->GetURL( |
| "/echoheader?frameId&resourceType&service-worker-navigation-preload"); |
| ui_test_utils::NavigateToURL(browser(), url); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| |
| // Since the request was to "/echoheader", the response describes the request |
| // headers. |
| // |
| // The expectation is "0\nmain_frame\ntrue" because... |
| // |
| // 1) The extension is expected to add a "frameId: {id}" header, where {id} is |
| // details.frameId. This id is 0 for the main frame. |
| // 2) The extension is similarly expected to add a "resourceType: {type}" |
| // header, where {type} is details.type. |
| // 3) The browser adds a "service-worker-navigation-preload: true" header for |
| // navigation preload requests, so also sanity check that header to prove |
| // that this test is really testing the navigation preload request. |
| EXPECT_EQ("0\nmain_frame\ntrue", |
| EvalJs(web_contents, "document.body.textContent;")); |
| |
| // Repeat the test from an iframe, to test that details.frameId and resource |
| // type is populated correctly. |
| const char kAddIframe[] = R"( |
| (async () => { |
| const iframe = document.createElement('iframe'); |
| await new Promise(resolve => { |
| iframe.src = $1; |
| iframe.onload = resolve; |
| document.body.appendChild(iframe); |
| }); |
| const result = iframe.contentWindow.document.body.textContent; |
| |
| // Expect "{frameId}\nsub_frame\ntrue" where {frameId} is a positive |
| // integer. |
| const split = result.split('\n'); |
| if (parseInt(split[0]) > 0 && split[1] == 'sub_frame' && |
| split[2] == 'true') { |
| return 'ok'; |
| } |
| return 'bad result: ' + result; |
| })(); |
| )"; |
| |
| EXPECT_EQ("ok", EvalJs(web_contents, content::JsReplace(kAddIframe, url))); |
| } |
| |
| // Ensure we don't strip off initiator incorrectly in web request events when |
| // both the normal and incognito contexts are active. Regression test for |
| // crbug.com/934398. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| Initiator_SpanningIncognito) { |
| embedded_test_server()->ServeFilesFromSourceDirectory("chrome/test/data"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| const bool will_reply = false; |
| ExtensionTestMessageListener ready_listener("ready", will_reply); |
| const Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("webrequest") |
| .AppendASCII("initiator_spanning")); |
| ASSERT_TRUE(extension); |
| const std::string extension_id = extension->id(); |
| EXPECT_TRUE(ready_listener.WaitUntilSatisfied()); |
| |
| Browser* incognito_browser = CreateIncognitoBrowser(profile()); |
| ASSERT_TRUE(incognito_browser); |
| |
| // iframe.html loads an iframe to title1.html. The extension listens for the |
| // request to title1.html and records the request initiator. |
| GURL url = embedded_test_server()->GetURL("google.com", "/iframe.html"); |
| const std::string url_origin = url::Origin::Create(url).Serialize(); |
| |
| const char* kScript = R"( |
| domAutomationController.send(JSON.stringify(window.initiators)); |
| window.initiators = []; |
| )"; |
| |
| ui_test_utils::NavigateToURL(browser(), url); |
| EXPECT_EQ(base::StringPrintf("[\"%s\"]", url_origin.c_str()), |
| ExecuteScriptInBackgroundPage(extension_id, kScript)); |
| |
| // The extension isn't enabled in incognito. Se we shouldn't intercept the |
| // request to |url|. |
| ui_test_utils::NavigateToURL(incognito_browser, url); |
| EXPECT_EQ("[]", ExecuteScriptInBackgroundPage(extension_id, kScript)); |
| |
| // Now enable the extension in incognito. |
| ready_listener.Reset(); |
| extensions::util::SetIsIncognitoEnabled(extension_id, profile(), |
| true /* enabled */); |
| EXPECT_TRUE(ready_listener.WaitUntilSatisfied()); |
| |
| // Now we should be able to intercept the incognito request. |
| ui_test_utils::NavigateToURL(incognito_browser, url); |
| EXPECT_EQ(base::StringPrintf("[\"%s\"]", url_origin.c_str()), |
| ExecuteScriptInBackgroundPage(extension_id, kScript)); |
| } |
| |
| // Ensure we don't strip off initiator incorrectly in web request events when |
| // both the normal and incognito contexts are active. Regression test for |
| // crbug.com/934398. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, Initiator_SplitIncognito) { |
| embedded_test_server()->ServeFilesFromSourceDirectory("chrome/test/data"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| const bool will_reply = false; |
| ExtensionTestMessageListener ready_listener("ready", will_reply); |
| ExtensionTestMessageListener incognito_ready_listener("incognito ready", |
| will_reply); |
| const Extension* extension = LoadExtension( |
| test_data_dir_.AppendASCII("webrequest").AppendASCII("initiator_split"), |
| {.allow_in_incognito = true}); |
| ASSERT_TRUE(extension); |
| EXPECT_TRUE(ready_listener.WaitUntilSatisfied()); |
| |
| Browser* incognito_browser = CreateIncognitoBrowser(profile()); |
| ASSERT_TRUE(incognito_browser); |
| EXPECT_TRUE(incognito_ready_listener.WaitUntilSatisfied()); |
| |
| // iframe.html loads an iframe to title1.html. The extension listens for the |
| // request to title1.html and records the request initiator. |
| GURL url_normal = |
| embedded_test_server()->GetURL("google.com", "/iframe.html"); |
| GURL url_incognito = |
| embedded_test_server()->GetURL("example.com", "/iframe.html"); |
| const std::string origin_normal = url::Origin::Create(url_normal).Serialize(); |
| const std::string origin_incognito = |
| url::Origin::Create(url_incognito).Serialize(); |
| |
| const char* kScript = R"( |
| domAutomationController.send(JSON.stringify(window.initiators)); |
| window.initiators = []; |
| )"; |
| |
| ui_test_utils::NavigateToURL(browser(), url_normal); |
| ui_test_utils::NavigateToURL(incognito_browser, url_incognito); |
| EXPECT_EQ(base::StringPrintf("[\"%s\"]", origin_normal.c_str()), |
| browsertest_util::ExecuteScriptInBackgroundPage( |
| profile(), extension->id(), kScript)); |
| EXPECT_EQ(base::StringPrintf("[\"%s\"]", origin_incognito.c_str()), |
| browsertest_util::ExecuteScriptInBackgroundPage( |
| profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true), |
| extension->id(), kScript)); |
| } |
| |
| // A request handler that sets the Access-Control-Allow-Origin header. |
| std::unique_ptr<net::test_server::HttpResponse> HandleXHRRequest( |
| const net::test_server::HttpRequest& request) { |
| auto http_response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| http_response->set_code(net::HTTP_OK); |
| http_response->AddCustomHeader("Access-Control-Allow-Origin", "*"); |
| return http_response; |
| } |
| |
| // Regression test for http://crbug.com/971206. The responseHeaders should still |
| // be present in onBeforeRedirect even for HSTS upgrade. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| ExtraHeadersWithHSTSUpgrade) { |
| net::EmbeddedTestServer https_test_server( |
| net::EmbeddedTestServer::TYPE_HTTPS); |
| https_test_server.RegisterRequestHandler( |
| base::BindRepeating(&HandleXHRRequest)); |
| ASSERT_TRUE(https_test_server.Start()); |
| |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Web Request HSTS Test", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] |
| })"); |
| test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( |
| chrome.webRequest.onBeforeRedirect.addListener(function(details) { |
| window.headerCount = details.responseHeaders.length; |
| }, {urls: ['<all_urls>']}, |
| ['responseHeaders', 'extraHeaders']); |
| |
| chrome.test.sendMessage('ready'); |
| )"); |
| |
| ExtensionTestMessageListener listener("ready", false); |
| const Extension* extension = LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| ui_test_utils::NavigateToURL(browser(), https_test_server.GetURL("/echo")); |
| |
| content::StoragePartition* partition = |
| profile()->GetDefaultStoragePartition(); |
| base::RunLoop run_loop; |
| partition->GetNetworkContext()->AddHSTS( |
| https_test_server.host_port_pair().host(), |
| base::Time::Now() + base::TimeDelta::FromDays(100), true, |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| PerformXhrInFrame( |
| browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame(), |
| https_test_server.host_port_pair().host(), https_test_server.port(), |
| "echo"); |
| EXPECT_GT( |
| GetCountFromBackgroundPage(extension, profile(), "window.headerCount"), |
| 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, AppCacheRequests) { |
| std::string origin = "http://127.0.0.1:8080"; |
| std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor = |
| content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin( |
| "content/test/data", GURL(origin)); |
| GURL main_url(origin + "/appcache/simple_page_with_manifest.html"); |
| |
| std::u16string expected_title = u"AppCache updated"; |
| |
| // Load the main page first to make sure it is cached. After the first |
| // navigation, load the extension, then navigate again. |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url)); |
| content::TitleWatcher title_watcher( |
| browser()->tab_strip_model()->GetActiveWebContents(), expected_title); |
| EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); |
| |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Web Request Appcache Test", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest"] |
| })"); |
| test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( |
| window.numMainResourceRequests = 0; |
| window.numSubresourceRequests = 0; |
| chrome.webRequest.onBeforeRequest.addListener(function(details) { |
| if (details.url.includes('logo.png')) |
| window.numSubresourceRequests++; |
| else if (details.url.includes('simple_page_with_manifest.html')) |
| window.numMainResourceRequests++; |
| }, {urls: ['<all_urls>']}, []); |
| |
| chrome.test.sendMessage('ready'); |
| )"); |
| |
| ExtensionTestMessageListener listener("ready", false); |
| const Extension* extension = LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| // This navigation should go through appcache. |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url)); |
| |
| EXPECT_EQ(GetCountFromBackgroundPage(extension, profile(), |
| "window.numSubresourceRequests"), |
| 1); |
| EXPECT_EQ(GetCountFromBackgroundPage(extension, profile(), |
| "window.numMainResourceRequests"), |
| 1); |
| } |
| |
| // Ensure that when an extension blocks a main-frame request, the resultant |
| // error page attributes this to an extension. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| ErrorPageForBlockedMainFrameNavigation) { |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest( |
| "webrequest", {.page_url = "test_simple_cancel_navigation.html"})) |
| << message_; |
| |
| std::string body; |
| WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_TRUE(content::ExecuteScriptAndExtractString( |
| tab, "window.domAutomationController.send(document.body.textContent)", |
| &body)); |
| |
| EXPECT_TRUE( |
| base::Contains(body, "This page has been blocked by an extension")); |
| EXPECT_TRUE(base::Contains(body, "Try disabling your extensions.")); |
| } |
| |
| // Regression test for http://crbug.com/996940. Requests that redirected to an |
| // appcache handled URL could have request ID collisions. |
| // This test is flaky on Linux: https://crbug.com/1094834. |
| // TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is |
| // complete. |
| #if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) |
| #define MAYBE_RedirectToAppCacheRequest DISABLED_RedirectToAppCacheRequest |
| #else |
| #define MAYBE_RedirectToAppCacheRequest RedirectToAppCacheRequest |
| #endif |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, |
| MAYBE_RedirectToAppCacheRequest) { |
| // Use the embedded test server to support server-redirect, but serve |
| // appcache from a fixed port using the url loader interceptor below |
| // so that the appcache origin trial works. |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| |
| std::string origin = "http://127.0.0.1:8080"; |
| auto interceptor = |
| content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin( |
| "content/test/data", GURL(origin)); |
| GURL main_url(origin + "/appcache/simple_page_with_manifest.html"); |
| |
| std::u16string expected_title = u"AppCache updated"; |
| |
| // Load the main page first to make sure it is cached. After the first |
| // navigation, load the extension, then navigate again. |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url)); |
| content::TitleWatcher title_watcher( |
| browser()->tab_strip_model()->GetActiveWebContents(), expected_title); |
| EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); |
| |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Web Request Appcache Redirect Test", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest"] |
| })"); |
| test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( |
| window.numErrors = 0; |
| chrome.webRequest.onErrorOccurred.addListener(function(details) { |
| window.numErrors++; |
| }, {urls: ['<all_urls>']}); |
| |
| chrome.test.sendMessage('ready'); |
| )"); |
| |
| ExtensionTestMessageListener listener("ready", false); |
| const Extension* extension = LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| // This navigation should go through appcache. |
| EXPECT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| embedded_test_server()->GetURL("/server-redirect?" + main_url.spec()))); |
| |
| EXPECT_EQ( |
| GetCountFromBackgroundPage(extension, profile(), "window.numErrors"), 0); |
| } |
| |
| // Regression test for https://crbug.com/1019614. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, HSTSUpgradeAfterRedirect) { |
| net::EmbeddedTestServer https_test_server( |
| net::EmbeddedTestServer::TYPE_HTTPS); |
| https_test_server.ServeFilesFromDirectory( |
| test_data_dir_.AppendASCII("webrequest")); |
| net::test_server::RegisterDefaultHandlers(&https_test_server); |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(https_test_server.Start()); |
| |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Web Request HSTS Test", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] |
| })"); |
| test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( |
| chrome.webRequest.onBeforeRedirect.addListener(function(details) { |
| }, {urls: ['<all_urls>']}, |
| ['responseHeaders', 'extraHeaders']); |
| |
| chrome.test.sendMessage('ready'); |
| )"); |
| |
| ExtensionTestMessageListener listener("ready", false); |
| const Extension* extension = LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| content::StoragePartition* partition = |
| profile()->GetDefaultStoragePartition(); |
| base::RunLoop run_loop; |
| partition->GetNetworkContext()->AddHSTS( |
| "hsts.com", base::Time::Now() + base::TimeDelta::FromDays(100), true, |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| GURL final_url = https_test_server.GetURL("hsts.com", "/echo"); |
| GURL::Replacements replace_scheme; |
| replace_scheme.SetSchemeStr("http"); |
| GURL http_url = final_url.ReplaceComponents(replace_scheme); |
| std::string redirect_path = |
| base::StringPrintf("/server-redirect?%s", http_url.spec().c_str()); |
| GURL redirect_url = embedded_test_server()->GetURL("test.com", redirect_path); |
| ui_test_utils::NavigateToURL(browser(), redirect_url); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| EXPECT_EQ(final_url, web_contents->GetLastCommittedURL()); |
| } |
| |
| class SubresourceWebBundlesWebRequestApiTest |
| : public testing::WithParamInterface<bool>, |
| public ExtensionApiTest { |
| public: |
| void SetUp() override { |
| feature_list_.InitWithFeatures({features::kSubresourceWebBundles}, {}); |
| ExtensionApiTest::SetUp(); |
| } |
| |
| protected: |
| // Whether 'extraHeaders' is set in opt_extraInfoSpec of addEventListener. |
| enum class ExtraInfoSpec { |
| // No 'extraHeaders' |
| kDefault, |
| // with 'extraHeaders' |
| kExtraHeaders |
| }; |
| |
| static ExtraInfoSpec GetExtraInfoSpec() { |
| return GetParam() ? ExtraInfoSpec::kExtraHeaders : ExtraInfoSpec::kDefault; |
| } |
| |
| bool TryLoadScript(const std::string& script_src) { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| bool success = false; |
| std::string script = base::StringPrintf(R"( |
| (() => { |
| const script = document.createElement('script'); |
| script.addEventListener('load', () => { |
| window.domAutomationController.send(true); |
| }); |
| script.addEventListener('error', () => { |
| window.domAutomationController.send(false); |
| }); |
| script.src = '%s'; |
| document.body.appendChild(script); |
| })(); |
| )", |
| script_src.c_str()); |
| EXPECT_TRUE(ExecuteScriptAndExtractBool(web_contents->GetMainFrame(), |
| script, &success)); |
| return success; |
| } |
| |
| // Registers a request handler for static content. |
| void RegisterRequestHandler(const std::string& relative_url, |
| const std::string& content_type, |
| const std::string& content) { |
| embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting( |
| [relative_url, content_type, |
| content](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| if (request.relative_url == relative_url) { |
| auto response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type(content_type); |
| response->set_content(content); |
| return std::move(response); |
| } |
| return nullptr; |
| })); |
| } |
| |
| // Registers a request handler for web bundle. This method takes a pointer of |
| // the content of the web bundle, because we can't create the content of the |
| // web bundle before starting the server since we need to know the port number |
| // of the test server due to the same-origin restriction (the origin of |
| // subresource which is written in the web bundle must be same as the origin |
| // of the web bundle), and we can't call |
| // EmbeddedTestServer::RegisterRequestHandler() after starting the server. |
| void RegisterWebBundleRequestHandler(const std::string& relative_url, |
| const std::string* web_bundle) { |
| embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting( |
| [relative_url, web_bundle](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| if (request.relative_url == relative_url) { |
| auto response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("application/webbundle"); |
| response->AddCustomHeader("X-Content-Type-Options", "nosniff"); |
| response->set_content(*web_bundle); |
| return std::move(response); |
| } |
| return nullptr; |
| })); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SubresourceWebBundlesWebRequestApiTest, |
| ::testing::Bool()); |
| |
| // Ensure web request listeners can intercept requests for a web bundle and its |
| // subresources. |
| IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, |
| RequestIntercepted) { |
| const std::string urn_uuid_script_url = |
| "urn:uuid:6a059ece-62f4-4c79-a9e2-1c641cbdbaaf"; |
| // Create an extension that intercepts requests. |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Web Request Subresource Web Bundles Test", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest"] |
| })"); |
| |
| std::string opt_extra_info_spec = ""; |
| if (GetExtraInfoSpec() == ExtraInfoSpec::kExtraHeaders) |
| opt_extra_info_spec += "'extraHeaders'"; |
| test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), |
| base::StringPrintf(R"( |
| window.numMainResourceRequests = 0; |
| window.numWebBundleRequests = 0; |
| window.numScriptRequests = 0; |
| window.numUrnUUIDScriptRequests = 0; |
| chrome.webRequest.onBeforeRequest.addListener(function(details) { |
| if (details.url.includes('test.html')) |
| window.numMainResourceRequests++; |
| else if (details.url.includes('web_bundle.wbn')) |
| window.numWebBundleRequests++; |
| else if (details.url.includes('test.js')) |
| window.numScriptRequests++; |
| else if (details.url === '%s') |
| window.numUrnUUIDScriptRequests++; |
| }, {urls: ['<all_urls>']}, [%s]); |
| |
| chrome.test.sendMessage('ready'); |
| )", |
| urn_uuid_script_url.c_str(), |
| opt_extra_info_spec.c_str())); |
| const Extension* extension = nullptr; |
| { |
| ExtensionTestMessageListener listener("ready", false); |
| extension = LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| } |
| |
| const std::string page_html = base::StringPrintf(R"( |
| <title>Loaded</title> |
| <body> |
| <script> |
| (() => { |
| const wbn_url = |
| new URL('./web_bundle.wbn', location.href).toString(); |
| const script_url = new URL('./test.js', location.href).toString(); |
| const urn_uuid_script_url = '%s'; |
| const link = document.createElement('link'); |
| link.rel = 'webbundle'; |
| link.href = wbn_url; |
| link.resources = script_url + ' ' + urn_uuid_script_url; |
| document.body.appendChild(link); |
| const script = document.createElement('script'); |
| script.src = script_url; |
| script.addEventListener('load', () => { |
| const urn_uuid_script = document.createElement('script'); |
| urn_uuid_script.src = urn_uuid_script_url; |
| document.body.appendChild(urn_uuid_script); |
| }); |
| document.body.appendChild(script); |
| })(); |
| </script> |
| </body> |
| )", |
| urn_uuid_script_url.c_str()); |
| std::string web_bundle; |
| RegisterWebBundleRequestHandler("/web_bundle.wbn", &web_bundle); |
| RegisterRequestHandler("/test.html", "text/html", page_html); |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| |
| // Create a web bundle. |
| std::string script_url_str = |
| embedded_test_server()->GetURL("/test.js").spec(); |
| // Currently the web bundle format requires a valid GURL for the fallback URL |
| // of a web bundle. So we use |script_url_str| for the fallback URL. |
| // TODO(crbug.com/966753): Stop using |script_url_str| when |
| // https://github.com/WICG/webpackage/issues/590 is resolved. |
| web_package::test::WebBundleBuilder builder(script_url_str, ""); |
| auto script_location = builder.AddResponse( |
| {{":status", "200"}, {"content-type", "application/javascript"}}, |
| "document.title = 'ScriptDone';"); |
| auto urn_uuid_script_location = builder.AddResponse( |
| {{":status", "200"}, {"content-type", "application/javascript"}}, |
| "document.title += ':UrnUUIDScriptDone';"); |
| builder.AddIndexEntry(script_url_str, "", {script_location}); |
| builder.AddIndexEntry(urn_uuid_script_url, "", {urn_uuid_script_location}); |
| std::vector<uint8_t> bundle = builder.CreateBundle(); |
| web_bundle = std::string(bundle.begin(), bundle.end()); |
| |
| GURL page_url = embedded_test_server()->GetURL("/test.html"); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| std::u16string expected_title = u"ScriptDone:UrnUUIDScriptDone"; |
| content::TitleWatcher title_watcher(web_contents, expected_title); |
| ui_test_utils::NavigateToURL(browser(), page_url); |
| EXPECT_EQ(page_url, web_contents->GetLastCommittedURL()); |
| // Check that the scripts in the web bundle are correctly loaded even when the |
| // extension intercepted the request. |
| EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); |
| |
| EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), |
| "window.numMainResourceRequests")); |
| EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), |
| "window.numWebBundleRequests")); |
| EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), |
| "window.numScriptRequests")); |
| EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), |
| "window.numUrnUUIDScriptRequests")); |
| } |
| |
| // Ensure web request API can block the requests for the subresources inside the |
| // web bundle. |
| IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, |
| RequestCanceled) { |
| // Create an extension that blocks a bundle subresource request. |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Web Request Subresource Web Bundles Test", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] |
| })"); |
| std::string pass_urn_uuid_js_url = |
| "urn:uuid:bf50ad1f-899e-42ca-95ac-ca592aa2ecb5"; |
| std::string cancel_urn_uuid_js_url = |
| "urn:uuid:9cc02e52-05b6-466a-8c0e-f22ee86825a8"; |
| std::string opt_extra_info_spec = "'blocking'"; |
| if (GetExtraInfoSpec() == ExtraInfoSpec::kExtraHeaders) |
| opt_extra_info_spec += ", 'extraHeaders'"; |
| test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), |
| base::StringPrintf(R"( |
| window.numPassScriptRequests = 0; |
| window.numCancelScriptRequests = 0; |
| window.numUrnUUIDPassScriptRequests = 0; |
| window.numUrnUUIDCancelScriptRequests = 0; |
| chrome.webRequest.onBeforeRequest.addListener(function(details) { |
| if (details.url.includes('pass.js')) { |
| window.numPassScriptRequests++; |
| return {cancel: false}; |
| } else if (details.url.includes('cancel.js')) { |
| window.numCancelScriptRequests++; |
| return {cancel: true}; |
| } else if (details.url === '%s') { |
| window.numUrnUUIDPassScriptRequests++; |
| return {cancel: false}; |
| } else if (details.url === '%s') { |
| window.numUrnUUIDCancelScriptRequests++; |
| return {cancel: true}; |
| } |
| }, {urls: ['<all_urls>']}, [%s]); |
| |
| chrome.test.sendMessage('ready'); |
| )", |
| pass_urn_uuid_js_url.c_str(), |
| cancel_urn_uuid_js_url.c_str(), |
| opt_extra_info_spec.c_str())); |
| const Extension* extension = nullptr; |
| { |
| ExtensionTestMessageListener listener("ready", false); |
| extension = LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| } |
| std::string page_html = base::StringPrintf(R"( |
| <title>Loaded</title> |
| <body> |
| <script> |
| (() => { |
| const wbn_url = new URL('./web_bundle.wbn', location.href).toString(); |
| const pass_js_url = new URL('./pass.js', location.href).toString(); |
| const cancel_js_url = |
| new URL('./cancel.js', location.href).toString(); |
| const pass_urn_uuid_js_url = '%s'; |
| const cancel_urn_uuid_js_url = '%s'; |
| const link = document.createElement('link'); |
| link.rel = 'webbundle'; |
| link.href = wbn_url; |
| link.resources.add(pass_js_url, |
| cancel_js_url, |
| pass_urn_uuid_js_url, |
| cancel_urn_uuid_js_url); |
| document.body.appendChild(link); |
| })(); |
| </script> |
| </body> |
| )", |
| pass_urn_uuid_js_url.c_str(), |
| cancel_urn_uuid_js_url.c_str()); |
| |
| std::string web_bundle; |
| RegisterWebBundleRequestHandler("/web_bundle.wbn", &web_bundle); |
| RegisterRequestHandler("/test.html", "text/html", page_html); |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| |
| // Create a web bundle. |
| std::string pass_js_url_str = |
| embedded_test_server()->GetURL("/pass.js").spec(); |
| std::string cancel_js_url_str = |
| embedded_test_server()->GetURL("/cancel.js").spec(); |
| web_package::test::WebBundleBuilder builder(pass_js_url_str, ""); |
| auto pass_js_location = builder.AddResponse( |
| {{":status", "200"}, {"content-type", "application/javascript"}}, |
| "document.title = 'script loaded';"); |
| auto cancel_js_location = builder.AddResponse( |
| {{":status", "200"}, {"content-type", "application/javascript"}}, ""); |
| auto urn_uuid_pass_js_location = builder.AddResponse( |
| {{":status", "200"}, {"content-type", "application/javascript"}}, |
| "document.title = 'urn uuid script loaded';"); |
| auto urn_uuid_cancel_js_location = builder.AddResponse( |
| {{":status", "200"}, {"content-type", "application/javascript"}}, ""); |
| builder.AddIndexEntry(pass_js_url_str, "", {pass_js_location}); |
| builder.AddIndexEntry(cancel_js_url_str, "", {cancel_js_location}); |
| builder.AddIndexEntry(pass_urn_uuid_js_url, "", {urn_uuid_pass_js_location}); |
| builder.AddIndexEntry(cancel_urn_uuid_js_url, "", |
| {urn_uuid_cancel_js_location}); |
| std::vector<uint8_t> bundle = builder.CreateBundle(); |
| web_bundle = std::string(bundle.begin(), bundle.end()); |
| |
| GURL page_url = embedded_test_server()->GetURL("/test.html"); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ui_test_utils::NavigateToURL(browser(), page_url); |
| EXPECT_EQ(page_url, web_contents->GetLastCommittedURL()); |
| |
| std::u16string expected_title1 = u"script loaded"; |
| content::TitleWatcher title_watcher1(web_contents, expected_title1); |
| EXPECT_TRUE(TryLoadScript("pass.js")); |
| // Check that the script in the web bundle is correctly loaded even when the |
| // extension with blocking handler intercepted the request. |
| EXPECT_EQ(expected_title1, title_watcher1.WaitAndGetTitle()); |
| |
| EXPECT_FALSE(TryLoadScript("cancel.js")); |
| |
| std::u16string expected_title2 = u"urn uuid script loaded"; |
| content::TitleWatcher title_watcher2(web_contents, expected_title2); |
| EXPECT_TRUE(TryLoadScript(pass_urn_uuid_js_url)); |
| // Check that the urn UUID script in the web bundle is correctly loaded even |
| // when the extension with blocking handler intercepted the request. |
| EXPECT_EQ(expected_title2, title_watcher2.WaitAndGetTitle()); |
| |
| EXPECT_FALSE(TryLoadScript(cancel_urn_uuid_js_url)); |
| |
| EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), |
| "window.numPassScriptRequests")); |
| EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), |
| "window.numCancelScriptRequests")); |
| EXPECT_EQ(1, |
| GetCountFromBackgroundPage(extension, profile(), |
| "window.numUrnUUIDPassScriptRequests")); |
| EXPECT_EQ( |
| 1, GetCountFromBackgroundPage(extension, profile(), |
| "window.numUrnUUIDCancelScriptRequests")); |
| } |
| |
| // Ensure web request API can change the headers of the subresources inside the |
| // web bundle. |
| IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, ChangeHeader) { |
| // Create an extension that changes the header of the subresources inside the |
| // web bundle. |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Web Request Subresource Web Bundles Test", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] |
| })"); |
| std::string opt_extra_info_spec = "'blocking', 'responseHeaders'"; |
| if (GetExtraInfoSpec() == ExtraInfoSpec::kExtraHeaders) |
| opt_extra_info_spec += ", 'extraHeaders'"; |
| test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), |
| base::StringPrintf(R"( |
| chrome.webRequest.onHeadersReceived.addListener(function(details) { |
| if (!details.url.includes('target.txt')) { |
| return {cancel: false}; |
| } |
| const headers = details.responseHeaders; |
| for (let i = 0; i < headers.length; i++) { |
| if (headers[i].name.toLowerCase() == 'foo') { |
| headers[i].value += '-changed'; |
| } |
| } |
| headers.push({name: 'foo', value:'inserted'}); |
| return {responseHeaders: headers}; |
| }, {urls: ['<all_urls>']}, [%s]); |
| |
| chrome.test.sendMessage('ready'); |
| )", |
| opt_extra_info_spec.c_str())); |
| const Extension* extension = nullptr; |
| { |
| ExtensionTestMessageListener listener("ready", false); |
| extension = LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| } |
| const char kPageHtml[] = R"( |
| <title>Loaded</title> |
| <body> |
| <script> |
| (async () => { |
| const wbn_url = new URL('./web_bundle.wbn', location.href).toString(); |
| const target_url = new URL('./target.txt', location.href).toString(); |
| const link = document.createElement('link'); |
| link.rel = 'webbundle'; |
| link.href = wbn_url; |
| link.resources = target_url; |
| document.body.appendChild(link); |
| const res = await fetch(target_url); |
| document.title = res.status + ':' + res.headers.get('foo'); |
| })(); |
| </script> |
| </body> |
| )"; |
| |
| std::string web_bundle; |
| RegisterWebBundleRequestHandler("/web_bundle.wbn", &web_bundle); |
| RegisterRequestHandler("/test.html", "text/html", kPageHtml); |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| |
| // Create a web bundle. |
| std::string target_txt_url_str = |
| embedded_test_server()->GetURL("/target.txt").spec(); |
| web_package::test::WebBundleBuilder builder(target_txt_url_str, ""); |
| auto target_txt_location = builder.AddResponse( |
| {{":status", "200"}, {"content-type", "text/plain"}, {"foo", "bar"}}, |
| "Hello world"); |
| builder.AddIndexEntry(target_txt_url_str, "", {target_txt_location}); |
| std::vector<uint8_t> bundle = builder.CreateBundle(); |
| web_bundle = std::string(bundle.begin(), bundle.end()); |
| |
| GURL page_url = embedded_test_server()->GetURL("/test.html"); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| |
| std::u16string expected_title = u"200:bar-changed, inserted"; |
| content::TitleWatcher title_watcher( |
| browser()->tab_strip_model()->GetActiveWebContents(), expected_title); |
| |
| ui_test_utils::NavigateToURL(browser(), page_url); |
| |
| EXPECT_EQ(page_url, web_contents->GetLastCommittedURL()); |
| EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); |
| } |
| |
| // Ensure web request API can change the response headers of the urn uuid URL |
| // subresources inside the web bundle. |
| // Note: Currently we can't directly check the response headers of urn uuid URL |
| // resources, because CORS requests are not allowed for urn uuid URLs. So we |
| // change the content-type header of a JavaScript file and monitor the error |
| // handler. Subresources in web bundles should be treated as if an artificial |
| // `X-Content-Type-Options: nosniff` header field is set. So when the |
| // content-type is not suitable for script execution, the script should fail to |
| // load. |
| IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, |
| ChangeHeaderUrnUuidUrlResource) { |
| std::string uun_uuid_url = "urn:uuid:71940cde-d20b-4fb5-b920-38a58a92c516"; |
| // Create an extension that changes the header of the subresources inside the |
| // web bundle. |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Web Request Subresource Web Bundles Test", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] |
| })"); |
| std::string opt_extra_info_spec = "'blocking', 'responseHeaders'"; |
| if (GetExtraInfoSpec() == ExtraInfoSpec::kExtraHeaders) |
| opt_extra_info_spec += ", 'extraHeaders'"; |
| test_dir.WriteFile( |
| FILE_PATH_LITERAL("background.js"), |
| base::StringPrintf(R"( |
| chrome.webRequest.onHeadersReceived.addListener(function(details) { |
| if (details.url != '%s') { |
| return; |
| } |
| const headers = details.responseHeaders; |
| for (let i = 0; i < headers.length; i++) { |
| if (headers[i].name.toLowerCase() == 'content-type') { |
| headers[i].value = 'unknown/type'; |
| } |
| } |
| return {responseHeaders: headers}; |
| }, {urls: ['<all_urls>']}, [%s]); |
| |
| chrome.test.sendMessage('ready'); |
| )", |
| uun_uuid_url.c_str(), opt_extra_info_spec.c_str())); |
| const Extension* extension = nullptr; |
| { |
| ExtensionTestMessageListener listener("ready", false); |
| extension = LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| } |
| std::string page_html = base::StringPrintf(R"( |
| <title>Loaded</title> |
| <body> |
| <script> |
| (async () => { |
| const wbn_url = new URL('./web_bundle.wbn', location.href).toString(); |
| const urn_uuid_url = '%s'; |
| const link = document.createElement('link'); |
| link.rel = 'webbundle'; |
| link.href = wbn_url; |
| link.resources = urn_uuid_url; |
| document.body.appendChild(link); |
| const script = document.createElement('script'); |
| script.src = urn_uuid_url; |
| script.addEventListener('error', () => { |
| document.title = 'failed to load'; |
| }); |
| document.body.appendChild(script); |
| })(); |
| </script> |
| </body> |
| )", |
| uun_uuid_url.c_str()); |
| |
| std::string web_bundle; |
| RegisterWebBundleRequestHandler("/web_bundle.wbn", &web_bundle); |
| RegisterRequestHandler("/test.html", "text/html", page_html); |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| |
| // Create a web bundle. |
| web_package::test::WebBundleBuilder builder(uun_uuid_url, ""); |
| auto uun_uuid_script = builder.AddResponse( |
| {{":status", "200"}, {"content-type", "application/javascript"}}, |
| "document.title = 'loaded';"); |
| builder.AddIndexEntry(uun_uuid_url, "", {uun_uuid_script}); |
| std::vector<uint8_t> bundle = builder.CreateBundle(); |
| web_bundle = std::string(bundle.begin(), bundle.end()); |
| |
| GURL page_url = embedded_test_server()->GetURL("/test.html"); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| |
| std::u16string expected_title = u"failed to load"; |
| content::TitleWatcher title_watcher( |
| browser()->tab_strip_model()->GetActiveWebContents(), expected_title); |
| |
| ui_test_utils::NavigateToURL(browser(), page_url); |
| |
| EXPECT_EQ(page_url, web_contents->GetLastCommittedURL()); |
| EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); |
| } |
| |
| // Ensure web request API can redirect the requests for the subresources inside |
| // the web bundle. |
| IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, |
| RequestRedirected) { |
| // Create an extension that redirects a bundle subresource request. |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Web Request Subresource Web Bundles Test", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] |
| })"); |
| std::string opt_extra_info_spec = "'blocking'"; |
| if (GetExtraInfoSpec() == ExtraInfoSpec::kExtraHeaders) |
| opt_extra_info_spec += ", 'extraHeaders'"; |
| test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), |
| base::StringPrintf(R"( |
| chrome.webRequest.onBeforeRequest.addListener(function(details) { |
| if (details.url.includes('redirect.js')) { |
| const redirectUrl = |
| details.url.replace('redirect.js', 'redirected.js'); |
| return {redirectUrl: redirectUrl}; |
| } else if (details.url.includes('redirect_to_unlisted.js')) { |
| const redirectUrl = |
| details.url.replace('redirect_to_unlisted.js', |
| 'redirected_to_unlisted.js'); |
| return {redirectUrl: redirectUrl}; |
| } else if (details.url.includes('redirect_to_server.js')) { |
| const redirectUrl = |
| details.url.replace('redirect_to_server.js', |
| 'redirected_to_server.js'); |
| return {redirectUrl: redirectUrl}; |
| } |
| }, {urls: ['<all_urls>']}, [%s]); |
| |
| chrome.test.sendMessage('ready'); |
| )", |
| opt_extra_info_spec.c_str())); |
| const Extension* extension = nullptr; |
| { |
| ExtensionTestMessageListener listener("ready", false); |
| extension = LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| } |
| const char kPageHtml[] = R"( |
| <title>Loaded</title> |
| <body> |
| <script> |
| (() => { |
| const wbn_url = new URL('./web_bundle.wbn', location.href).toString(); |
| const redirect_js_url = |
| new URL('./redirect.js', location.href).toString(); |
| const redirected_js_url = |
| new URL('./redirected.js', location.href).toString(); |
| const redirect_to_unlisted_js_url = |
| new URL('./redirect_to_unlisted.js', location.href).toString(); |
| const redirect_to_server = |
| new URL('./redirect_to_server.js', location.href).toString(); |
| const link = document.createElement('link'); |
| link.rel = 'webbundle'; |
| link.href = wbn_url; |
| link.resources = redirect_js_url + ' ' + redirected_js_url + ' ' + |
| redirect_to_unlisted_js_url + ' ' + |
| redirect_to_server; |
| document.body.appendChild(link); |
| })(); |
| </script> |
| </body> |
| )"; |
| |
| std::string web_bundle; |
| RegisterWebBundleRequestHandler("/web_bundle.wbn", &web_bundle); |
| RegisterRequestHandler("/test.html", "text/html", kPageHtml); |
| RegisterRequestHandler("/redirect_to_server.js", "application/javascript", |
| "document.title = 'redirect_to_server';"); |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| |
| // Create a web bundle. |
| std::string redirect_js_url_str = |
| embedded_test_server()->GetURL("/redirect.js").spec(); |
| std::string redirected_js_url_str = |
| embedded_test_server()->GetURL("/redirected.js").spec(); |
| std::string redirect_to_unlisted_js_url_str = |
| embedded_test_server()->GetURL("/redirect_to_unlisted.js").spec(); |
| std::string redirected_to_unlisted_js_url_str = |
| embedded_test_server()->GetURL("/redirected_to_unlisted.js").spec(); |
| std::string redirect_to_server_js_url_str = |
| embedded_test_server()->GetURL("/redirect_to_server.js").spec(); |
| web_package::test::WebBundleBuilder builder(redirect_js_url_str, ""); |
| auto redirect_js_location = builder.AddResponse( |
| {{":status", "200"}, {"content-type", "application/javascript"}}, |
| "document.title = 'redirect';"); |
| auto redirected_js_location = builder.AddResponse( |
| {{":status", "200"}, {"content-type", "application/javascript"}}, |
| "document.title = 'redirected';"); |
| auto redirect_to_unlisted_location = builder.AddResponse( |
| {{":status", "200"}, {"content-type", "application/javascript"}}, |
| "document.title = 'redirect_to_unlisted';"); |
| auto redirected_to_unlisted_js_location = builder.AddResponse( |
| {{":status", "200"}, {"content-type", "application/javascript"}}, |
| "document.title = 'redirected_to_unlisted';"); |
| auto redirect_to_server_js_location = builder.AddResponse( |
| {{":status", "200"}, {"content-type", "application/javascript"}}, |
| "document.title = 'redirect_to_server';"); |
| builder.AddIndexEntry(redirect_js_url_str, "", {redirect_js_location}); |
| builder.AddIndexEntry(redirected_js_url_str, "", {redirected_js_location}); |
| builder.AddIndexEntry(redirect_to_unlisted_js_url_str, "", |
| {redirect_to_unlisted_location}); |
| builder.AddIndexEntry(redirected_to_unlisted_js_url_str, "", |
| {redirected_to_unlisted_js_location}); |
| builder.AddIndexEntry(redirect_to_server_js_url_str, "", |
| {redirect_to_server_js_location}); |
| std::vector<uint8_t> bundle = builder.CreateBundle(); |
| web_bundle = std::string(bundle.begin(), bundle.end()); |
| |
| GURL page_url = embedded_test_server()->GetURL("/test.html"); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ui_test_utils::NavigateToURL(browser(), page_url); |
| EXPECT_EQ(page_url, web_contents->GetLastCommittedURL()); |
| { |
| std::u16string expected_title = u"redirected"; |
| content::TitleWatcher title_watcher(web_contents, expected_title); |
| EXPECT_TRUE(TryLoadScript("redirect.js")); |
| EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); |
| } |
| { |
| // In the current implementation, extensions can redirect the request to |
| // the other resource in the web bundle even if the resource is not listed |
| // in the resources attribute. |
| std::u16string expected_title = u"redirected_to_unlisted"; |
| content::TitleWatcher title_watcher(web_contents, expected_title); |
| EXPECT_TRUE(TryLoadScript("redirect_to_unlisted.js")); |
| EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); |
| } |
| // In the current implementation, extensions can't redirect the request to |
| // outside the web bundle. |
| EXPECT_FALSE(TryLoadScript("redirect_to_server.js")); |
| } |
| |
| // TODO(crbug.com/1082020) When we implement variant matching of subresource |
| // web bundles, we should add test for request header modification. |
| |
| enum class RedirectType { |
| kOnBeforeRequest, |
| kOnHeadersReceived, |
| }; |
| |
| class RedirectInfoWebRequestApiTest |
| : public testing::WithParamInterface<RedirectType>, |
| public ExtensionApiTest { |
| public: |
| void SetUpOnMainThread() override { |
| ExtensionApiTest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data"); |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| } |
| |
| void InstallRequestRedirectingExtension(const std::string& resource_type) { |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(R"({ |
| "name": "Simple Redirect", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": { "scripts": ["background.js"] }, |
| "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] |
| })"); |
| test_dir.WriteFile( |
| FILE_PATH_LITERAL("background.js"), |
| base::StringPrintf(R"( |
| chrome.webRequest.%s.addListener(function(details) { |
| if (details.type == '%s' && |
| details.url.includes('hello.html')) { |
| var redirectUrl = |
| details.url.replace('original.test', 'redirected.test'); |
| return {redirectUrl: redirectUrl}; |
| } |
| }, {urls: ['*://original.test/*']}, ['blocking']); |
| chrome.test.sendMessage('ready'); |
| )", |
| GetParam() == RedirectType::kOnBeforeRequest |
| ? "onBeforeRequest" |
| : "onHeadersReceived", |
| resource_type.c_str())); |
| ExtensionTestMessageListener listener("ready", false); |
| const Extension* extension = LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| } |
| |
| private: |
| TestExtensionDir test_dir_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(RedirectMode, |
| RedirectInfoWebRequestApiTest, |
| ::testing::Values(RedirectType::kOnBeforeRequest, |
| RedirectType::kOnHeadersReceived)); |
| |
| // Test that a main frame request redirected by an extension has the correct |
| // site_for_cookies and network_isolation_key parameters. |
| IN_PROC_BROWSER_TEST_P(RedirectInfoWebRequestApiTest, |
| VerifyRedirectInfoMainFrame) { |
| InstallRequestRedirectingExtension("main_frame"); |
| |
| content::URLLoaderMonitor monitor; |
| |
| // Navigate to the URL that should be redirected, and check that the extension |
| // redirects it. |
| GURL url = embedded_test_server()->GetURL("original.test", "/hello.html"); |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| GURL redirected_url = |
| embedded_test_server()->GetURL("redirected.test", "/hello.html"); |
| EXPECT_EQ(redirected_url, web_contents->GetLastCommittedURL()); |
| |
| // Check the parameters passed to the URLLoaderFactory. |
| absl::optional<network::ResourceRequest> resource_request = |
| monitor.GetRequestInfo(redirected_url); |
| ASSERT_TRUE(resource_request.has_value()); |
| EXPECT_TRUE(resource_request->site_for_cookies.IsFirstParty(redirected_url)); |
| ASSERT_TRUE(resource_request->trusted_params); |
| url::Origin redirected_origin = url::Origin::Create(redirected_url); |
| EXPECT_TRUE( |
| resource_request->trusted_params->isolation_info.IsEqualForTesting( |
| net::IsolationInfo::Create( |
| net::IsolationInfo::RequestType::kMainFrame, redirected_origin, |
| redirected_origin, |
| net::SiteForCookies::FromOrigin(redirected_origin), |
| std::set<net::SchemefulSite>()))); |
| } |
| |
| // Test that a sub frame request redirected by an extension has the correct |
| // site_for_cookies and network_isolation_key parameters. |
| IN_PROC_BROWSER_TEST_P(RedirectInfoWebRequestApiTest, |
| VerifyBeforeRequestRedirectInfoSubFrame) { |
| InstallRequestRedirectingExtension("sub_frame"); |
| |
| content::URLLoaderMonitor monitor; |
| |
| // Navigate to page with an iframe that should be redirected, and check that |
| // the extension redirects it. |
| GURL original_iframed_url = |
| embedded_test_server()->GetURL("original.test", "/hello.html"); |
| GURL page_with_iframe_url = embedded_test_server()->GetURL( |
| "somewhere-else.test", |
| net::test_server::GetFilePathWithReplacements( |
| "/page_with_iframe.html", |
| base::StringPairs{ |
| {"title1.html", original_iframed_url.spec().c_str()}})); |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), page_with_iframe_url)); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| EXPECT_EQ(page_with_iframe_url, web_contents->GetLastCommittedURL()); |
| |
| // Since frames are returned in breadth first order, and there's only one |
| // iframe, the iframe should be the second frame in this vector. |
| std::vector<content::RenderFrameHost*> all_frames = |
| web_contents->GetAllFrames(); |
| ASSERT_EQ(2u, all_frames.size()); |
| GURL redirected_url = |
| embedded_test_server()->GetURL("redirected.test", "/hello.html"); |
| ASSERT_EQ(redirected_url, all_frames[1]->GetLastCommittedURL()); |
| |
| // Check the parameters passed to the URLLoaderFactory. |
| absl::optional<network::ResourceRequest> resource_request = |
| monitor.GetRequestInfo(redirected_url); |
| ASSERT_TRUE(resource_request.has_value()); |
| EXPECT_TRUE( |
| resource_request->site_for_cookies.IsFirstParty(page_with_iframe_url)); |
| EXPECT_FALSE(resource_request->site_for_cookies.IsFirstParty(redirected_url)); |
| ASSERT_TRUE(resource_request->trusted_params); |
| url::Origin top_level_origin = url::Origin::Create(page_with_iframe_url); |
| url::Origin redirected_origin = url::Origin::Create(redirected_url); |
| EXPECT_TRUE( |
| resource_request->trusted_params->isolation_info.IsEqualForTesting( |
| net::IsolationInfo::Create( |
| net::IsolationInfo::RequestType::kSubFrame, top_level_origin, |
| redirected_origin, |
| net::SiteForCookies::FromOrigin(top_level_origin), |
| std::set<net::SchemefulSite>()))); |
| } |
| |
| class ExtensionWebRequestApiIdentifiabilityTest |
| : public ExtensionWebRequestApiTest { |
| public: |
| void SetUpOnMainThread() override { |
| identifiability_metrics_test_helper_.SetUpOnMainThread(); |
| ExtensionWebRequestApiTest::SetUpOnMainThread(); |
| } |
| |
| protected: |
| IdentifiabilityMetricsTestHelper identifiability_metrics_test_helper_; |
| }; |
| |
| // Test that identifiability study of request cancellation produces expected |
| // events for a subresource. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiIdentifiabilityTest, Loader) { |
| base::RunLoop run_loop; |
| identifiability_metrics_test_helper_.PrepareForTest(&run_loop); |
| |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE( |
| RunExtensionTest("webrequest", {.page_url = "test_simple_cancel.html"})) |
| << message_; |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ukm::SourceId frame_id = web_contents->GetMainFrame()->GetPageUkmSourceId(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| identifiability_metrics_test_helper_.NavigateToBlankAndWaitForMetrics( |
| web_contents, &run_loop); |
| |
| std::set<ukm::SourceId> cancel_ids = |
| IdentifiabilityMetricsTestHelper::GetSourceIDsForSurfaceAndExtension( |
| merged_entries, |
| blink::IdentifiableSurface::Type::kExtensionCancelRequest, |
| GetSingleLoadedExtension()->id()); |
| ASSERT_EQ(1u, cancel_ids.size()); |
| EXPECT_TRUE(base::Contains(cancel_ids, frame_id)); |
| } |
| |
| // Test that identifiability study of request cancellation produces expected |
| // events when the navigation is cancelled. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiIdentifiabilityTest, Navigation) { |
| base::RunLoop run_loop; |
| identifiability_metrics_test_helper_.PrepareForTest(&run_loop); |
| |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(RunExtensionTest( |
| "webrequest", {.page_url = "test_simple_cancel_navigation.html"})) |
| << message_; |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ukm::SourceId frame_id = web_contents->GetMainFrame()->GetPageUkmSourceId(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| identifiability_metrics_test_helper_.NavigateToBlankAndWaitForMetrics( |
| web_contents, &run_loop); |
| |
| std::set<ukm::SourceId> cancel_ids = |
| IdentifiabilityMetricsTestHelper::GetSourceIDsForSurfaceAndExtension( |
| merged_entries, |
| blink::IdentifiableSurface::Type::kExtensionCancelRequest, |
| GetSingleLoadedExtension()->id()); |
| ASSERT_EQ(1u, cancel_ids.size()); |
| EXPECT_TRUE(base::Contains(cancel_ids, frame_id)); |
| } |
| |
| // Test that identifiability study of request cancellation produces expected |
| // events with WebSocket. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiIdentifiabilityTest, WebSocket) { |
| base::RunLoop run_loop; |
| identifiability_metrics_test_helper_.PrepareForTest(&run_loop); |
| |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ASSERT_TRUE(StartWebSocketServer(net::GetWebSocketTestDataDirectory())); |
| ASSERT_TRUE(RunExtensionTest( |
| "webrequest", {.page_url = "test_simple_websocket_cancel.html"})) |
| << message_; |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ukm::SourceId frame_id = web_contents->GetMainFrame()->GetPageUkmSourceId(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| identifiability_metrics_test_helper_.NavigateToBlankAndWaitForMetrics( |
| web_contents, &run_loop); |
| |
| std::set<ukm::SourceId> cancel_ids = |
| IdentifiabilityMetricsTestHelper::GetSourceIDsForSurfaceAndExtension( |
| merged_entries, |
| blink::IdentifiableSurface::Type::kExtensionCancelRequest, |
| GetSingleLoadedExtension()->id()); |
| ASSERT_EQ(1u, cancel_ids.size()); |
| EXPECT_TRUE(cancel_ids.find(frame_id) != cancel_ids.end()); |
| } |
| |
| // Test that where a page doesn't have extensions cancelling requests, no |
| // such event is recorded. |
| IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiIdentifiabilityTest, |
| NoInjectionRecorded) { |
| base::RunLoop run_loop; |
| identifiability_metrics_test_helper_.PrepareForTest(&run_loop); |
| |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| ui_test_utils::NavigateToURL(browser(), GURL("about:blank")); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| identifiability_metrics_test_helper_.EnsureIdentifiabilityEventGenerated( |
| web_contents); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| identifiability_metrics_test_helper_.NavigateToBlankAndWaitForMetrics( |
| web_contents, &run_loop); |
| EXPECT_FALSE(IdentifiabilityMetricsTestHelper::ContainsSurfaceOfType( |
| merged_entries, |
| blink::IdentifiableSurface::Type::kExtensionCancelRequest)); |
| } |
| |
| class ProxyCORSWebRequestApiTest : public ExtensionApiTest { |
| public: |
| ProxyCORSWebRequestApiTest() = default; |
| ~ProxyCORSWebRequestApiTest() override = default; |
| |
| protected: |
| class ProceedLoginDialog : public content::NotificationObserver { |
| public: |
| ProceedLoginDialog(const std::string& user, const std::string& password) |
| : user_(base::ASCIIToUTF16(user)), |
| password_(base::ASCIIToUTF16(password)) { |
| registrar_.Add(this, chrome::NOTIFICATION_AUTH_NEEDED, |
| content::NotificationService::AllSources()); |
| } |
| |
| ~ProceedLoginDialog() override = default; |
| |
| private: |
| ProceedLoginDialog(const ProceedLoginDialog&) = delete; |
| ProceedLoginDialog& operator=(const ProceedLoginDialog&) = delete; |
| |
| void Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) override { |
| LoginHandler* handler = |
| content::Details<LoginNotificationDetails>(details).ptr()->handler(); |
| handler->SetAuth(user_, password_); |
| } |
| |
| content::NotificationRegistrar registrar_; |
| std::u16string user_; |
| std::u16string password_; |
| }; |
| |
| void SetUpOnMainThread() override { |
| ExtensionApiTest::SetUpOnMainThread(); |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| proxy_cors_server_.RegisterRequestHandler(base::BindRepeating( |
| &ProxyCORSWebRequestApiTest::HandleProxiedCORSRequest)); |
| ASSERT_TRUE(proxy_cors_server_.Start()); |
| |
| PrefService* pref_service = browser()->profile()->GetPrefs(); |
| pref_service->Set(proxy_config::prefs::kProxy, |
| ProxyConfigDictionary::CreateFixedServers( |
| proxy_cors_server_.host_port_pair().ToString(), |
| "accounts.google.com")); |
| |
| // Flush the proxy configuration change to avoid any races. |
| ProfileNetworkContextServiceFactory::GetForContext(browser()->profile()) |
| ->FlushProxyConfigMonitorForTesting(); |
| profile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting(); |
| } |
| |
| static std::unique_ptr<net::test_server::HttpResponse> |
| HandleProxiedCORSRequest(const net::test_server::HttpRequest& request) { |
| std::string request_url; |
| // Request url with be replaced by host:port pair of embedded proxy server |
| // in HttpRequest, extract requested url from request line instead. |
| std::vector<std::string> request_lines = |
| base::SplitString(request.all_headers, "\r\n", base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY); |
| if (!request_lines.empty()) { |
| std::vector<std::string> request_line = |
| base::SplitString(request_lines[0], " ", base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY); |
| if (request_line.size() > 1) { |
| request_url = request_line[1]; |
| } |
| } |
| if (request_url != kCORSUrl) { |
| return nullptr; |
| } |
| |
| // Handle request as proxy server. |
| const auto proxy_auth = request.headers.find("Proxy-Authorization"); |
| std::string auth; |
| if (proxy_auth != request.headers.end()) { |
| auth = proxy_auth->second; |
| const std::string auth_method_prefix = "Basic "; |
| const auto prefix_pos = auth.find(auth_method_prefix); |
| EXPECT_EQ(0U, prefix_pos); |
| EXPECT_GT(auth.size(), auth_method_prefix.size()); |
| if (prefix_pos == 0U && auth.size() > auth_method_prefix.size()) { |
| auth = auth.substr(auth_method_prefix.size()); |
| EXPECT_TRUE(base::Base64Decode(auth, &auth)); |
| } else { |
| auth.clear(); |
| } |
| } |
| if (auth != base::StringPrintf("%s:%s", kCORSProxyUser, kCORSProxyPass)) { |
| std::unique_ptr<net::test_server::BasicHttpResponse> response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->AddCustomHeader("Proxy-Authenticate", |
| "Basic realm=\"TestRealm\""); |
| response->set_code(net::HTTP_PROXY_AUTHENTICATION_REQUIRED); |
| return response; |
| } |
| |
| // Handle request as cors server. |
| if (request.method == net::test_server::METHOD_OPTIONS) { |
| const auto preflight_method = |
| request.headers.find("Access-Control-Request-Method"); |
| const auto preflight_header = |
| request.headers.find("Access-Control-Request-Headers"); |
| if (preflight_method == request.headers.end() || |
| preflight_header == request.headers.end()) { |
| ADD_FAILURE() << "Expected Access-Control-Request-* headers were not " |
| "found in preflight request"; |
| std::unique_ptr<net::test_server::BasicHttpResponse> response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_BAD_REQUEST); |
| return response; |
| } |
| EXPECT_EQ("GET", preflight_method->second); |
| EXPECT_EQ(kCustomPreflightHeader, preflight_header->second); |
| |
| std::unique_ptr<net::test_server::BasicHttpResponse> response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->AddCustomHeader("Access-Control-Allow-Origin", "*"); |
| response->AddCustomHeader("Access-Control-Allow-Methods", "GET"); |
| response->AddCustomHeader("Access-Control-Allow-Headers", |
| kCustomPreflightHeader); |
| response->set_code(net::HTTP_NO_CONTENT); |
| return response; |
| } |
| EXPECT_EQ(net::test_server::METHOD_GET, request.method); |
| |
| std::unique_ptr<net::test_server::BasicHttpResponse> response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->AddCustomHeader("Access-Control-Allow-Origin", "*"); |
| response->set_content_type("text/plain"); |
| response->set_content("PASS"); |
| response->set_code(net::HTTP_OK); |
| return response; |
| } |
| |
| net::EmbeddedTestServer proxy_cors_server_; |
| }; |
| |
| // Regression test for crbug.com/1212625 |
| // Test that CORS preflight request which requires proxy auth completes |
| // successfully instead of being cancelled after proxy auth required response. |
| IN_PROC_BROWSER_TEST_F(ProxyCORSWebRequestApiTest, |
| PreflightCompletesSuccessfully) { |
| ProceedLoginDialog login_dialog(kCORSProxyUser, kCORSProxyPass); |
| ExtensionTestMessageListener ready_listener("ready", false); |
| const Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("webrequest_cors_preflight")); |
| ASSERT_TRUE(extension) << message_; |
| ASSERT_TRUE(ready_listener.WaitUntilSatisfied()); |
| ui_test_utils::NavigateToURL(browser(), |
| embedded_test_server()->GetURL("/empty.html")); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| ExtensionTestMessageListener preflight_listener("cors-preflight-succeeded", |
| false); |
| const char kCORSPreflightedRequest[] = R"( |
| var xhr = new XMLHttpRequest(); |
| xhr.open('GET', '%s'); |
| xhr.setRequestHeader('%s', 'testvalue'); |
| xhr.onload = () => {window.domAutomationController.send(true);}; |
| xhr.onerror = () => {window.domAutomationController.send(false);}; |
| xhr.send();)"; |
| bool success = false; |
| ASSERT_TRUE(ExecuteScriptAndExtractBool( |
| web_contents->GetMainFrame(), |
| base::StringPrintf(kCORSPreflightedRequest, kCORSUrl, |
| kCustomPreflightHeader), |
| &success)); |
| EXPECT_TRUE(success); |
| EXPECT_TRUE(preflight_listener.WaitUntilSatisfied()); |
| EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), |
| "preflightHeadersReceivedCount")); |
| EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), |
| "preflightProxyAuthRequiredCount")); |
| EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), |
| "preflightResponseStartedCount")); |
| EXPECT_EQ(1, GetCountFromBackgroundPage( |
| extension, profile(), |
| "preflightResponseStartedSuccessfullyCount")); |
| } |
| |
| } // namespace extensions |