| // Copyright 2018 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 "chrome/browser/extensions/extension_action_runner.h" |
| |
| #include "base/feature_list.h" |
| #include "base/files/file_path.h" |
| #include "base/json/json_reader.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/values.h" |
| #include "chrome/browser/extensions/api/tabs/tabs_api.h" |
| #include "chrome/browser/extensions/extension_browsertest.h" |
| #include "chrome/browser/extensions/extension_function_test_utils.h" |
| #include "chrome/browser/extensions/extension_tab_util.h" |
| #include "chrome/browser/extensions/tab_helper.h" |
| #include "chrome/browser/metrics/subprocess_metrics_provider.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_navigator.h" |
| #include "chrome/browser/ui/browser_navigator_params.h" |
| #include "chrome/browser/ui/extensions/app_launch_params.h" |
| #include "chrome/browser/ui/extensions/application_launch.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/network_service_util.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "extensions/browser/browsertest_util.h" |
| #include "extensions/common/extension_features.h" |
| #include "extensions/test/test_extension_dir.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "services/network/cross_origin_read_blocking.h" |
| #include "services/network/public/cpp/features.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace extensions { |
| |
| using CORBAction = network::CrossOriginReadBlocking::Action; |
| |
| class CrossOriginReadBlockingExtensionTest : public ExtensionBrowserTest { |
| public: |
| CrossOriginReadBlockingExtensionTest() = default; |
| |
| void SetUpOnMainThread() override { |
| ExtensionBrowserTest::SetUpOnMainThread(); |
| |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| content::SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| protected: |
| content::WebContents* active_web_contents() { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| const Extension* InstallExtension( |
| GURL resource_to_fetch_from_declarative_content_script = GURL()) { |
| bool use_declarative_content_script = |
| resource_to_fetch_from_declarative_content_script.is_valid(); |
| const char kContentScriptManifestEntry[] = R"( |
| "content_scripts": [{ |
| "all_frames": true, |
| "match_about_blank": true, |
| "matches": ["*://*/*"], |
| "js": ["content_script.js"] |
| }], |
| )"; |
| |
| const char kManifestTemplate[] = R"( |
| { |
| "name": "CrossOriginReadBlockingTest - Extension", |
| "version": "1.0", |
| "manifest_version": 2, |
| "permissions": ["tabs", "*://*/*"], |
| %s |
| "background": {"scripts": ["background_script.js"]} |
| } )"; |
| dir_.WriteManifest(base::StringPrintf( |
| kManifestTemplate, |
| use_declarative_content_script ? kContentScriptManifestEntry : "")); |
| |
| dir_.WriteFile(FILE_PATH_LITERAL("background_script.js"), ""); |
| dir_.WriteFile(FILE_PATH_LITERAL("page.html"), "<body>Hello World!</body>"); |
| |
| if (use_declarative_content_script) { |
| dir_.WriteFile( |
| FILE_PATH_LITERAL("content_script.js"), |
| CreateFetchScript(resource_to_fetch_from_declarative_content_script)); |
| } |
| extension_ = LoadExtension(dir_.UnpackedPath()); |
| return extension_; |
| } |
| |
| const Extension* InstallApp() { |
| const char kManifest[] = R"( |
| { |
| "name": "CrossOriginReadBlockingTest - App", |
| "version": "1.0", |
| "manifest_version": 2, |
| "permissions": ["*://*/*", "webview"], |
| "app": { |
| "background": { |
| "scripts": ["background_script.js"] |
| } |
| } |
| } )"; |
| dir_.WriteManifest(kManifest); |
| |
| const char kBackgroungScript[] = R"( |
| chrome.app.runtime.onLaunched.addListener(function() { |
| chrome.app.window.create('page.html', {}, function () {}); |
| }); |
| )"; |
| dir_.WriteFile(FILE_PATH_LITERAL("background_script.js"), |
| kBackgroungScript); |
| |
| const char kPage[] = R"( |
| <div id="webview-tag-container"></div> |
| )"; |
| dir_.WriteFile(FILE_PATH_LITERAL("page.html"), kPage); |
| |
| extension_ = LoadExtension(dir_.UnpackedPath()); |
| return extension_; |
| } |
| |
| bool RegisterServiceWorkerForExtension( |
| const std::string& service_worker_script) { |
| const char kServiceWorkerPath[] = "service_worker.js"; |
| dir_.WriteFile(base::FilePath::FromUTF8Unsafe(kServiceWorkerPath).value(), |
| service_worker_script); |
| |
| const char kRegistrationScript[] = R"( |
| navigator.serviceWorker.register($1).then(function() { |
| // Wait until the service worker is active. |
| return navigator.serviceWorker.ready; |
| }).then(function(r) { |
| window.domAutomationController.send('SUCCESS'); |
| }).catch(function(err) { |
| window.domAutomationController.send('ERROR: ' + err.message); |
| }); )"; |
| std::string registration_script = |
| content::JsReplace(kRegistrationScript, kServiceWorkerPath); |
| |
| std::string result = browsertest_util::ExecuteScriptInBackgroundPage( |
| browser()->profile(), extension_->id(), registration_script); |
| if (result != "SUCCESS") { |
| ADD_FAILURE() << "Failed to register the service worker: " << result; |
| return false; |
| } |
| return !::testing::Test::HasFailure(); |
| } |
| |
| // Injects (into |web_contents|) a content_script that performs a fetch of |
| // |url|. Returns the body of the response. |
| // |
| // The method below uses "programmatic" (rather than "declarative") way to |
| // inject a content script, but the behavior and permissions of the conecnt |
| // script should be the same in both cases. See also |
| // https://developer.chrome.com/extensions/content_scripts#programmatic. |
| std::string FetchViaContentScript(const GURL& url, |
| content::WebContents* web_contents) { |
| return FetchHelper( |
| url, base::BindOnce( |
| &CrossOriginReadBlockingExtensionTest::ExecuteContentScript, |
| base::Unretained(this), base::Unretained(web_contents))); |
| } |
| |
| // Performs a fetch of |url| from the background page of the test extension. |
| // Returns the body of the response. |
| std::string FetchViaBackgroundPage(const GURL& url) { |
| return FetchHelper( |
| url, base::BindOnce( |
| &browsertest_util::ExecuteScriptInBackgroundPageNoWait, |
| base::Unretained(browser()->profile()), extension_->id())); |
| } |
| |
| // Performs a fetch of |url| from |web_contents| (directly, without going |
| // through content scripts). Returns the body of the response. |
| std::string FetchViaWebContents(const GURL& url, |
| content::WebContents* web_contents) { |
| return FetchHelper( |
| url, base::BindOnce( |
| &CrossOriginReadBlockingExtensionTest::ExecuteRegularScript, |
| base::Unretained(this), base::Unretained(web_contents))); |
| } |
| |
| // Performs a fetch of |url| from a srcdoc subframe added to |parent_frame| |
| // and executing a script via <script> tag. Returns the body of the response. |
| std::string FetchViaSrcDocFrame(GURL url, |
| content::RenderFrameHost* parent_frame) { |
| return FetchHelper( |
| url, base::BindOnce( |
| &CrossOriginReadBlockingExtensionTest::ExecuteInSrcDocFrame, |
| base::Unretained(this), base::Unretained(parent_frame))); |
| } |
| |
| void VerifyContentScriptHistogramIsPresent( |
| const base::HistogramTester& histograms, |
| content::ResourceType resource_type) { |
| VerifyContentScriptHistogram( |
| histograms, testing::ElementsAre(base::Bucket(resource_type, 1))); |
| } |
| |
| void VerifyContentScriptHistogramIsMissing( |
| const base::HistogramTester& histograms) { |
| VerifyContentScriptHistogram(histograms, testing::IsEmpty()); |
| } |
| |
| std::string PopString(content::DOMMessageQueue* message_queue) { |
| std::string json; |
| EXPECT_TRUE(message_queue->WaitForMessage(&json)); |
| base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS); |
| std::unique_ptr<base::Value> value = reader.ReadToValue(json); |
| std::string result; |
| EXPECT_TRUE(value->GetAsString(&result)); |
| return result; |
| } |
| |
| GURL GetExtensionResource(const std::string& relative_path) { |
| return extension_->GetResourceURL(relative_path); |
| } |
| |
| url::Origin GetExtensionOrigin() { |
| return url::Origin::Create(extension_->url()); |
| } |
| |
| GURL GetTestPageUrl(const std::string& hostname) { |
| // Using the page below avoids a network fetch of /favicon.ico which helps |
| // avoid extra synchronization hassles in the tests. |
| return embedded_test_server()->GetURL( |
| hostname, "/favicon/title1_with_data_uri_icon.html"); |
| } |
| |
| const Extension* extension() { return extension_; } |
| |
| std::string CreateFetchScript(const GURL& resource) { |
| const char kXhrScriptTemplate[] = R"( |
| fetch($1) |
| .then(response => response.text()) |
| .then(text => domAutomationController.send(text)) |
| .catch(err => domAutomationController.send('error: ' + err)); |
| )"; |
| return content::JsReplace(kXhrScriptTemplate, resource); |
| } |
| |
| bool IsNetworkServiceEnabled() { |
| return base::FeatureList::IsEnabled(network::features::kNetworkService); |
| } |
| |
| private: |
| // Asks the test |extension_| to inject |content_script| into |web_contents|. |
| // |
| // This is an implementation of FetchCallback. |
| // Returns true if the content script execution started succeessfully. |
| bool ExecuteContentScript(content::WebContents* web_contents, |
| const std::string& content_script) { |
| int tab_id = ExtensionTabUtil::GetTabId(web_contents); |
| std::string background_script = content::JsReplace( |
| "chrome.tabs.executeScript($1, { code: $2 });", tab_id, content_script); |
| return browsertest_util::ExecuteScriptInBackgroundPageNoWait( |
| browser()->profile(), extension_->id(), background_script); |
| } |
| |
| // Executes |regular_script| in |web_contents|. |
| // |
| // This is an implementation of FetchCallback. |
| // Returns true if the script execution started succeessfully. |
| bool ExecuteRegularScript(content::WebContents* web_contents, |
| const std::string& regular_script) { |
| content::ExecuteScriptAsync(web_contents, regular_script); |
| |
| // Report artificial success to meet FetchCallback's requirements. |
| return true; |
| } |
| |
| // Injects into |parent_frame| an "srcdoc" subframe that contains/executes |
| // |script_to_run_in_subframe| via <script> tag. |
| // |
| // This function is useful to exercise a scenario when a <script> tag may |
| // execute before the browser gets a chance to see the a frame/navigation |
| // commit is happening. |
| // |
| // This is an implementation of FetchCallback. |
| // Returns true if the script execution started succeessfully. |
| bool ExecuteInSrcDocFrame(content::RenderFrameHost* parent_frame, |
| const std::string& script_to_run_in_subframe) { |
| static int sequence_id = 0; |
| sequence_id++; |
| std::string filename = |
| base::StringPrintf("srcdoc_script_%d.js", sequence_id); |
| dir_.WriteFile(base::FilePath::FromUTF8Unsafe(filename).value(), |
| script_to_run_in_subframe); |
| |
| // Using <script src=...></script> instead of <script>...</script> to avoid |
| // extensions CSP which forbids inline scripts. |
| const char kScriptTemplate[] = R"( |
| var subframe = document.createElement('iframe'); |
| subframe.srcdoc = '<script src=' + $1 + '></script>'; |
| document.body.appendChild(subframe); )"; |
| std::string subframe_injection_script = |
| content::JsReplace(kScriptTemplate, filename); |
| content::ExecuteScriptAsync(parent_frame, subframe_injection_script); |
| |
| // Report artificial success to meet FetchCallback's requirements. |
| return true; |
| } |
| |
| // FetchCallback represents a function that executes |fetch_script|. |
| // |
| // |fetch_script| will include calls to |domAutomationController.send| and |
| // therefore instances of FetchCallback should not inject their own calls to |
| // |domAutomationController.send| (e.g. this constraint rules out |
| // browsertest_util::ExecuteScriptInBackgroundPage and/or |
| // content::ExecuteScript). |
| // |
| // The function should return true if script execution started successfully. |
| // |
| // Currently used "implementations": |
| // - CrossOriginReadBlockingExtensionTest::ExecuteContentScript(web_contents) |
| // - CrossOriginReadBlockingExtensionTest::ExecuteRegularScript(web_contents) |
| // - browsertest_util::ExecuteScriptInBackgroundPageNoWait(profile, ext_id) |
| using FetchCallback = |
| base::OnceCallback<bool(const std::string& fetch_script)>; |
| |
| // Returns response body of a fetch of |url| initiated via |fetch_callback|. |
| std::string FetchHelper(const GURL& url, FetchCallback fetch_callback) { |
| content::DOMMessageQueue message_queue; |
| |
| // Inject a content script that performs a cross-origin XHR to bar.com. |
| EXPECT_TRUE(std::move(fetch_callback).Run(CreateFetchScript(url))); |
| |
| // Wait until the message comes back and extract result from the message. |
| return PopString(&message_queue); |
| } |
| |
| void VerifyContentScriptHistogram( |
| const base::HistogramTester& histograms, |
| testing::Matcher<std::vector<base::Bucket>> matcher) { |
| // LogInitiatorSchemeBypassingDocumentBlocking is only implemented in the |
| // pre-NetworkService CrossSiteDocumentResourceHandler, because we hope to |
| // gather enough data before NetworkService ships. Logging in |
| // NetworkService world should be possible but would require an extra IPC |
| // from NetworkService to the Browser process which seems like unnecessary |
| // complexity, given that the metrics gathered won't be needed in the |
| // long-term. |
| if (IsNetworkServiceEnabled()) |
| return; |
| |
| // Verify that LogInitiatorSchemeBypassingDocumentBlocking returned early |
| // for a request that wasn't from a content script. |
| EXPECT_THAT(histograms.GetAllSamples( |
| "SiteIsolation.XSD.Browser.Allowed.ContentScript"), |
| matcher); |
| } |
| |
| TestExtensionDir dir_; |
| const Extension* extension_ = nullptr; |
| |
| DISALLOW_COPY_AND_ASSIGN(CrossOriginReadBlockingExtensionTest); |
| }; |
| |
| struct AllowlistingParam { |
| AllowlistingParam(bool feature_enabled, bool test_extension_allowlisted) |
| : feature_enabled(feature_enabled), |
| test_extension_allowlisted(test_extension_allowlisted) {} |
| |
| bool feature_enabled; |
| bool test_extension_allowlisted; |
| }; |
| |
| class CrossOriginReadBlockingExtensionAllowlistingTest |
| : public CrossOriginReadBlockingExtensionTest, |
| public ::testing::WithParamInterface<AllowlistingParam> { |
| public: |
| using Base = CrossOriginReadBlockingExtensionTest; |
| |
| CrossOriginReadBlockingExtensionAllowlistingTest() {} |
| |
| bool IsAllowlistFeatureEnabled() { return GetParam().feature_enabled; } |
| bool IsExtensionAllowlisted() { |
| return GetParam().test_extension_allowlisted; |
| } |
| |
| const Extension* InstallExtension( |
| GURL resource_to_fetch_from_declarative_content_script = GURL()) { |
| const Extension* extension = Base::InstallExtension( |
| resource_to_fetch_from_declarative_content_script); |
| |
| if (IsAllowlistFeatureEnabled()) { |
| if (IsExtensionAllowlisted()) { |
| scoped_feature_list_.InitAndEnableFeatureWithParameters( |
| extensions_features::kBypassCorbOnlyForExtensionsAllowlist, |
| {{extensions_features::kBypassCorbAllowlistParamName, |
| extension->hashed_id().value()}}); |
| } else { |
| scoped_feature_list_.InitAndEnableFeature( |
| extensions_features::kBypassCorbOnlyForExtensionsAllowlist); |
| } |
| } else { |
| scoped_feature_list_.InitAndDisableFeature( |
| extensions_features::kBypassCorbOnlyForExtensionsAllowlist); |
| } |
| |
| return extension; |
| } |
| |
| bool AreContentScriptFetchesExpectedToBeBlocked() { |
| return IsNetworkServiceEnabled() && IsAllowlistFeatureEnabled() && |
| !IsExtensionAllowlisted(); |
| } |
| |
| bool IsCorbExpectedToBeTurnedOffAltogether() { |
| return IsNetworkServiceEnabled() && |
| (IsExtensionAllowlisted() || !IsAllowlistFeatureEnabled()); |
| } |
| |
| void VerifyFetchFromContentScriptWasBlocked( |
| const base::HistogramTester& histograms) { |
| // Make sure that histograms logged in other processes (e.g. in |
| // NetworkService process) get synced. |
| SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| histograms.ExpectBucketCount("SiteIsolation.XSD.Browser.Action", |
| CORBAction::kResponseStarted, 1); |
| histograms.ExpectBucketCount("SiteIsolation.XSD.Browser.Action", |
| CORBAction::kBlockedWithoutSniffing, 1); |
| |
| if (!IsNetworkServiceEnabled()) { |
| EXPECT_THAT(histograms.GetAllSamples("SiteIsolation.XSD.Browser.Blocked"), |
| testing::Not(testing::IsEmpty())); |
| } |
| } |
| |
| void VerifyFetchFromContentScriptWasAllowed( |
| const base::HistogramTester& histograms, |
| bool expecting_sniffing = false) { |
| // Make sure that histograms logged in other processes (e.g. in |
| // NetworkService process) get synced. |
| SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| if (IsCorbExpectedToBeTurnedOffAltogether()) { |
| EXPECT_EQ(0u, |
| histograms.GetTotalCountsForPrefix("SiteIsolation.XSD.Browser") |
| .size()); |
| return; |
| } |
| |
| histograms.ExpectBucketCount("SiteIsolation.XSD.Browser.Action", |
| CORBAction::kResponseStarted, 1); |
| histograms.ExpectBucketCount("SiteIsolation.XSD.Browser.Action", |
| expecting_sniffing |
| ? CORBAction::kAllowedAfterSniffing |
| : CORBAction::kAllowedWithoutSniffing, |
| 1); |
| |
| if (!IsNetworkServiceEnabled()) { |
| EXPECT_THAT(histograms.GetAllSamples("SiteIsolation.XSD.Browser.Blocked"), |
| testing::IsEmpty()); |
| } |
| } |
| |
| // Verifies results of fetching a CORB-eligible resource from a content |
| // script. Expectations differ depending on the following: |
| // 1. Non-NetworkService: Fetches from content scripts should never be blocked |
| // 2. NetworkService + allowlisted extension: Fetches from content scripts |
| // should not be blocked |
| // 3. NetworkService + other extension: Fetches from content scripts should |
| // be blocked |
| void VerifyFetchFromContentScript(const base::HistogramTester& histograms, |
| const std::string& actual_fetch_result, |
| const std::string& expected_fetch_result) { |
| if (AreContentScriptFetchesExpectedToBeBlocked()) { |
| // Verify the fetch was blocked. |
| EXPECT_EQ(std::string(), actual_fetch_result); |
| VerifyFetchFromContentScriptWasBlocked(histograms); |
| } else { |
| // Verify the fetch was allowed. |
| EXPECT_EQ(expected_fetch_result, actual_fetch_result); |
| VerifyFetchFromContentScriptWasAllowed(histograms); |
| |
| // Verify that "SiteIsolation.XSD.Browser.Allowed.ContentScript" UMA |
| // metric was recorded. This indirectly verifies that |
| // Extensions.CrossOriginFetchFromContentScript2 Rappor metric got |
| // properly recorded (because both the UMA and the Rappor metrics are |
| // logged next to each other by the |
| // LogInitiatorSchemeBypassingDocumentBlocking method in |
| // ChromeContentBrowserClientExtensionsPart). |
| VerifyContentScriptHistogramIsPresent(histograms, |
| content::RESOURCE_TYPE_XHR); |
| } |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CrossOriginReadBlockingExtensionAllowlistingTest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(CrossOriginReadBlockingExtensionAllowlistingTest, |
| FromDeclarativeContentScript_NoSniffXml) { |
| // Load the test extension. |
| GURL cross_site_resource( |
| embedded_test_server()->GetURL("bar.com", "/nosniff.xml")); |
| ASSERT_TRUE(InstallExtension(cross_site_resource)); |
| |
| // Test case #1: Declarative script injected after a browser-initiated |
| // navigation of the main frame. |
| { |
| // Monitor CORB behavior + result of the fetch. |
| base::HistogramTester histograms; |
| content::DOMMessageQueue message_queue; |
| |
| // Navigate to a foo.com page - this should trigger execution of the |
| // |content_script| declared in the extension manifest. |
| GURL page_url = GetTestPageUrl("foo.com"); |
| ui_test_utils::NavigateToURL(browser(), page_url); |
| EXPECT_EQ(page_url, |
| active_web_contents()->GetMainFrame()->GetLastCommittedURL()); |
| EXPECT_EQ(url::Origin::Create(page_url), |
| active_web_contents()->GetMainFrame()->GetLastCommittedOrigin()); |
| |
| // Extract results of the fetch done in the declarative content script. |
| std::string fetch_result = PopString(&message_queue); |
| |
| // Verify whether the fetch worked or not (expectations differ depending on |
| // various factors - see the body of VerifyFetchFromContentScript). |
| VerifyFetchFromContentScript(histograms, fetch_result, |
| "nosniff.xml - body\n"); |
| } |
| |
| // Test case #2: Declarative script injected after a renderer-initiated |
| // creation of an about:blank frame. |
| { |
| // Monitor CORB behavior + result of the fetch. |
| base::HistogramTester histograms; |
| content::DOMMessageQueue message_queue; |
| |
| // Inject an about:blank subframe - this should trigger execution of the |
| // |content_script| declared in the extension manifest. |
| const char kBlankSubframeInjectionScript[] = R"( |
| var subframe = document.createElement('iframe'); |
| document.body.appendChild(subframe); )"; |
| content::ExecuteScriptAsync(active_web_contents(), |
| kBlankSubframeInjectionScript); |
| |
| // Extract results of the fetch done in the declarative content script. |
| std::string fetch_result = PopString(&message_queue); |
| |
| // Verify whether the fetch worked or not (expectations differ depending on |
| // various factors - see the body of VerifyFetchFromContentScript). |
| VerifyFetchFromContentScript(histograms, fetch_result, |
| "nosniff.xml - body\n"); |
| } |
| } |
| |
| // Test that verifies the current, baked-in (but not necessarily desirable |
| // behavior) where an extension that has permission to inject a content script |
| // to any page can also XHR (without CORS!) any cross-origin resource. |
| // See also https://crbug.com/846346. |
| IN_PROC_BROWSER_TEST_P(CrossOriginReadBlockingExtensionAllowlistingTest, |
| FromProgrammaticContentScript_NoSniffXml) { |
| // Load the test extension. |
| ASSERT_TRUE(InstallExtension()); |
| |
| // Navigate to a foo.com page. |
| GURL page_url = GetTestPageUrl("foo.com"); |
| ui_test_utils::NavigateToURL(browser(), page_url); |
| ASSERT_EQ(page_url, |
| active_web_contents()->GetMainFrame()->GetLastCommittedURL()); |
| ASSERT_EQ(url::Origin::Create(page_url), |
| active_web_contents()->GetMainFrame()->GetLastCommittedOrigin()); |
| |
| // Inject a content script that performs a cross-origin XHR to bar.com. |
| base::HistogramTester histograms; |
| GURL cross_site_resource( |
| embedded_test_server()->GetURL("bar.com", "/nosniff.xml")); |
| std::string fetch_result = |
| FetchViaContentScript(cross_site_resource, active_web_contents()); |
| |
| // Verify whether the fetch worked or not (expectations differ depending on |
| // various factors - see the body of VerifyFetchFromContentScript). |
| VerifyFetchFromContentScript(histograms, fetch_result, |
| "nosniff.xml - body\n"); |
| } |
| |
| // Tests that same-origin fetches (same-origin relative to the webpage the |
| // content script is injected into) are allowed. See also |
| // https://crbug.com/918660. |
| IN_PROC_BROWSER_TEST_P(CrossOriginReadBlockingExtensionAllowlistingTest, |
| FromProgrammaticContentScript_SameOrigin) { |
| // Load the test extension. |
| ASSERT_TRUE(InstallExtension()); |
| |
| // Navigate to a foo.com page. |
| GURL page_url = GetTestPageUrl("foo.com"); |
| ui_test_utils::NavigateToURL(browser(), page_url); |
| ASSERT_EQ(page_url, |
| active_web_contents()->GetMainFrame()->GetLastCommittedURL()); |
| ASSERT_EQ(url::Origin::Create(page_url), |
| active_web_contents()->GetMainFrame()->GetLastCommittedOrigin()); |
| |
| // Inject a content script that performs a same-origin XHR to foo.com. |
| base::HistogramTester histograms; |
| GURL same_origin_resource( |
| embedded_test_server()->GetURL("foo.com", "/nosniff.xml")); |
| std::string fetch_result = |
| FetchViaContentScript(same_origin_resource, active_web_contents()); |
| |
| // Verify that no blocking occurred. |
| EXPECT_THAT(fetch_result, ::testing::StartsWith("nosniff.xml - body")); |
| VerifyFetchFromContentScriptWasAllowed(histograms, |
| false /* expecting_sniffing */); |
| } |
| |
| // Test that responses that would have been allowed by CORB anyway are not |
| // reported to LogInitiatorSchemeBypassingDocumentBlocking. |
| IN_PROC_BROWSER_TEST_P(CrossOriginReadBlockingExtensionAllowlistingTest, |
| FromProgrammaticContentScript_AllowedTextResource) { |
| // Load the test extension. |
| ASSERT_TRUE(InstallExtension()); |
| |
| // Navigate to a foo.com page. |
| GURL page_url = GetTestPageUrl("foo.com"); |
| ui_test_utils::NavigateToURL(browser(), page_url); |
| ASSERT_EQ(page_url, |
| active_web_contents()->GetMainFrame()->GetLastCommittedURL()); |
| ASSERT_EQ(url::Origin::Create(page_url), |
| active_web_contents()->GetMainFrame()->GetLastCommittedOrigin()); |
| |
| // Inject a content script that performs a cross-origin XHR to bar.com. |
| // |
| // StartsWith (rather than equality) is used in the verification step to |
| // account for \n VS \r\n difference on Windows. |
| base::HistogramTester histograms; |
| GURL cross_site_resource( |
| embedded_test_server()->GetURL("bar.com", "/save_page/text.txt")); |
| std::string fetch_result = |
| FetchViaContentScript(cross_site_resource, active_web_contents()); |
| |
| // Verify that no blocking occurred. |
| EXPECT_THAT(fetch_result, |
| ::testing::StartsWith( |
| "text-object.txt: ae52dd09-9746-4b7e-86a6-6ada5e2680c2")); |
| VerifyFetchFromContentScriptWasAllowed(histograms, |
| true /* expecting_sniffing */); |
| |
| // Verify that we didn't call LogInitiatorSchemeBypassingDocumentBlocking |
| // for a response that would have been allowed by CORB anyway. |
| VerifyContentScriptHistogramIsMissing(histograms); |
| } |
| |
| // Test that responses are blocked by CORB, but have empty response body are not |
| // reported to LogInitiatorSchemeBypassingDocumentBlocking. |
| IN_PROC_BROWSER_TEST_P(CrossOriginReadBlockingExtensionAllowlistingTest, |
| FromProgrammaticContentScript_EmptyAndBlocked) { |
| // Load the test extension. |
| ASSERT_TRUE(InstallExtension()); |
| |
| // Navigate to a foo.com page. |
| GURL page_url = GetTestPageUrl("foo.com"); |
| ui_test_utils::NavigateToURL(browser(), page_url); |
| ASSERT_EQ(page_url, |
| active_web_contents()->GetMainFrame()->GetLastCommittedURL()); |
| ASSERT_EQ(url::Origin::Create(page_url), |
| active_web_contents()->GetMainFrame()->GetLastCommittedOrigin()); |
| |
| // Inject a content script that performs a cross-origin XHR to bar.com. |
| base::HistogramTester histograms; |
| GURL cross_site_resource( |
| embedded_test_server()->GetURL("bar.com", "/nosniff.empty")); |
| EXPECT_EQ(std::string(), |
| FetchViaContentScript(cross_site_resource, active_web_contents())); |
| |
| // Verify whether blocking occurred or not. |
| if (AreContentScriptFetchesExpectedToBeBlocked()) |
| VerifyFetchFromContentScriptWasBlocked(histograms); |
| else |
| VerifyFetchFromContentScriptWasAllowed(histograms); |
| |
| // Verify that we didn't call LogInitiatorSchemeBypassingDocumentBlocking |
| // for a response that would have been blocked by CORB, but was empty. |
| VerifyContentScriptHistogramIsMissing(histograms); |
| } |
| |
| // Test that LogInitiatorSchemeBypassingDocumentBlocking exits early for |
| // requests that aren't from content scripts. |
| IN_PROC_BROWSER_TEST_F(CrossOriginReadBlockingExtensionTest, |
| FromBackgroundPage_NoSniffXml) { |
| // Load the test extension. |
| ASSERT_TRUE(InstallExtension()); |
| |
| // Performs a cross-origin XHR from the background page. |
| base::HistogramTester histograms; |
| GURL cross_site_resource( |
| embedded_test_server()->GetURL("bar.com", "/nosniff.xml")); |
| std::string fetch_result = FetchViaBackgroundPage(cross_site_resource); |
| |
| // Verify that no blocking occurred. |
| EXPECT_EQ("nosniff.xml - body\n", fetch_result); |
| EXPECT_THAT(histograms.GetAllSamples("SiteIsolation.XSD.Browser.Blocked"), |
| testing::IsEmpty()); |
| |
| // Verify that LogInitiatorSchemeBypassingDocumentBlocking returned early |
| // for a request that wasn't from a content script. |
| VerifyContentScriptHistogramIsMissing(histograms); |
| } |
| |
| // Test that requests from a extension page hosted in a foreground tab use |
| // relaxed CORB processing. |
| IN_PROC_BROWSER_TEST_F(CrossOriginReadBlockingExtensionTest, |
| FromForegroundPage_NoSniffXml) { |
| // Load the test extension. |
| ASSERT_TRUE(InstallExtension()); |
| |
| // Navigate a tab to an extension page. |
| ui_test_utils::NavigateToURL(browser(), GetExtensionResource("page.html")); |
| ASSERT_EQ(GetExtensionOrigin(), |
| active_web_contents()->GetMainFrame()->GetLastCommittedOrigin()); |
| |
| // Test case #1: Fetch from a chrome-extension://... main frame. |
| { |
| // Perform a cross-origin XHR from the foreground extension page. |
| base::HistogramTester histograms; |
| GURL cross_site_resource( |
| embedded_test_server()->GetURL("bar.com", "/nosniff.xml")); |
| std::string fetch_result = |
| FetchViaWebContents(cross_site_resource, active_web_contents()); |
| |
| // Verify that no blocking occurred. |
| EXPECT_EQ("nosniff.xml - body\n", fetch_result); |
| EXPECT_THAT(histograms.GetAllSamples("SiteIsolation.XSD.Browser.Blocked"), |
| testing::IsEmpty()); |
| |
| // Verify that LogInitiatorSchemeBypassingDocumentBlocking returned early |
| // for a request that wasn't from a content script. |
| VerifyContentScriptHistogramIsMissing(histograms); |
| } |
| |
| // Test case #2: Fetch from an about:srcdoc subframe of a |
| // chrome-extension://... frame. |
| { |
| // Perform a cross-origin XHR from the foreground extension page. |
| base::HistogramTester histograms; |
| GURL cross_site_resource( |
| embedded_test_server()->GetURL("bar.com", "/nosniff.xml")); |
| std::string fetch_result = FetchViaSrcDocFrame( |
| cross_site_resource, active_web_contents()->GetMainFrame()); |
| |
| // Verify that no blocking occurred. |
| EXPECT_EQ("nosniff.xml - body\n", fetch_result); |
| EXPECT_THAT(histograms.GetAllSamples("SiteIsolation.XSD.Browser.Blocked"), |
| testing::IsEmpty()); |
| |
| // Verify that LogInitiatorSchemeBypassingDocumentBlocking returned early |
| // for a request that wasn't from a content script. |
| VerifyContentScriptHistogramIsMissing(histograms); |
| } |
| } |
| |
| // Test that requests from an extension's service worker to the network use |
| // relaxed CORB processing (both in the case of requests that 1) are initiated |
| // by the service worker and/or 2) are ignored by the service worker and fall |
| // back to the network). |
| IN_PROC_BROWSER_TEST_F(CrossOriginReadBlockingExtensionTest, |
| FromServiceWorker_NoSniffXml) { |
| // Load the test extension. |
| ASSERT_TRUE(InstallExtension()); |
| |
| // Register the service worker which injects "SERVICE WORKER INTERCEPT: " |
| // prefix to the body of each response. |
| const char kServiceWorkerScript[] = R"( |
| self.addEventListener('fetch', function(event) { |
| // Intercept all http requests to bar.com and inject |
| // 'SERVICE WORKER INTERCEPT:' prefix. |
| if (event.request.url.startsWith('http://bar.com')) { |
| event.respondWith( |
| // By using the 'fetch' call below, the service worker initiates |
| // a network request that will go through the URLLoaderFactory |
| // created via CreateFactoryBundle called / posted indirectly |
| // from EmbeddedWorkerInstance::StartTask::Start. |
| fetch(event.request) |
| .then(response => response.text()) |
| .then(text => new Response( |
| 'SERVICE WORKER INTERCEPT: >>>' + text + '<<<'))); |
| } |
| |
| // Let the request go directly to the network in all the other cases, |
| // like: |
| // - loading the extension resources like page.html (avoiding going |
| // through the service worker is required for correctness of test |
| // setup), |
| // - handling the cross-origin fetch to other.com in test case #2. |
| // Note that these requests will use the URLLoaderFactory owned by |
| // ServiceWorkerSubresourceLoader which can be different to the |
| // network loader factory owned by the ServiceWorker thread (which is |
| // used for fetch intiated by the service worker above). |
| }); )"; |
| ASSERT_TRUE(RegisterServiceWorkerForExtension(kServiceWorkerScript)); |
| |
| // Navigate a tab to an extension page. |
| ui_test_utils::NavigateToURL(browser(), GetExtensionResource("page.html")); |
| ASSERT_EQ(GetExtensionOrigin(), |
| active_web_contents()->GetMainFrame()->GetLastCommittedOrigin()); |
| |
| // Verify that the service worker controls the fetches. |
| bool is_controlled_by_service_worker = false; |
| ASSERT_TRUE(ExecuteScriptAndExtractBool( |
| active_web_contents(), |
| "domAutomationController.send(!!navigator.serviceWorker.controller)", |
| &is_controlled_by_service_worker)); |
| ASSERT_TRUE(is_controlled_by_service_worker); |
| |
| // Test case #1: Network fetch initiated by the service worker. |
| // |
| // This covers URLLoaderFactory owned by the ServiceWorker thread and created |
| // created via CreateFactoryBundle called / posted indirectly from |
| // EmbeddedWorkerInstance::StartTask::Start. |
| { |
| // Perform a cross-origin XHR from the foreground extension page. |
| // This should be intercepted by the service worker installed above. |
| base::HistogramTester histograms; |
| GURL cross_site_resource_intercepted_by_service_worker( |
| embedded_test_server()->GetURL("bar.com", "/nosniff.xml")); |
| std::string fetch_result = |
| FetchViaWebContents(cross_site_resource_intercepted_by_service_worker, |
| active_web_contents()); |
| |
| // Verify that no blocking occurred (and that the response really did go |
| // through the service worker). |
| EXPECT_EQ("SERVICE WORKER INTERCEPT: >>>nosniff.xml - body\n<<<", |
| fetch_result); |
| EXPECT_THAT(histograms.GetAllSamples("SiteIsolation.XSD.Browser.Blocked"), |
| testing::IsEmpty()); |
| |
| // Verify that LogInitiatorSchemeBypassingDocumentBlocking returned early |
| // for a request that wasn't from a content script. |
| VerifyContentScriptHistogramIsMissing(histograms); |
| } |
| |
| // Test case #2: Network fetch used as a fallback when service worker ignores |
| // the 'fetch' event. |
| // |
| // This covers URLLoaderFactory owned by the ServiceWorkerSubresourceLoader, |
| // which can be different to the network loader factory owned by the |
| // ServiceWorker thread (which is used in test case #1). |
| { |
| // Perform a cross-origin XHR from the foreground extension page. |
| // This should be intercepted by the service worker installed above. |
| base::HistogramTester histograms; |
| GURL cross_site_resource_ignored_by_service_worker( |
| embedded_test_server()->GetURL("other.com", "/nosniff.xml")); |
| std::string fetch_result = FetchViaWebContents( |
| cross_site_resource_ignored_by_service_worker, active_web_contents()); |
| |
| // Verify that no blocking occurred. |
| EXPECT_EQ("nosniff.xml - body\n", fetch_result); |
| EXPECT_THAT(histograms.GetAllSamples("SiteIsolation.XSD.Browser.Blocked"), |
| testing::IsEmpty()); |
| |
| // Verify that LogInitiatorSchemeBypassingDocumentBlocking returned early |
| // for a request that wasn't from a content script. |
| VerifyContentScriptHistogramIsMissing(histograms); |
| } |
| } |
| |
| class ReadyToCommitWaiter : public content::WebContentsObserver { |
| public: |
| explicit ReadyToCommitWaiter(content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents) {} |
| |
| ~ReadyToCommitWaiter() override {} |
| |
| void Wait() { run_loop_.Run(); } |
| |
| void ReadyToCommitNavigation( |
| content::NavigationHandle* navigation_handle) override { |
| run_loop_.Quit(); |
| } |
| |
| private: |
| base::RunLoop run_loop_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ReadyToCommitWaiter); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CrossOriginReadBlockingExtensionTest, |
| ProgrammaticContentScriptVsWebUI) { |
| // Load the test extension. |
| ASSERT_TRUE(InstallExtension()); |
| |
| // Try to inject a content script just as we are about to commit a WebUI page. |
| // This will cause ExecuteCodeInTabFunction::CanExecuteScriptOnPage to execute |
| // while RenderFrameHost::GetLastCommittedOrigin() still corresponds to the |
| // old page. |
| { |
| // Initiate navigating a new, blank tab (this avoids process swaps which |
| // would otherwise occur when navigating to a WebUI pages from either the |
| // NTP or from a web page). This simulates choosing "Settings" from the |
| // main menu. |
| GURL web_ui_url("chrome://settings"); |
| NavigateParams nav_params( |
| browser(), web_ui_url, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_GENERATED)); |
| nav_params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| content::WebContentsAddedObserver new_web_contents_observer; |
| Navigate(&nav_params); |
| |
| // Capture the new WebContents. |
| content::WebContents* new_web_contents = |
| new_web_contents_observer.GetWebContents(); |
| ReadyToCommitWaiter ready_to_commit_waiter(new_web_contents); |
| content::TestNavigationObserver navigation_observer(new_web_contents, 1); |
| |
| // Repro of https://crbug.com/894766 requires that no cross-process swap |
| // takes place - this is what happens when navigating an initial/blank tab. |
| |
| // Wait until ReadyToCommit happens. |
| // |
| // For the repro to happen, content script injection needs to run |
| // 1) after RenderFrameHostImpl::CommitNavigation |
| // (which runs in the same revolution of the message pump as |
| // WebContentsObserver::ReadyToCommitNavigation) |
| // 2) before RenderFrameHostImpl::DidCommitProvisionalLoad |
| // (task posted from ReadyToCommitNavigation above should execute |
| // before any IPC responses that come after PostTask call). |
| ready_to_commit_waiter.Wait(); |
| DCHECK_NE(web_ui_url, |
| active_web_contents()->GetMainFrame()->GetLastCommittedURL()); |
| |
| // Inject the content script (simulating chrome.tabs.executeScript, but |
| // using TabsExecuteScriptFunction directly to ensure the right timing). |
| int tab_id = ExtensionTabUtil::GetTabId(active_web_contents()); |
| const char kArgsTemplate[] = R"( |
| [%d, {"code": " |
| var p = document.createElement('p'); |
| p.innerText = 'content script injection succeeded unexpectedly'; |
| p.id = 'content-script-injection-result'; |
| document.body.appendChild(p); |
| "}] )"; |
| std::string args = base::StringPrintf(kArgsTemplate, tab_id); |
| auto function = base::MakeRefCounted<TabsExecuteScriptFunction>(); |
| function->set_extension(extension()); |
| std::string actual_error = |
| extension_function_test_utils::RunFunctionAndReturnError( |
| function.get(), args, browser()); |
| std::string expected_error = |
| "Cannot access contents of url \"chrome://settings/\". " |
| "Extension manifest must request permission to access this host."; |
| EXPECT_EQ(expected_error, actual_error); |
| |
| // Wait until the navigation completes. |
| navigation_observer.Wait(); |
| EXPECT_EQ(web_ui_url, |
| active_web_contents()->GetMainFrame()->GetLastCommittedURL()); |
| } |
| |
| // Check if the injection above succeeded (it shouldn't have, because of |
| // renderer-side checks). |
| const char kInjectionVerificationScript[] = R"( |
| domAutomationController.send( |
| !!document.getElementById('content-script-injection-result')); )"; |
| bool has_content_script_run = false; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool(active_web_contents(), |
| kInjectionVerificationScript, |
| &has_content_script_run)); |
| EXPECT_FALSE(has_content_script_run); |
| |
| // Try to fetch a WebUI resource (i.e. verify that the unsucessful content |
| // script injection above didn't clobber the WebUI-specific URLLoaderFactory). |
| const char kScript[] = R"( |
| var img = document.createElement('img'); |
| img.src = 'chrome://resources/images/arrow_down.svg'; |
| img.onload = () => domAutomationController.send('LOADED'); |
| img.onerror = e => domAutomationController.send('ERROR: ' + e); |
| )"; |
| std::string result; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractString(active_web_contents(), |
| kScript, &result)); |
| EXPECT_EQ("LOADED", result); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(CrossOriginReadBlockingExtensionAllowlistingTest, |
| ProgrammaticContentScriptVsAppCache) { |
| // Load the test extension. |
| ASSERT_TRUE(InstallExtension()); |
| |
| // Set up http server serving files from content/test/data (which conveniently |
| // already contains appcache-related test files, unlike chrome/test/data). |
| net::EmbeddedTestServer content_test_data_server; |
| content_test_data_server.AddDefaultHandlers( |
| base::FilePath(FILE_PATH_LITERAL("content/test/data"))); |
| ASSERT_TRUE(content_test_data_server.Start()); |
| |
| // Load the main page twice. The second navigation should have AppCache |
| // initialized for the page. |
| GURL main_url = content_test_data_server.GetURL( |
| "/appcache/simple_page_with_manifest.html"); |
| ui_test_utils::NavigateToURL(browser(), main_url); |
| base::string16 expected_title = base::ASCIIToUTF16("AppCache updated"); |
| content::TitleWatcher title_watcher(active_web_contents(), expected_title); |
| EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); |
| ui_test_utils::NavigateToURL(browser(), main_url); |
| |
| // Turn off the server and sanity check that the resource is still available. |
| const char kScriptTemplate[] = R"( |
| new Promise(function (resolve, reject) { |
| var img = document.createElement('img'); |
| img.src = '/appcache/' + $1; |
| img.onload = _ => resolve('IMG LOADED'); |
| img.onerror = reject; |
| }) |
| )"; |
| ASSERT_TRUE(content_test_data_server.ShutdownAndWaitUntilComplete()); |
| EXPECT_EQ("IMG LOADED", |
| content::EvalJs(active_web_contents(), |
| content::JsReplace(kScriptTemplate, "logo.png"))); |
| |
| // Inject a content script and verify that this doesn't negatively impact |
| // AppCache (i.e. verify that |
| // RenderFrameHostImpl::MarkInitiatorsAsRequiringSeparateURLLoaderFactory |
| // does not clobber the default URLLoaderFactory). |
| { |
| SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| base::HistogramTester histograms; |
| GURL cross_site_resource( |
| embedded_test_server()->GetURL("bar.com", "/nosniff.xml")); |
| std::string fetch_result = |
| FetchViaContentScript(cross_site_resource, active_web_contents()); |
| |
| // Verify whether the fetch worked or not (expectations differ depending on |
| // various factors - see the body of VerifyFetchFromContentScript). |
| VerifyFetchFromContentScript(histograms, fetch_result, |
| "nosniff.xml - body\n"); |
| } |
| // Using a different image, to bypass renderer-side caching. |
| EXPECT_EQ("IMG LOADED", |
| content::EvalJs(active_web_contents(), |
| content::JsReplace(kScriptTemplate, "logo2.png"))); |
| |
| // Crash the network service and wait for things to come back up. This (and |
| // the remaining part of the test) only makes sense if 1) the network service |
| // is enabled and running in a separate process and 2) the frame has at least |
| // one network-bound URLLoaderFactory (i.e. the test extension is |
| // allowlisted). |
| if (!content::IsOutOfProcessNetworkService() || !IsExtensionAllowlisted()) |
| return; |
| SimulateNetworkServiceCrash(); |
| active_web_contents() |
| ->GetMainFrame() |
| ->FlushNetworkAndNavigationInterfacesForTesting(); |
| |
| // Make sure that both requests still work - the code should have recovered |
| // from the crash by 1) refreshing the URLLoaderFactory for the content script |
| // and 2) without cloberring the default factory for the AppCache. |
| { |
| SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| base::HistogramTester histograms; |
| GURL cross_site_resource( |
| embedded_test_server()->GetURL("bar.com", "/nosniff.xml")); |
| std::string fetch_result = |
| FetchViaContentScript(cross_site_resource, active_web_contents()); |
| |
| // Verify whether the fetch worked or not (expectations differ depending on |
| // various factors - see the body of VerifyFetchFromContentScript). |
| VerifyFetchFromContentScript(histograms, fetch_result, |
| "nosniff.xml - body\n"); |
| } |
| // Using a different image, to bypass renderer-side caching. |
| EXPECT_EQ("IMG LOADED", |
| content::EvalJs(active_web_contents(), |
| content::JsReplace(kScriptTemplate, "logo3.png"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CrossOriginReadBlockingExtensionTest, |
| WebViewContentScript) { |
| // Load the test app. |
| const Extension* app = InstallApp(); |
| ASSERT_TRUE(app); |
| |
| // Launch the test app and grab its WebContents. |
| content::WebContents* app_contents = nullptr; |
| { |
| content::WebContentsAddedObserver new_contents_observer; |
| OpenApplication(AppLaunchParams( |
| browser()->profile(), app, LAUNCH_CONTAINER_NONE, |
| WindowOpenDisposition::NEW_WINDOW, extensions::SOURCE_TEST)); |
| app_contents = new_contents_observer.GetWebContents(); |
| } |
| ASSERT_TRUE(content::WaitForLoadStop(app_contents)); |
| |
| // Inject a <webview> script and declare desire to inject |
| // cross-origin-fetching content scripts into the guest. |
| const char kWebViewInjectionScriptTemplate[] = R"( |
| document.querySelector('#webview-tag-container').innerHTML = |
| '<webview style="width: 100px; height: 100px;"></webview>'; |
| var webview = document.querySelector('webview'); |
| webview.addContentScripts([{ |
| name: 'rule', |
| matches: ['*://*/*'], |
| js: { code: $1 }, |
| run_at: 'document_start'}]); |
| )"; |
| GURL cross_site_resource( |
| embedded_test_server()->GetURL("bar.com", "/nosniff.xml")); |
| std::string web_view_injection_script = content::JsReplace( |
| kWebViewInjectionScriptTemplate, CreateFetchScript(cross_site_resource)); |
| ASSERT_TRUE(ExecuteScript(app_contents, web_view_injection_script)); |
| |
| // Navigate <webview>, which should trigger content script execution. |
| GURL guest_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); |
| const char kWebViewNavigationScriptTemplate[] = R"( |
| var webview = document.querySelector('webview'); |
| webview.src = $1; |
| )"; |
| std::string web_view_navigation_script = |
| content::JsReplace(kWebViewNavigationScriptTemplate, guest_url); |
| { |
| content::DOMMessageQueue queue; |
| base::HistogramTester histograms; |
| content::ExecuteScriptAsync(app_contents, web_view_navigation_script); |
| std::string fetch_result = PopString(&queue); |
| |
| // Verify that no CORB blocking occurred. |
| EXPECT_EQ("nosniff.xml - body\n", fetch_result); |
| EXPECT_THAT(histograms.GetAllSamples("SiteIsolation.XSD.Browser.Blocked"), |
| testing::IsEmpty()); |
| } |
| } |
| |
| INSTANTIATE_TEST_CASE_P(AllowlistingDisabled, |
| CrossOriginReadBlockingExtensionAllowlistingTest, |
| ::testing::Values(AllowlistingParam(false, false))); |
| INSTANTIATE_TEST_CASE_P(Allowlisted, |
| CrossOriginReadBlockingExtensionAllowlistingTest, |
| ::testing::Values(AllowlistingParam(true, true))); |
| INSTANTIATE_TEST_CASE_P(NotAllowlisted, |
| CrossOriginReadBlockingExtensionAllowlistingTest, |
| ::testing::Values(AllowlistingParam(true, false))); |
| |
| } // namespace extensions |