blob: a50d3bd04e00f486aa6260908b11685b31877d76 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/command_line.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/ui_test_utils.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_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/service_worker_test_helpers.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/common/notifications/platform_notification_data.h"
#include "url/gurl.h"
namespace extensions {
class ExtensionResourceRequestPolicyTest : public ExtensionApiTest {
protected:
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
}
void OpenUrlInSubFrameAndVerifyNavigationBlocked(
const GURL& target_url,
const std::string& target_frame_name,
const GURL& expected_navigation_url) {
GURL main_url = embedded_test_server()->GetURL(
"/frame_tree/page_with_two_frames_remote_and_local.html");
ui_test_utils::NavigateToURL(browser(), main_url);
// Navigate |target_frame_name| to |target_url|.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(web_contents, 1);
ASSERT_TRUE(content::ExecJs(
web_contents, content::JsReplace("window.open($1, $2)", target_url,
target_frame_name)));
nav_observer.Wait();
// Verify that the navigation has failed.
//
// It is important that the failure mode below is the same in _all_ of the
// tests like (to prevent fingerprinting):
// - WebNavigationToNonWebAccessibleResource...
// - WebNavigationToNonExistentResource
// - WebNavigationToNonExistentExtension
// - ...
EXPECT_FALSE(nav_observer.last_navigation_succeeded());
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, nav_observer.last_net_error_code());
EXPECT_EQ(expected_navigation_url, nav_observer.last_navigation_url());
}
void OpenUrlInLocalFrameAndVerifyNavigationBlocked(const GURL& target_url) {
// Tentatively check that the renderer-side validation took place. Without
// renderer-side navigation we would still expect browser-side validation to
// result in ERR_BLOCKED_BY_CLIENT (with a different final URL though) -
// this is why the test assertion below is secondary / not that important.
GURL url_blocked_by_renderer("chrome-extension://invalid/");
OpenUrlInSubFrameAndVerifyNavigationBlocked(target_url, "local-frame",
url_blocked_by_renderer);
}
// Used to test that javascript history.back() navigations to a target
// non-web accessible resource are blocked, using remote and local iframes.
void OpenUrlInSubFrameAndVerifyBackNavigationBlocked(
const GURL& target_url,
const std::string& target_frame_id,
const GURL& expected_navigation_url) {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Load up an iframe we can navigate.
ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/frame_tree/page_with_two_frames_remote_and_local.html"));
const char kNavigateScriptTemplate[] = R"(
var iframe = document.getElementById($1);
iframe.src = $2;
)";
{
// Navigate the iframe to an inaccessible resource and expect an error.
content::TestNavigationObserver nav_observer(web_contents);
ASSERT_TRUE(content::ExecJs(
web_contents, content::JsReplace(kNavigateScriptTemplate,
target_frame_id, target_url)));
nav_observer.Wait();
EXPECT_FALSE(nav_observer.last_navigation_succeeded());
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, nav_observer.last_net_error_code());
EXPECT_EQ(expected_navigation_url, nav_observer.last_navigation_url());
}
{
// Navigate the iframe to an accessible page (about:blank).
content::TestNavigationObserver nav_observer(web_contents);
ASSERT_TRUE(content::ExecJs(
web_contents,
content::JsReplace(kNavigateScriptTemplate, target_frame_id,
GURL("about:blank"))));
nav_observer.Wait();
EXPECT_TRUE(nav_observer.last_navigation_succeeded());
}
{
// Finally, trigger a back navigation which should lead to a blocked page.
const char kNavigateBackScriptTemplate[] = R"(
var iframe = document.getElementById($1);
iframe.contentWindow.history.back();
)";
content::TestNavigationObserver nav_observer(web_contents);
ASSERT_TRUE(content::ExecJs(
web_contents,
content::JsReplace(kNavigateBackScriptTemplate, target_frame_id)));
nav_observer.Wait();
EXPECT_FALSE(nav_observer.last_navigation_succeeded());
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, nav_observer.last_net_error_code());
EXPECT_EQ(expected_navigation_url, nav_observer.last_navigation_url());
}
}
};
// Note, this mostly tests the logic of chrome/renderer/extensions/
// extension_resource_request_policy.*, but we have it as a browser test so that
// can make sure it works end-to-end.
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest, OriginPrivileges) {
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("extension")));
GURL web_resource(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"index.html"));
GURL::Replacements make_host_a_com;
make_host_a_com.SetHostStr("a.com");
GURL::Replacements make_host_b_com;
make_host_b_com.SetHostStr("b.com");
// A web host that has permission.
ui_test_utils::NavigateToURL(
browser(), web_resource.ReplaceComponents(make_host_a_com));
std::string result;
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
browser()->tab_strip_model()->GetActiveWebContents(),
"window.domAutomationController.send(document.title)",
&result));
EXPECT_EQ(result, "Loaded");
// A web host that loads a non-existent extension.
GURL non_existent_extension(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"non_existent_extension.html"));
ui_test_utils::NavigateToURL(browser(), non_existent_extension);
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
browser()->tab_strip_model()->GetActiveWebContents(),
"window.domAutomationController.send(document.title)",
&result));
EXPECT_EQ(result, "Image failed to load");
// A data URL. Data URLs should always be able to load chrome-extension://
// resources.
std::string file_source;
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(base::ReadFileToString(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("index.html"),
&file_source));
}
ui_test_utils::NavigateToURL(browser(),
GURL(std::string("data:text/html;charset=utf-8,") + file_source));
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
browser()->tab_strip_model()->GetActiveWebContents(),
"window.domAutomationController.send(document.title)",
&result));
EXPECT_EQ(result, "Loaded");
// A different extension. Legacy (manifest_version 1) extensions should always
// be able to load each other's resources.
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("extension2")));
ui_test_utils::NavigateToURL(
browser(),
GURL("chrome-extension://pbkkcbgdkliohhfaeefcijaghglkahja/index.html"));
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
browser()->tab_strip_model()->GetActiveWebContents(),
"window.domAutomationController.send(document.title)",
&result));
EXPECT_EQ(result, "Loaded");
}
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest,
ExtensionCanLoadHostedAppIcons) {
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("hosted_app")));
ASSERT_TRUE(
RunExtensionSubtest("extension_resource_request_policy/extension2/",
"can_load_icons_from_hosted_apps.html"))
<< message_;
}
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest, Audio) {
EXPECT_TRUE(RunExtensionSubtest(
"extension_resource_request_policy/extension2", "audio.html"))
<< message_;
}
#if defined(OS_MACOSX) || defined(OS_WIN)
// http://crbug.com/238733 - Video is flaky on Mac and Win.
#define MAYBE_Video DISABLED_Video
#else
#define MAYBE_Video Video
#endif
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest, MAYBE_Video) {
EXPECT_TRUE(RunExtensionSubtest(
"extension_resource_request_policy/extension2", "video.html"))
<< message_;
}
// This test times out regularly on win_rel trybots. See http://crbug.com/122154
#if defined(OS_WIN)
#define MAYBE_WebAccessibleResources DISABLED_WebAccessibleResources
#else
#define MAYBE_WebAccessibleResources WebAccessibleResources
#endif
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest,
MAYBE_WebAccessibleResources) {
std::string result;
ASSERT_TRUE(LoadExtension(test_data_dir_
.AppendASCII("extension_resource_request_policy")
.AppendASCII("web_accessible")));
GURL accessible_resource(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"web_accessible/accessible_resource.html"));
ui_test_utils::NavigateToURL(browser(), accessible_resource);
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
browser()->tab_strip_model()->GetActiveWebContents(),
"window.domAutomationController.send(document.title)",
&result));
EXPECT_EQ("Loaded", result);
GURL xhr_accessible_resource(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"web_accessible/xhr_accessible_resource.html"));
ui_test_utils::NavigateToURL(
browser(), xhr_accessible_resource);
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
browser()->tab_strip_model()->GetActiveWebContents(),
"window.domAutomationController.send(document.title)",
&result));
EXPECT_EQ("XHR completed with status: 200", result);
GURL xhr_inaccessible_resource(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"web_accessible/xhr_inaccessible_resource.html"));
ui_test_utils::NavigateToURL(
browser(), xhr_inaccessible_resource);
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
browser()->tab_strip_model()->GetActiveWebContents(),
"window.domAutomationController.send(document.title)",
&result));
EXPECT_EQ("XHR failed to load resource", result);
GURL nonaccessible_resource(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"web_accessible/nonaccessible_resource.html"));
ui_test_utils::NavigateToURL(browser(), nonaccessible_resource);
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
browser()->tab_strip_model()->GetActiveWebContents(),
"window.domAutomationController.send(document.title)",
&result));
EXPECT_EQ("Image failed to load", result);
GURL nonexistent_resource(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"web_accessible/nonexistent_resource.html"));
ui_test_utils::NavigateToURL(browser(), nonexistent_resource);
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
browser()->tab_strip_model()->GetActiveWebContents(),
"window.domAutomationController.send(document.title)",
&result));
EXPECT_EQ("Image failed to load", result);
GURL newtab_page("chrome://newtab");
GURL accessible_newtab_override(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"web_accessible/accessible_history_navigation.html"));
ui_test_utils::NavigateToURL(browser(), newtab_page);
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
browser(), accessible_newtab_override, 1);
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
browser()->tab_strip_model()->GetActiveWebContents(),
"window.domAutomationController.send(document.title)",
&result));
EXPECT_EQ("New Tab Page Loaded Successfully", result);
}
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest,
LinkToWebAccessibleResources) {
std::string result;
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("web_accessible"));
ASSERT_TRUE(extension);
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::NavigationController& controller = web_contents->GetController();
GURL accessible_linked_resource(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"web_accessible/accessible_link_resource.html"));
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
browser(), accessible_linked_resource, 1);
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
web_contents, "window.domAutomationController.send(document.URL)",
&result));
EXPECT_EQ(content::PAGE_TYPE_NORMAL,
controller.GetLastCommittedEntry()->GetPageType());
GURL accessible_url = extension->GetResourceURL("/test.png");
EXPECT_EQ(accessible_url, result);
EXPECT_EQ(accessible_url,
web_contents->GetMainFrame()->GetLastCommittedURL());
GURL nonaccessible_linked_resource(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"web_accessible/nonaccessible_link_resource.html"));
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
browser(), nonaccessible_linked_resource, 1);
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
web_contents, "window.domAutomationController.send(document.URL)",
&result));
EXPECT_EQ(content::PAGE_TYPE_ERROR,
controller.GetLastCommittedEntry()->GetPageType());
EXPECT_EQ("chrome-error://chromewebdata/", result);
GURL invalid_url("chrome-extension://invalid/");
EXPECT_EQ(invalid_url, web_contents->GetMainFrame()->GetLastCommittedURL());
// Redirects can sometimes occur before the load event, so use a
// UrlLoadObserver instead of blocking waiting for two load events.
ui_test_utils::UrlLoadObserver accessible_observer(
accessible_url, content::NotificationService::AllSources());
GURL accessible_client_redirect_resource(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"web_accessible/accessible_redirect_resource.html"));
ui_test_utils::NavigateToURL(browser(), accessible_client_redirect_resource);
accessible_observer.Wait();
EXPECT_EQ(content::PAGE_TYPE_NORMAL,
controller.GetLastCommittedEntry()->GetPageType());
EXPECT_EQ(accessible_url, web_contents->GetLastCommittedURL());
ui_test_utils::UrlLoadObserver nonaccessible_observer(
invalid_url, content::NotificationService::AllSources());
GURL nonaccessible_client_redirect_resource(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"web_accessible/nonaccessible_redirect_resource.html"));
ui_test_utils::NavigateToURL(browser(),
nonaccessible_client_redirect_resource);
nonaccessible_observer.Wait();
EXPECT_EQ(content::PAGE_TYPE_ERROR,
controller.GetLastCommittedEntry()->GetPageType());
EXPECT_EQ(invalid_url, web_contents->GetLastCommittedURL());
}
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest,
WebAccessibleResourcesWithCSP) {
std::string result;
ASSERT_TRUE(LoadExtension(test_data_dir_
.AppendASCII("extension_resource_request_policy")
.AppendASCII("web_accessible")));
GURL accessible_resource_with_csp(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"web_accessible/accessible_resource_with_csp.html"));
ui_test_utils::NavigateToURL(browser(), accessible_resource_with_csp);
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
browser()->tab_strip_model()->GetActiveWebContents(),
"window.domAutomationController.send(document.title)",
&result));
EXPECT_EQ("Loaded", result);
}
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest, Iframe) {
// Load another extension, which the test one shouldn't be able to get
// resources from.
ASSERT_TRUE(LoadExtension(test_data_dir_
.AppendASCII("extension_resource_request_policy")
.AppendASCII("inaccessible")));
EXPECT_TRUE(RunExtensionSubtest(
"extension_resource_request_policy/web_accessible",
"iframe.html")) << message_;
}
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest,
IframeNavigateToInaccessible) {
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("some_accessible")));
GURL iframe_navigate_url(embedded_test_server()->GetURL(
"/extensions/api_test/extension_resource_request_policy/"
"iframe_navigate.html"));
ui_test_utils::NavigateToURL(browser(), iframe_navigate_url);
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
GURL private_page(
"chrome-extension://kegmjfcnjamahdnldjmlpachmpielcdk/private.html");
ASSERT_TRUE(content::ExecuteScript(web_contents, "navigateFrameNow()"));
WaitForLoadStop(web_contents);
EXPECT_NE(private_page, web_contents->GetLastCommittedURL());
std::string content;
EXPECT_TRUE(ExecuteScriptAndExtractString(
ChildFrameAt(web_contents->GetMainFrame(), 0),
"domAutomationController.send(document.body.innerText)", &content));
// The iframe should not load |private_page|, which is not web-accessible.
//
// TODO(alexmos): Make this check stricter, as extensions are now fully
// isolated. The failure mode is that the request is canceled and we stay on
// public.html (see https://crbug.com/656752).
EXPECT_NE("Private", content);
}
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest,
IframeNavigateToInaccessibleViaServerRedirect) {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Any valid extension that happens to have a web accessible resource.
const Extension* patsy = LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("some_accessible"));
// An extension with a non-webaccessible resource.
const Extension* target = LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("inaccessible"));
// Start with an http iframe.
ui_test_utils::NavigateToURL(browser(),
embedded_test_server()->GetURL("/iframe.html"));
// Send it to a web accessible resource of a valid extension.
GURL patsy_url = patsy->GetResourceURL("public.html");
content::NavigateIframeToURL(web_contents, "test", patsy_url);
// Now send it to a NON-web-accessible resource of any other extension, via
// http redirect.
GURL target_url = target->GetResourceURL("inaccessible-iframe-contents.html");
GURL http_redirect_to_target_url =
embedded_test_server()->GetURL("/server-redirect?" + target_url.spec());
content::NavigateIframeToURL(web_contents, "test",
http_redirect_to_target_url);
// That should not have been allowed.
EXPECT_NE(url::Origin::Create(target_url).GetURL(),
ChildFrameAt(web_contents->GetMainFrame(), 0)
->GetLastCommittedOrigin()
.GetURL());
}
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest,
WebNavigationToNonWebAccessibleResource_LocalSubframe) {
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("inaccessible"));
ASSERT_TRUE(extension);
const GURL non_web_accessible_url =
extension->GetResourceURL("inaccessible-iframe-contents.html");
OpenUrlInLocalFrameAndVerifyNavigationBlocked(non_web_accessible_url);
}
// This test tries to ensure that there is no difference between
// 1) navigating to a non-web-accessible-resource of an existing extension
// (tested by WebNavigationToNonWebAccessibleResource_... tests)
// and
// 2a) navigating to a non-existent resource of an existing extension
// (the WebNavigationToNonExistentResource test here)
// and
// 2b) navigating to a resource of a non-existent extension
// (the WebNavigationToNonExistentExtension test below)
//
// The lack of differences is important to prevent web pages from fingerprinting
// (by making it difficult for web pages to detect which extensions are
// present).
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest,
WebNavigationToNonExistentResource) {
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("inaccessible"));
ASSERT_TRUE(extension);
const GURL non_existent_resource_url =
extension->GetResourceURL("no-such-extension-resource.html");
OpenUrlInLocalFrameAndVerifyNavigationBlocked(non_existent_resource_url);
}
// This test tries to ensure that there is no difference between
// 1) navigating to a non-web-accessible-resource of an existing extension
// (tested by WebNavigationToNonWebAccessibleResource_... tests)
// and
// 2a) navigating to a non-existent resource of an existing extension
// (the WebNavigationToNonExistentResource test above)
// and
// 2b) navigating to a resource of a non-existent extension
// (the WebNavigationToNonExistentExtension test here)
//
// The lack of differences is important to prevent web pages from fingerprinting
// (by making it difficult for web pages to detect which extensions are
// present).
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest,
WebNavigationToNonExistentExtension) {
const GURL non_existent_extension_url(
"chrome-extension://aaaaabbbbbcccccdddddeeeeefffffgg/blah.png");
OpenUrlInLocalFrameAndVerifyNavigationBlocked(non_existent_extension_url);
}
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest,
WebNavigationToNonWebAccessibleResource_RemoteSubframe) {
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("inaccessible"));
ASSERT_TRUE(extension);
const GURL non_web_accessible_url =
extension->GetResourceURL("inaccessible-iframe-contents.html");
OpenUrlInSubFrameAndVerifyNavigationBlocked(
non_web_accessible_url, "remote-frame", non_web_accessible_url);
}
// This is a regression test for https://crbug.com/442579.
IN_PROC_BROWSER_TEST_F(
ExtensionResourceRequestPolicyTest,
WebNavigationToNonWebAccessibleResource_FormTargetingNewWindow) {
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("inaccessible"));
ASSERT_TRUE(extension);
const GURL non_web_accessible_url =
extension->GetResourceURL("inaccessible-iframe-contents.html");
GURL main_url = embedded_test_server()->GetURL("/title1.html");
ui_test_utils::NavigateToURL(browser(), main_url);
// Inject and submit a form that will navigate a new window to a
// non-web-accessible-resource. This replicates the repro steps
// from https://crbug.com/442579 (although a simpler repro might
// exist - window.open(non-war-url, '_blank')).
content::WebContentsAddedObserver new_window_observer;
content::WebContents* old_window =
browser()->tab_strip_model()->GetActiveWebContents();
const char* kScriptTemplate = R"(
var f = document.createElement('form');
f.target = "extWindow";
f.action = $1;
f.method = "post";
document.body.appendChild(f);
f.submit();
)";
ASSERT_TRUE(content::ExecJs(
old_window, content::JsReplace(kScriptTemplate, non_web_accessible_url)));
content::WebContents* new_window = new_window_observer.GetWebContents();
content::TestNavigationObserver nav_observer(new_window, 1);
nav_observer.Wait();
// Verify that the navigation has failed.
//
// It is important that the failure mode below is the same in _all_ of the
// tests like (to prevent fingerprinting):
// - WebNavigationToNonWebAccessibleResource...
// - WebNavigationToNonExistentResource
// - WebNavigationToNonExistentExtension
EXPECT_FALSE(nav_observer.last_navigation_succeeded());
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, nav_observer.last_net_error_code());
}
// Tests that a service worker for a web origin can't use client.navigate() to
// navigate to a non-web accessible resource of a Chrome extension.
IN_PROC_BROWSER_TEST_F(
ExtensionResourceRequestPolicyTest,
WebNavigationToNonWebAccessibleResource_ViaServiceWorkerNavigate) {
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("inaccessible"));
ASSERT_TRUE(extension);
const GURL non_web_accessible_url =
extension->GetResourceURL("inaccessible-iframe-contents.html");
// Load a page that registers a service worker.
GURL web_page_url = embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html");
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(), web_page_url);
EXPECT_EQ("DONE", EvalJs(web_contents, "register('client_api_worker.js');"));
// Load the page again so we are controlled.
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html"));
EXPECT_EQ(true, content::EvalJs(web_contents,
"!!navigator.serviceWorker.controller"));
// Have the service worker call client.navigate() on the page.
content::TestNavigationObserver nav_observer(web_contents, 1);
const char kNavigateScriptTemplate[] = R"(
(async () => {
const registration = await navigator.serviceWorker.ready;
registration.active.postMessage({command: 'navigate', url: $1});
return true;
})();
)";
EXPECT_EQ(true, content::EvalJs(web_contents,
content::JsReplace(kNavigateScriptTemplate,
non_web_accessible_url)));
// Verify that the navigation was blocked.
nav_observer.Wait();
EXPECT_FALSE(nav_observer.last_navigation_succeeded());
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, nav_observer.last_net_error_code());
EXPECT_EQ(non_web_accessible_url, nav_observer.last_navigation_url());
ASSERT_TRUE(nav_observer.last_initiator_origin().has_value());
EXPECT_EQ(url::Origin::Create(web_page_url),
nav_observer.last_initiator_origin().value());
}
// Tests that a service worker for a web origin can't use the openWindow API to
// navigate to a non-web accessible resource of a Chrome extension.
IN_PROC_BROWSER_TEST_F(
ExtensionResourceRequestPolicyTest,
WebNavigationToNonWebAccessibleResource_ViaServiceWorkerOpenWindow) {
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("inaccessible"));
ASSERT_TRUE(extension);
const GURL non_web_accessible_url =
extension->GetResourceURL("inaccessible-iframe-contents.html");
// Load a page that registers a service worker.
GURL web_page_url = embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html");
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(), web_page_url);
EXPECT_EQ("DONE",
content::EvalJs(web_contents, "register('client_api_worker.js');"));
// Simulate clicking a notification - this will prompt the test service worker
// to call clients.openWindow(non_web_accessible_url).
content::WebContents* new_window = nullptr;
{
GURL target_url = non_web_accessible_url;
blink::PlatformNotificationData notification_data;
notification_data.body = base::UTF8ToUTF16(target_url.spec());
GURL scope_url = embedded_test_server()->GetURL("/service_worker/");
content::StoragePartition* storage_partition =
content::BrowserContext::GetDefaultStoragePartition(
browser()->profile());
content::ServiceWorkerContext* context =
storage_partition->GetServiceWorkerContext();
content::WebContentsAddedObserver new_window_observer;
content::DispatchServiceWorkerNotificationClick(context, scope_url,
notification_data);
new_window = new_window_observer.GetWebContents();
}
// Verify that the navigation in the new window will be blocked - we are
// disallowing navigations to non-web-accessible-resources.
content::TestNavigationObserver nav_observer(new_window, 1);
nav_observer.Wait();
EXPECT_FALSE(nav_observer.last_navigation_succeeded());
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, nav_observer.last_net_error_code());
EXPECT_EQ(non_web_accessible_url, nav_observer.last_navigation_url());
ASSERT_TRUE(nav_observer.last_initiator_origin().has_value());
EXPECT_EQ(url::Origin::Create(web_page_url),
nav_observer.last_initiator_origin().value());
}
// Tests that a page can't use history.back() on another page to navigate to a
// non-web accessible resource of an extension.
// Regression test for https://crbug.com/1043965.
IN_PROC_BROWSER_TEST_F(ExtensionResourceRequestPolicyTest,
WebNavigationToNonWebAccessibleResource_ViaHistoryBack) {
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("inaccessible"));
ASSERT_TRUE(extension);
const GURL non_web_accessible_url =
extension->GetResourceURL("inaccessible-iframe-contents.html");
GURL main_url = embedded_test_server()->GetURL("/title1.html");
ui_test_utils::NavigateToURL(browser(), main_url);
// Have a page open a new window with JS and retain a reference to it.
content::WebContentsAddedObserver new_window_observer;
content::WebContents* old_window =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(content::ExecJs(
old_window,
content::JsReplace("var newWindow = open($1);", non_web_accessible_url)));
content::WebContents* new_window = new_window_observer.GetWebContents();
content::WaitForLoadStop(new_window);
// As this resource is non-web accessible, we expect an error page.
// NOTE: It would be nice to check for the actual ERR_BLOCKED_BY_CLIENT error,
// but the observer we are using to grab the new page doesn't keep track of
// the navigation handle or any of the specific error codes.
EXPECT_EQ(non_web_accessible_url, new_window->GetLastCommittedURL());
EXPECT_EQ(content::PAGE_TYPE_ERROR,
new_window->GetController().GetLastCommittedEntry()->GetPageType());
{
// Navigate the second window from the first to about:blank.
content::TestNavigationObserver nav_observer(new_window, 1);
ASSERT_TRUE(content::ExecJs(old_window,
"newWindow.location.href = 'about:blank';"));
nav_observer.Wait();
EXPECT_EQ("about:blank", new_window->GetLastCommittedURL());
}
{
// Navigate the second window back using history, which should be blocked.
content::TestNavigationObserver nav_observer(new_window, 1);
ASSERT_TRUE(content::ExecJs(old_window, "newWindow.history.back();"));
nav_observer.Wait();
EXPECT_EQ(non_web_accessible_url, new_window->GetLastCommittedURL());
EXPECT_FALSE(nav_observer.last_navigation_succeeded());
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, nav_observer.last_net_error_code());
EXPECT_EQ(non_web_accessible_url, nav_observer.last_navigation_url());
}
}
// Tests that a page can't use history.back() on a remote iframe to navigate to
// a non-web accessible resource of an extension.
IN_PROC_BROWSER_TEST_F(
ExtensionResourceRequestPolicyTest,
WebNavigationToNonWebAccessibleResource_ViaHistoryBackRemoteIframe) {
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("inaccessible"));
ASSERT_TRUE(extension);
GURL inaccessible_resource =
extension->GetResourceURL("inaccessible-iframe-contents.html");
OpenUrlInSubFrameAndVerifyBackNavigationBlocked(
inaccessible_resource, "remote-frame", inaccessible_resource);
}
// Tests that a page can't use history.back() on a local iframe to navigate to a
// non-web accessible resource of an extension.
IN_PROC_BROWSER_TEST_F(
ExtensionResourceRequestPolicyTest,
WebNavigationToNonWebAccessibleResource_ViaHistoryBackLocalIframe) {
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("extension_resource_request_policy")
.AppendASCII("inaccessible"));
ASSERT_TRUE(extension);
GURL inaccessible_resource =
extension->GetResourceURL("inaccessible-iframe-contents.html");
GURL url_blocked_by_renderer("chrome-extension://invalid/");
OpenUrlInSubFrameAndVerifyBackNavigationBlocked(
inaccessible_resource, "local-frame", url_blocked_by_renderer);
}
} // namespace extensions