[Extensions] Update navigations across hypothetical extension extents
Update code to treat navigations across hypothetical extension extents
(e.g. for nonexistent extensions) the same as we do for navigations
crossing installed extension extents.
Bug: 598265
Change-Id: Ibdf2f563ce1fd108ead279077901020a24de732b
Reviewed-on: https://chromium-review.googlesource.com/617180
Commit-Queue: Devlin <[email protected]>
Reviewed-by: Alex Moshchuk <[email protected]>
Reviewed-by: Nasko Oskov <[email protected]>
Cr-Commit-Position: refs/heads/master@{#495779}
diff --git a/chrome/browser/extensions/app_process_apitest.cc b/chrome/browser/extensions/app_process_apitest.cc
index a5fab6b..8ca3335 100644
--- a/chrome/browser/extensions/app_process_apitest.cc
+++ b/chrome/browser/extensions/app_process_apitest.cc
@@ -125,9 +125,9 @@
// Opening tabs with window.open should keep the page in the opener's
// process.
ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
- OpenWindow(tab1, base_url.Resolve("path1/empty.html"), true, NULL);
+ OpenWindow(tab1, base_url.Resolve("path1/empty.html"), true, true, NULL);
LOG(INFO) << "WindowOpenHelper 1.";
- OpenWindow(tab2, base_url.Resolve("path2/empty.html"), true, NULL);
+ OpenWindow(tab2, base_url.Resolve("path2/empty.html"), true, true, NULL);
LOG(INFO) << "End of test.";
UnloadExtension(extension->id());
}
@@ -211,16 +211,16 @@
// Now let's do the same using window.open. The same should happen.
ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
- OpenWindow(tab, base_url.Resolve("path1/empty.html"), true, NULL);
+ OpenWindow(tab, base_url.Resolve("path1/empty.html"), true, true, NULL);
LOG(INFO) << "WindowOpenHelper 1.";
- OpenWindow(tab, base_url.Resolve("path2/empty.html"), true, NULL);
+ OpenWindow(tab, base_url.Resolve("path2/empty.html"), true, true, NULL);
LOG(INFO) << "WindowOpenHelper 2.";
// TODO(creis): This should open in a new process (i.e., false for the last
// argument), but we temporarily avoid swapping processes away from a hosted
// app if it has an opener, because some OAuth providers make script calls
// between non-app popups and non-app iframes in the app process.
// See crbug.com/59285.
- OpenWindow(tab, base_url.Resolve("path3/empty.html"), true, NULL);
+ OpenWindow(tab, base_url.Resolve("path3/empty.html"), true, true, NULL);
LOG(INFO) << "WindowOpenHelper 3.";
// Now let's have these pages navigate, into or out of the extension web
@@ -349,8 +349,8 @@
// Now let's do the same using window.open. The same should happen.
ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
- OpenWindow(tab, base_url.Resolve("path1/empty.html"), true, NULL);
- OpenWindow(tab, base_url.Resolve("path2/empty.html"), true, NULL);
+ OpenWindow(tab, base_url.Resolve("path1/empty.html"), true, true, NULL);
+ OpenWindow(tab, base_url.Resolve("path2/empty.html"), true, true, NULL);
// Now let's have a tab navigate out of and back into the app's web
// extent. Neither navigation should switch processes.
diff --git a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
index 6254ba8..a7904cf6 100644
--- a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
+++ b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
@@ -585,7 +585,9 @@
const Extension* to_extension =
registry->enabled_extensions().GetByID(to_origin.host());
if (!to_extension) {
- *result = true;
+ // Treat non-existent extensions the same as an extension without accessible
+ // resources.
+ *result = false;
return true;
}
diff --git a/chrome/browser/extensions/extension_browsertest.cc b/chrome/browser/extensions/extension_browsertest.cc
index 8203d9e..194cdc6 100644
--- a/chrome/browser/extensions/extension_browsertest.cc
+++ b/chrome/browser/extensions/extension_browsertest.cc
@@ -539,6 +539,7 @@
void ExtensionBrowserTest::OpenWindow(content::WebContents* contents,
const GURL& url,
bool newtab_process_should_equal_opener,
+ bool should_succeed,
content::WebContents** newtab_result) {
content::WebContentsAddedObserver tab_added_observer;
ASSERT_TRUE(content::ExecuteScript(contents,
@@ -546,7 +547,20 @@
content::WebContents* newtab = tab_added_observer.GetWebContents();
ASSERT_TRUE(newtab);
WaitForLoadStop(newtab);
- EXPECT_EQ(url, newtab->GetLastCommittedURL());
+
+ if (should_succeed) {
+ EXPECT_EQ(url, newtab->GetLastCommittedURL());
+ EXPECT_EQ(content::PAGE_TYPE_NORMAL,
+ newtab->GetController().GetLastCommittedEntry()->GetPageType());
+ } else {
+ // "Failure" comes in two forms: redirecting to about:blank or showing an
+ // error page. At least one should be true.
+ EXPECT_TRUE(
+ newtab->GetLastCommittedURL() == GURL(url::kAboutBlankURL) ||
+ newtab->GetController().GetLastCommittedEntry()->GetPageType() ==
+ content::PAGE_TYPE_ERROR);
+ }
+
if (newtab_process_should_equal_opener) {
EXPECT_EQ(contents->GetMainFrame()->GetSiteInstance(),
newtab->GetMainFrame()->GetSiteInstance());
diff --git a/chrome/browser/extensions/extension_browsertest.h b/chrome/browser/extensions/extension_browsertest.h
index 39f3b32f..c1d5a7aa 100644
--- a/chrome/browser/extensions/extension_browsertest.h
+++ b/chrome/browser/extensions/extension_browsertest.h
@@ -276,9 +276,13 @@
// Simulates a page calling window.open on an URL and waits for the
// navigation.
+ // |should_succeed| indicates whether the navigation should succeed, in which
+ // case the last committed url should match the passed url and the page should
+ // not be an error or interstitial page.
void OpenWindow(content::WebContents* contents,
const GURL& url,
bool newtab_process_should_equal_opener,
+ bool should_succeed,
content::WebContents** newtab_result);
// Simulates a page navigating itself to an URL and waits for the
diff --git a/chrome/browser/extensions/isolated_app_browsertest.cc b/chrome/browser/extensions/isolated_app_browsertest.cc
index 3c3733c..71f8f2b 100644
--- a/chrome/browser/extensions/isolated_app_browsertest.cc
+++ b/chrome/browser/extensions/isolated_app_browsertest.cc
@@ -473,13 +473,13 @@
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
// For the third tab, use window.open to keep it in process with an opener.
OpenWindow(browser()->tab_strip_model()->GetWebContentsAt(0),
- base_url.Resolve("app1/main.html"), true, NULL);
+ base_url.Resolve("app1/main.html"), true, true, nullptr);
// In a fourth tab, use window.open to a non-app URL. It should open in a
// separate process, even though this would trigger the OAuth workaround
// for hosted apps (from http://crbug.com/59285).
OpenWindow(browser()->tab_strip_model()->GetWebContentsAt(0),
- base_url.Resolve("non_app/main.html"), false, NULL);
+ base_url.Resolve("non_app/main.html"), false, true, nullptr);
// We should now have four tabs, the first and third sharing a process.
// The second one is an independent instance in a separate process.
diff --git a/chrome/browser/extensions/process_management_browsertest.cc b/chrome/browser/extensions/process_management_browsertest.cc
index f0702c8..44b868f 100644
--- a/chrome/browser/extensions/process_management_browsertest.cc
+++ b/chrome/browser/extensions/process_management_browsertest.cc
@@ -29,6 +29,7 @@
#include "extensions/browser/extension_host.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/process_map.h"
+#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
#include "extensions/common/switches.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
@@ -36,6 +37,8 @@
using content::NavigationController;
using content::WebContents;
+namespace extensions {
+
namespace {
class ProcessManagementTest : public ExtensionBrowserTest {
@@ -65,7 +68,7 @@
ASSERT_TRUE(embedded_test_server()->Start());
gallery_url_ =
embedded_test_server()->GetURL("chrome.webstore.test.com", "/");
- command_line->AppendSwitchASCII(switches::kAppsGalleryURL,
+ command_line->AppendSwitchASCII(::switches::kAppsGalleryURL,
gallery_url_.spec());
}
@@ -448,3 +451,66 @@
EXPECT_EQ(new_site_instance->GetProcess(),
web_contents->GetSiteInstance()->GetProcess());
}
+
+// Check that whether we can access the window object of a window.open()'d url
+// to an extension is the same regardless of whether the extension is installed.
+// https://crbug.com/598265.
+IN_PROC_BROWSER_TEST_F(
+ ProcessManagementTest,
+ TestForkingBehaviorForUninstalledAndNonAccessibleExtensions) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ const Extension* extension =
+ LoadExtension(test_data_dir_.AppendASCII("simple_with_icon"));
+ ASSERT_TRUE(extension);
+ ASSERT_FALSE(
+ WebAccessibleResourcesInfo::HasWebAccessibleResources(extension));
+
+ const GURL installed_extension = extension->url();
+ const GURL nonexistent_extension("chrome-extension://" +
+ std::string(32, 'a') + "/");
+ EXPECT_NE(installed_extension, nonexistent_extension);
+
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("example.com", "/empty.html"));
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ auto can_access_window = [this, web_contents](const GURL& url) {
+ bool can_access = false;
+ const char kOpenNewWindow[] = "window.newWin = window.open('%s');";
+ const char kGetAccess[] =
+ R"(
+ {
+ let canAccess = false;
+ try {
+ window.newWin.document;
+ canAccess = true;
+ } catch (e) {
+ canAccess = false;
+ }
+ window.newWin.close();
+ window.domAutomationController.send(canAccess);
+ }
+ )";
+ EXPECT_TRUE(content::ExecuteScript(
+ web_contents, base::StringPrintf(kOpenNewWindow, url.spec().c_str())));
+
+ // WaitForLoadStop() will return false on a 404, but that can happen if we
+ // navigate to a blocked or nonexistent extension page.
+ ignore_result(content::WaitForLoadStop(
+ browser()->tab_strip_model()->GetActiveWebContents()));
+
+ EXPECT_TRUE(content::ExecuteScriptAndExtractBool(web_contents, kGetAccess,
+ &can_access));
+
+ return can_access;
+ };
+
+ bool can_access_installed = can_access_window(installed_extension);
+ bool can_access_nonexistent = can_access_window(nonexistent_extension);
+ // Behavior for installed and nonexistent extensions should be equivalent.
+ // We don't care much about what the result is (since if it can access it,
+ // it's about:blank); only that the result is safe.
+ EXPECT_EQ(can_access_installed, can_access_nonexistent);
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/window_open_apitest.cc b/chrome/browser/extensions/window_open_apitest.cc
index 3f6bf2e0..86af3e2 100644
--- a/chrome/browser/extensions/window_open_apitest.cc
+++ b/chrome/browser/extensions/window_open_apitest.cc
@@ -246,7 +246,7 @@
WebContents* newtab = NULL;
ASSERT_NO_FATAL_FAILURE(
OpenWindow(browser()->tab_strip_model()->GetActiveWebContents(),
- start_url.Resolve("newtab.html"), true, &newtab));
+ start_url.Resolve("newtab.html"), true, true, &newtab));
bool result = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(newtab, "testExtensionApi()",
@@ -264,25 +264,15 @@
GURL start_url = extension->GetResourceURL("/test.html");
ui_test_utils::NavigateToURL(browser(), start_url);
WebContents* newtab = nullptr;
- bool expect_error_page_in_new_process =
- content::IsBrowserSideNavigationEnabled();
+ bool new_page_in_same_process = true;
+ bool expect_success = false;
ASSERT_NO_FATAL_FAILURE(OpenWindow(
browser()->tab_strip_model()->GetActiveWebContents(),
GURL("chrome-extension://thisissurelynotavalidextensionid/newtab.html"),
- expect_error_page_in_new_process, &newtab));
+ new_page_in_same_process, expect_success, &newtab));
- // This is expected to commit an error page.
- ASSERT_EQ(content::PAGE_TYPE_ERROR,
- newtab->GetController().GetLastCommittedEntry()->GetPageType());
-
- std::string document_body;
- EXPECT_TRUE(content::ExecuteScriptAndExtractString(
- newtab, "domAutomationController.send(document.body.innerText.trim());",
- &document_body));
-
- EXPECT_TRUE(base::StartsWith(document_body,
- "thisissurelynotavalidextensionid is blocked",
- base::CompareCase::SENSITIVE));
+ // This is expected to redirect to about:blank.
+ EXPECT_EQ(GURL(url::kAboutBlankURL), newtab->GetLastCommittedURL());
}
// Tests that calling window.open from the newtab page to an extension URL
@@ -298,10 +288,9 @@
ASSERT_NO_FATAL_FAILURE(
OpenWindow(browser()->tab_strip_model()->GetActiveWebContents(),
GURL(std::string(extensions::kExtensionScheme) +
- url::kStandardSchemeSeparator +
- last_loaded_extension_id() + "/newtab.html"),
- false,
- &newtab));
+ url::kStandardSchemeSeparator +
+ last_loaded_extension_id() + "/newtab.html"),
+ false, true, &newtab));
// Extension API should succeed.
bool result = false;
diff --git a/chrome/common/extensions/extension_process_policy.cc b/chrome/common/extensions/extension_process_policy.cc
index 2b7041b9..ad4e3a7 100644
--- a/chrome/common/extensions/extension_process_policy.cc
+++ b/chrome/common/extensions/extension_process_policy.cc
@@ -62,6 +62,17 @@
return false;
}
+ // If there are no extensions associated with either url, we check if the new
+ // url points to an extension origin. If it does, fork - extension
+ // installation should not be a factor.
+ if (!old_url_extension && !new_url_extension) {
+ // Hypothetically, we could also do an origin check here to make sure that
+ // the two urls point two different extensions, but it's not really
+ // necesary since we know there wasn't an associated extension with the old
+ // url.
+ return new_url.SchemeIs(kExtensionScheme);
+ }
+
return old_url_extension != new_url_extension;
}
diff --git a/chrome/common/extensions/extension_process_policy_unittest.cc b/chrome/common/extensions/extension_process_policy_unittest.cc
new file mode 100644
index 0000000..33bf77e
--- /dev/null
+++ b/chrome/common/extensions/extension_process_policy_unittest.cc
@@ -0,0 +1,63 @@
+// Copyright 2017 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/common/extensions/extension_process_policy.h"
+
+#include "components/crx_file/id_util.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+scoped_refptr<const Extension> CreateExtension(const std::string& id_seed) {
+ std::unique_ptr<base::DictionaryValue> manifest =
+ DictionaryBuilder()
+ .Set("name", "test")
+ .Set("version", "1.0")
+ .Set("manifest_version", 2)
+ .Build();
+ return ExtensionBuilder()
+ .SetManifest(std::move(manifest))
+ .SetID(crx_file::id_util::GenerateId(id_seed))
+ .Build();
+}
+
+} // namespace
+
+TEST(CrossesExtensionBoundaryTest, InstalledExtensions) {
+ ExtensionSet extensions;
+ scoped_refptr<const Extension> extension1 = CreateExtension("a");
+ scoped_refptr<const Extension> extension2 = CreateExtension("b");
+ extensions.Insert(extension1);
+ extensions.Insert(extension2);
+
+ GURL web_url("https://example.com");
+
+ EXPECT_TRUE(CrossesExtensionProcessBoundary(extensions, web_url,
+ extension1->url(), false));
+ EXPECT_TRUE(CrossesExtensionProcessBoundary(extensions, extension1->url(),
+ extension2->url(), false));
+ EXPECT_TRUE(CrossesExtensionProcessBoundary(extensions, extension1->url(),
+ web_url, false));
+}
+
+TEST(CrossesExtensionBoundaryTest, UninstalledExtensions) {
+ ExtensionSet extensions;
+ scoped_refptr<const Extension> extension1 = CreateExtension("a");
+ extensions.Insert(extension1);
+ GURL web_url("https://example.com");
+ GURL non_existent_extension_url("chrome-extension://" + std::string(32, 'a') +
+ "/foo");
+
+ EXPECT_TRUE(CrossesExtensionProcessBoundary(
+ extensions, web_url, non_existent_extension_url, false));
+ EXPECT_TRUE(CrossesExtensionProcessBoundary(
+ extensions, extension1->url(), non_existent_extension_url, false));
+}
+
+} // namespace extensions
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index a04f8fc..9e827dc 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -4180,6 +4180,7 @@
"../common/extensions/chrome_extensions_client_unittest.cc",
"../common/extensions/chrome_manifest_url_handlers_unittest.cc",
"../common/extensions/command_unittest.cc",
+ "../common/extensions/extension_process_policy_unittest.cc",
"../common/extensions/extension_unittest.cc",
"../common/extensions/feature_switch_unittest.cc",
"../common/extensions/manifest_handlers/automation_unittest.cc",