summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-22 23:56:30 +0000
committermpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-22 23:56:30 +0000
commit3a8eecb2bb859491b344be321abb4ff3d2b7f0f3 (patch)
tree07f90f564c2c5d92d38f1fdae15597a523ca31c1
parent58bc899f2ffdc14d78b6f209f57415e79837d7a6 (diff)
downloadchromium_src-3a8eecb2bb859491b344be321abb4ff3d2b7f0f3.zip
chromium_src-3a8eecb2bb859491b344be321abb4ff3d2b7f0f3.tar.gz
chromium_src-3a8eecb2bb859491b344be321abb4ff3d2b7f0f3.tar.bz2
Implement app process model isolation.
The process grouping logic is unfortunately duplicated in SiteInstance and RenderView. URLs that are part of extension X's web extent get converted into a pseudo URL of the form chrome-extension://X/path. This groups pages from an extension app and its offline resources into the same process. The rest is mostly plumbing and passing data around. BUG=41273 Review URL: http://codereview.chromium.org/1735004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@45384 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/browser.cc2
-rw-r--r--chrome/browser/browsing_instance.cc8
-rw-r--r--chrome/browser/extensions/app_process_apitest.cc107
-rw-r--r--chrome/browser/extensions/extension_browsertests_misc.cc18
-rw-r--r--chrome/browser/extensions/extension_host.cc1
-rw-r--r--chrome/browser/notifications/balloon_host.cc4
-rw-r--r--chrome/browser/renderer_host/browser_render_process_host.cc33
-rw-r--r--chrome/browser/renderer_host/browser_render_process_host.h3
-rw-r--r--chrome/browser/renderer_host/render_view_host.cc9
-rw-r--r--chrome/browser/renderer_host/render_view_host.h9
-rw-r--r--chrome/browser/renderer_host/site_instance.cc29
-rw-r--r--chrome/browser/renderer_host/site_instance.h11
-rw-r--r--chrome/browser/renderer_host/test/site_instance_unittest.cc32
-rw-r--r--chrome/browser/tab_contents/render_view_host_manager.cc42
-rw-r--r--chrome/browser/tab_contents/render_view_host_manager.h8
-rw-r--r--chrome/browser/tab_contents/tab_contents.cc6
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/extensions/extension_extent.cc2
-rw-r--r--chrome/common/render_messages.h47
-rw-r--r--chrome/common/render_messages_internal.h3
-rw-r--r--chrome/renderer/render_thread.cc23
-rw-r--r--chrome/renderer/render_thread.h22
-rw-r--r--chrome/renderer/render_view.cc24
-rw-r--r--chrome/test/data/extensions/api_test/app_process/manifest.json15
-rw-r--r--chrome/test/data/extensions/api_test/app_process/path1/empty.html1
-rw-r--r--chrome/test/data/extensions/api_test/app_process/path2/empty.html1
-rw-r--r--chrome/test/data/extensions/api_test/app_process/path3/empty.html1
-rw-r--r--chrome/test/data/extensions/api_test/app_process/test.html25
28 files changed, 430 insertions, 57 deletions
diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc
index 216533c..5d9495f 100644
--- a/chrome/browser/browser.cc
+++ b/chrome/browser/browser.cc
@@ -3333,7 +3333,7 @@ void Browser::OpenURLAtIndex(TabContents* source,
if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessPerTab)) {
if (current_tab) {
const GURL& current_url = current_tab->GetURL();
- if (SiteInstance::IsSameWebSite(current_url, url))
+ if (SiteInstance::IsSameWebSite(profile_, current_url, url))
instance = current_tab->GetSiteInstance();
}
}
diff --git a/chrome/browser/browsing_instance.cc b/chrome/browser/browsing_instance.cc
index ff3346a..aba73b2 100644
--- a/chrome/browser/browsing_instance.cc
+++ b/chrome/browser/browsing_instance.cc
@@ -46,7 +46,7 @@ bool BrowsingInstance::ShouldUseProcessPerSite(const GURL& url) {
BrowsingInstance::SiteInstanceMap* BrowsingInstance::GetSiteInstanceMap(
Profile* profile, const GURL& url) {
- if (!ShouldUseProcessPerSite(url)) {
+ if (!ShouldUseProcessPerSite(SiteInstance::GetEffectiveURL(profile, url))) {
// Not using process-per-site, so use a map specific to this instance.
return &site_instance_map_;
}
@@ -59,7 +59,8 @@ BrowsingInstance::SiteInstanceMap* BrowsingInstance::GetSiteInstanceMap(
}
bool BrowsingInstance::HasSiteInstance(const GURL& url) {
- std::string site = SiteInstance::GetSiteForURL(url).possibly_invalid_spec();
+ std::string site =
+ SiteInstance::GetSiteForURL(profile_, url).possibly_invalid_spec();
SiteInstanceMap* map = GetSiteInstanceMap(profile_, url);
SiteInstanceMap::iterator i = map->find(site);
@@ -67,7 +68,8 @@ bool BrowsingInstance::HasSiteInstance(const GURL& url) {
}
SiteInstance* BrowsingInstance::GetSiteInstanceForURL(const GURL& url) {
- std::string site = SiteInstance::GetSiteForURL(url).possibly_invalid_spec();
+ std::string site =
+ SiteInstance::GetSiteForURL(profile_, url).possibly_invalid_spec();
SiteInstanceMap* map = GetSiteInstanceMap(profile_, url);
SiteInstanceMap::iterator i = map->find(site);
diff --git a/chrome/browser/extensions/app_process_apitest.cc b/chrome/browser/extensions/app_process_apitest.cc
new file mode 100644
index 0000000..ecf0c63
--- /dev/null
+++ b/chrome/browser/extensions/app_process_apitest.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2010 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/browser.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_host.h"
+#include "chrome/browser/extensions/extension_process_manager.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/ui_test_utils.h"
+#include "net/base/mock_host_resolver.h"
+
+class AppApiTest : public ExtensionApiTest {
+ public:
+ void SetUpCommandLine(CommandLine* command_line) {
+ ExtensionApiTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitch(switches::kEnableExtensionApps);
+ }
+};
+
+// Simulates a page calling window.open on an URL, and waits for the navigation.
+static void WindowOpenHelper(Browser* browser,
+ RenderViewHost* opener_host, const GURL& url) {
+ bool result = false;
+ ui_test_utils::ExecuteJavaScriptAndExtractBool(
+ opener_host, L"",
+ L"window.open('" + UTF8ToWide(url.spec()) + L"');"
+ L"window.domAutomationController.send(true);",
+ &result);
+ ASSERT_TRUE(result);
+
+ // Now the current tab should be the new tab.
+ TabContents* newtab = browser->GetSelectedTabContents();
+ if (!newtab->controller().GetLastCommittedEntry() ||
+ newtab->controller().GetLastCommittedEntry()->url() != url)
+ ui_test_utils::WaitForNavigation(&newtab->controller());
+ EXPECT_EQ(url, newtab->controller().GetLastCommittedEntry()->url());
+}
+
+// Simulates a page navigating itself to an URL, and waits for the navigation.
+static void NavigateTabHelper(TabContents* contents, const GURL& url) {
+ bool result = false;
+ ui_test_utils::ExecuteJavaScriptAndExtractBool(
+ contents->render_view_host(), L"",
+ L"window.location = '" + UTF8ToWide(url.spec()) + L"';"
+ L"window.domAutomationController.send(true);",
+ &result);
+ ASSERT_TRUE(result);
+
+ if (contents->controller().GetLastCommittedEntry() ||
+ contents->controller().GetLastCommittedEntry()->url() != url)
+ ui_test_utils::WaitForNavigation(&contents->controller());
+ EXPECT_EQ(url, contents->controller().GetLastCommittedEntry()->url());
+}
+
+IN_PROC_BROWSER_TEST_F(AppApiTest, AppProcess) {
+ host_resolver()->AddRule("*", "127.0.0.1");
+ StartHTTPServer();
+
+ ASSERT_TRUE(RunExtensionTest("app_process")) << message_;
+
+ Extension* extension = GetSingleLoadedExtension();
+ ExtensionHost* host = browser()->profile()->GetExtensionProcessManager()->
+ GetBackgroundHostForExtension(extension);
+ ASSERT_TRUE(host);
+
+ // The extension should have opened 3 new tabs. Including the original blank
+ // tab, we now have 4 tabs. Two should be part of the extension app, and
+ // grouped in the extension process.
+ ASSERT_EQ(4, browser()->tab_count());
+ EXPECT_EQ(host->render_process_host(),
+ browser()->GetTabContentsAt(1)->render_view_host()->process());
+ EXPECT_EQ(host->render_process_host(),
+ browser()->GetTabContentsAt(2)->render_view_host()->process());
+ EXPECT_NE(host->render_process_host(),
+ browser()->GetTabContentsAt(3)->render_view_host()->process());
+
+ // Now let's do the same using window.open. The same should happen.
+ WindowOpenHelper(browser(), host->render_view_host(),
+ browser()->GetTabContentsAt(1)->GetURL());
+ WindowOpenHelper(browser(), host->render_view_host(),
+ browser()->GetTabContentsAt(2)->GetURL());
+ WindowOpenHelper(browser(), host->render_view_host(),
+ browser()->GetTabContentsAt(3)->GetURL());
+
+ ASSERT_EQ(7, browser()->tab_count());
+ EXPECT_EQ(host->render_process_host(),
+ browser()->GetTabContentsAt(4)->render_view_host()->process());
+ EXPECT_EQ(host->render_process_host(),
+ browser()->GetTabContentsAt(5)->render_view_host()->process());
+ EXPECT_NE(host->render_process_host(),
+ browser()->GetTabContentsAt(6)->render_view_host()->process());
+
+ // Now let's have these pages navigate, into or out of the extension web
+ // extent. They should switch processes.
+ const GURL& app_url(browser()->GetTabContentsAt(1)->GetURL());
+ const GURL& non_app_url(browser()->GetTabContentsAt(3)->GetURL());
+ NavigateTabHelper(browser()->GetTabContentsAt(1), non_app_url);
+ NavigateTabHelper(browser()->GetTabContentsAt(3), app_url);
+ EXPECT_NE(host->render_process_host(),
+ browser()->GetTabContentsAt(1)->render_view_host()->process());
+ EXPECT_EQ(host->render_process_host(),
+ browser()->GetTabContentsAt(3)->render_view_host()->process());
+}
diff --git a/chrome/browser/extensions/extension_browsertests_misc.cc b/chrome/browser/extensions/extension_browsertests_misc.cc
index bea4d81..24e90f3 100644
--- a/chrome/browser/extensions/extension_browsertests_misc.cc
+++ b/chrome/browser/extensions/extension_browsertests_misc.cc
@@ -611,11 +611,10 @@ static TabContents* WindowOpenHelper(Browser* browser, const GURL& start_url,
// Now the current tab should be the new tab.
TabContents* newtab = browser->GetSelectedTabContents();
GURL expected_url = start_url.Resolve(newtab_url);
- if (newtab->GetURL() != expected_url) {
- ui_test_utils::WaitForNavigation(
- &browser->GetSelectedTabContents()->controller());
- }
- EXPECT_EQ(newtab->GetURL(), expected_url);
+ if (!newtab->controller().GetLastCommittedEntry() ||
+ newtab->controller().GetLastCommittedEntry()->url() != expected_url)
+ ui_test_utils::WaitForNavigation(&newtab->controller());
+ EXPECT_EQ(expected_url, newtab->controller().GetLastCommittedEntry()->url());
return newtab;
}
@@ -654,8 +653,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, WindowOpenInvalidExtension) {
}
// Tests that calling window.open from the newtab page to an extension URL
-// does not give the new window extension privileges - because the opening page
-// does not have extension privileges.
+// gives the new window extension privileges - even though the opening page
+// does not have extension privileges, we break the script connection, so
+// there is no privilege leak.
IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, WindowOpenNoPrivileges) {
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("uitest").AppendASCII("window_open")));
@@ -666,11 +666,11 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, WindowOpenNoPrivileges) {
std::string("chrome-extension://") + last_loaded_extension_id_ +
"/newtab.html");
- // Extension API should fail.
+ // Extension API should succeed.
bool result = false;
ui_test_utils::ExecuteJavaScriptAndExtractBool(
newtab->render_view_host(), L"", L"testExtensionApi()", &result);
- EXPECT_FALSE(result);
+ EXPECT_TRUE(result);
}
#if !defined(OS_WIN)
diff --git a/chrome/browser/extensions/extension_host.cc b/chrome/browser/extensions/extension_host.cc
index 78a3e5b..234e352 100644
--- a/chrome/browser/extensions/extension_host.cc
+++ b/chrome/browser/extensions/extension_host.cc
@@ -132,6 +132,7 @@ ExtensionHost::ExtensionHost(Extension* extension, SiteInstance* site_instance,
dom_storage_context()->AllocateSessionStorageNamespaceId();
render_view_host_ = new RenderViewHost(site_instance, this, MSG_ROUTING_NONE,
session_storage_namespace_id);
+ render_view_host_->set_is_extension_process(true);
render_view_host_->AllowBindings(BindingsPolicy::EXTENSION);
if (enable_dom_automation_)
render_view_host_->AllowBindings(BindingsPolicy::DOM_AUTOMATION);
diff --git a/chrome/browser/notifications/balloon_host.cc b/chrome/browser/notifications/balloon_host.cc
index 845ebee..d1786d9 100644
--- a/chrome/browser/notifications/balloon_host.cc
+++ b/chrome/browser/notifications/balloon_host.cc
@@ -127,8 +127,10 @@ void BalloonHost::Init() {
extension_function_dispatcher_.reset(
ExtensionFunctionDispatcher::Create(
rvh, this, balloon_->notification().content_url()));
- if (extension_function_dispatcher_.get())
+ if (extension_function_dispatcher_.get()) {
rvh->AllowBindings(BindingsPolicy::EXTENSION);
+ rvh->set_is_extension_process(true);
+ }
// Do platform-specific initialization.
render_view_host_ = rvh;
diff --git a/chrome/browser/renderer_host/browser_render_process_host.cc b/chrome/browser/renderer_host/browser_render_process_host.cc
index 6aca057..f54afd9 100644
--- a/chrome/browser/renderer_host/browser_render_process_host.cc
+++ b/chrome/browser/renderer_host/browser_render_process_host.cc
@@ -201,6 +201,10 @@ BrowserRenderProcessHost::BrowserRenderProcessHost(Profile* profile)
registrar_.Add(this, NotificationType::USER_SCRIPTS_UPDATED,
Source<Profile>(profile));
+ registrar_.Add(this, NotificationType::EXTENSION_LOADED,
+ Source<Profile>(profile));
+ registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
+ Source<Profile>(profile));
registrar_.Add(this, NotificationType::SPELLCHECK_HOST_REINITIALIZED,
NotificationService::AllSources());
registrar_.Add(this, NotificationType::SPELLCHECK_WORD_ADDED,
@@ -644,6 +648,27 @@ void BrowserRenderProcessHost::SendUserScriptsUpdate(
}
}
+void BrowserRenderProcessHost::SendExtensionExtentsUpdate() {
+ // Check if the process is still starting and we don't have a handle for it
+ // yet, in which case this will happen later when InitVisitedLinks is called.
+ if (!run_renderer_in_process() &&
+ (!child_process_.get() || child_process_->IsStarting())) {
+ return;
+ }
+
+ ExtensionsService* service = profile()->GetExtensionsService();
+ ViewMsg_ExtensionExtentsUpdated_Params params;
+ for (size_t i = 0; i < service->extensions()->size(); ++i) {
+ Extension* extension = service->extensions()->at(i);
+ if (!extension->web_extent().origin().is_empty()) {
+ params.extension_apps.push_back(
+ make_pair(extension->id(), extension->web_extent()));
+ }
+ }
+
+ Send(new ViewMsg_ExtensionExtentsUpdated(params));
+}
+
bool BrowserRenderProcessHost::FastShutdownIfPossible() {
if (run_renderer_in_process())
return false; // Single process mode can't do fast shutdown.
@@ -909,6 +934,13 @@ void BrowserRenderProcessHost::Observe(NotificationType type,
}
break;
}
+ case NotificationType::EXTENSION_LOADED:
+ case NotificationType::EXTENSION_UNLOADED: {
+ Extension* extension = Details<Extension>(details).ptr();
+ if (!extension->web_extent().origin().is_empty())
+ SendExtensionExtentsUpdate();
+ break;
+ }
case NotificationType::SPELLCHECK_HOST_REINITIALIZED: {
InitSpellChecker();
break;
@@ -941,6 +973,7 @@ void BrowserRenderProcessHost::OnProcessLaunched() {
InitVisitedLinks();
InitUserScripts();
InitExtensions();
+ SendExtensionExtentsUpdate();
// We don't want to initialize the spellchecker unless SpellCheckHost has been
// created. In InitSpellChecker(), we know if GetSpellCheckHost() is NULL
// then the spellchecker has been turned off, but here, we don't know if
diff --git a/chrome/browser/renderer_host/browser_render_process_host.h b/chrome/browser/renderer_host/browser_render_process_host.h
index 277a53e..2a7f8b6 100644
--- a/chrome/browser/renderer_host/browser_render_process_host.h
+++ b/chrome/browser/renderer_host/browser_render_process_host.h
@@ -126,6 +126,9 @@ class BrowserRenderProcessHost : public RenderProcessHost,
// Sends the renderer process a new set of user scripts.
void SendUserScriptsUpdate(base::SharedMemory* shared_memory);
+ // Sends the renderer process a new set of extension extents.
+ void SendExtensionExtentsUpdate();
+
// Generates a command line to be used to spawn a renderer and appends the
// results to |*command_line|.
void AppendRendererCommandLine(CommandLine* command_line) const;
diff --git a/chrome/browser/renderer_host/render_view_host.cc b/chrome/browser/renderer_host/render_view_host.cc
index 4e68666..e1ac486 100644
--- a/chrome/browser/renderer_host/render_view_host.cc
+++ b/chrome/browser/renderer_host/render_view_host.cc
@@ -125,7 +125,8 @@ RenderViewHost::RenderViewHost(SiteInstance* instance,
unload_ack_is_for_cross_site_transition_(false),
are_javascript_messages_suppressed_(false),
sudden_termination_allowed_(false),
- session_storage_namespace_id_(session_storage_namespace_id) {
+ session_storage_namespace_id_(session_storage_namespace_id),
+ is_extension_process_(false) {
DCHECK(instance_);
DCHECK(delegate_);
}
@@ -155,9 +156,7 @@ bool RenderViewHost::CreateRenderView(
// initialized it) or may not (we have our own process or the old process
// crashed) have been initialized. Calling Init multiple times will be
// ignored, so this is safe.
- bool is_extensions_process =
- BindingsPolicy::is_extension_enabled(enabled_bindings_);
- if (!process()->Init(is_extensions_process, request_context))
+ if (!process()->Init(is_extension_process_, request_context))
return false;
DCHECK(process()->HasConnection());
DCHECK(process()->profile());
@@ -167,7 +166,7 @@ bool RenderViewHost::CreateRenderView(
process()->id());
}
- if (is_extensions_process) {
+ if (BindingsPolicy::is_extension_enabled(enabled_bindings_)) {
ChildProcessSecurityPolicy::GetInstance()->GrantExtensionBindings(
process()->id());
diff --git a/chrome/browser/renderer_host/render_view_host.h b/chrome/browser/renderer_host/render_view_host.h
index b971679..93c6158 100644
--- a/chrome/browser/renderer_host/render_view_host.h
+++ b/chrome/browser/renderer_host/render_view_host.h
@@ -315,6 +315,11 @@ class RenderViewHost : public RenderWidgetHost {
// RenderView. See BindingsPolicy for details.
int enabled_bindings() { return enabled_bindings_; }
+ // See variable comment.
+ void set_is_extension_process(bool is_extension_process) {
+ is_extension_process_ = is_extension_process;
+ }
+
// Sets a property with the given name and value on the DOM UI binding object.
// Must call AllowDOMUIBindings() on this renderer first.
void SetDOMUIProperty(const std::string& name, const std::string& value);
@@ -690,6 +695,10 @@ class RenderViewHost : public RenderWidgetHost {
// The session storage namespace id to be used by the associated render view.
int64 session_storage_namespace_id_;
+ // Whether this render view will be used for extensions. This controls
+ // what process type we use.
+ bool is_extension_process_;
+
DISALLOW_COPY_AND_ASSIGN(RenderViewHost);
};
diff --git a/chrome/browser/renderer_host/site_instance.cc b/chrome/browser/renderer_host/site_instance.cc
index 337ce3d..62c6bd7 100644
--- a/chrome/browser/renderer_host/site_instance.cc
+++ b/chrome/browser/renderer_host/site_instance.cc
@@ -6,6 +6,7 @@
#include "chrome/browser/browsing_instance.h"
#include "chrome/browser/dom_ui/dom_ui_factory.h"
+#include "chrome/browser/extensions/extensions_service.h"
#include "chrome/browser/renderer_host/browser_render_process_host.h"
#include "chrome/common/url_constants.h"
#include "chrome/common/notification_service.h"
@@ -79,7 +80,7 @@ void SiteInstance::SetSite(const GURL& url) {
// Remember that this SiteInstance has been used to load a URL, even if the
// URL is invalid.
has_site_ = true;
- site_ = GetSiteForURL(url);
+ site_ = GetSiteForURL(browsing_instance_->profile(), url);
// Now that we have a site, register it with the BrowsingInstance. This
// ensures that we won't create another SiteInstance for this site within
@@ -111,7 +112,9 @@ SiteInstance* SiteInstance::CreateSiteInstanceForURL(Profile* profile,
}
/*static*/
-GURL SiteInstance::GetSiteForURL(const GURL& url) {
+GURL SiteInstance::GetSiteForURL(Profile* profile, const GURL& real_url) {
+ GURL url = GetEffectiveURL(profile, real_url);
+
// URLs with no host should have an empty site.
GURL site;
@@ -144,7 +147,11 @@ GURL SiteInstance::GetSiteForURL(const GURL& url) {
}
/*static*/
-bool SiteInstance::IsSameWebSite(const GURL& url1, const GURL& url2) {
+bool SiteInstance::IsSameWebSite(Profile* profile,
+ const GURL& real_url1, const GURL& real_url2) {
+ GURL url1 = GetEffectiveURL(profile, real_url1);
+ GURL url2 = GetEffectiveURL(profile, real_url2);
+
// We infer web site boundaries based on the registered domain name of the
// top-level page and the scheme. We do not pay attention to the port if
// one is present, because pages served from different ports can still
@@ -167,6 +174,22 @@ bool SiteInstance::IsSameWebSite(const GURL& url1, const GURL& url2) {
return net::RegistryControlledDomainService::SameDomainOrHost(url1, url2);
}
+/*static*/
+GURL SiteInstance::GetEffectiveURL(Profile* profile, const GURL& url) {
+ if (!profile || !profile->GetExtensionsService())
+ return url;
+
+ Extension* extension =
+ profile->GetExtensionsService()->GetExtensionByWebExtent(url);
+ if (extension) {
+ // If the URL is part of an extension's web extent, convert it to an
+ // extension URL.
+ return extension->GetResourceURL(url.path());
+ } else {
+ return url;
+ }
+}
+
RenderProcessHost::Type SiteInstance::GetRendererType() {
// We may not have a site at this point, which generally means this is a
// normal navigation.
diff --git a/chrome/browser/renderer_host/site_instance.h b/chrome/browser/renderer_host/site_instance.h
index 9c2f3f8..4fbbdc8 100644
--- a/chrome/browser/renderer_host/site_instance.h
+++ b/chrome/browser/renderer_host/site_instance.h
@@ -117,7 +117,7 @@ class SiteInstance : public base::RefCounted<SiteInstance>,
// Returns the site for the given URL, which includes only the scheme and
// registered domain. Returns an empty GURL if the URL has no host.
- static GURL GetSiteForURL(const GURL& url);
+ static GURL GetSiteForURL(Profile* profile, const GURL& url);
// Return whether both URLs are part of the same web site, for the purpose of
// assigning them to processes accordingly. The decision is currently based
@@ -126,7 +126,8 @@ class SiteInstance : public base::RefCounted<SiteInstance>,
// the same process if they can communicate with other via JavaScript.
// (e.g., docs.google.com and mail.google.com have DOM access to each other
// if they both set their document.domain properties to google.com.)
- static bool IsSameWebSite(const GURL& url1, const GURL& url2);
+ static bool IsSameWebSite(Profile* profile,
+ const GURL& url1, const GURL& url2);
protected:
friend class base::RefCounted<SiteInstance>;
@@ -140,6 +141,12 @@ class SiteInstance : public base::RefCounted<SiteInstance>,
// GetRelatedSiteInstance instead.
explicit SiteInstance(BrowsingInstance* browsing_instance);
+ // Get the effective URL for the given actual URL. If the URL is part of an
+ // installed app, the effective URL is an extension URL with the ID of that
+ // extension as the host. This has the effect of grouping apps together in
+ // a common SiteInstance.
+ static GURL GetEffectiveURL(Profile* profile, const GURL& url);
+
// Returns the type of renderer process this instance belongs in, for grouping
// purposes.
RenderProcessHost::Type GetRendererType();
diff --git a/chrome/browser/renderer_host/test/site_instance_unittest.cc b/chrome/browser/renderer_host/test/site_instance_unittest.cc
index 2c99835..cf5c154 100644
--- a/chrome/browser/renderer_host/test/site_instance_unittest.cc
+++ b/chrome/browser/renderer_host/test/site_instance_unittest.cc
@@ -216,27 +216,29 @@ TEST_F(SiteInstanceTest, SetSite) {
TEST_F(SiteInstanceTest, GetSiteForURL) {
// Pages are irrelevant.
GURL test_url = GURL("http://www.google.com/index.html");
- EXPECT_EQ(GURL("http://google.com"), SiteInstance::GetSiteForURL(test_url));
+ EXPECT_EQ(GURL("http://google.com"),
+ SiteInstance::GetSiteForURL(NULL, test_url));
// Ports are irrlevant.
test_url = GURL("https://www.google.com:8080");
- EXPECT_EQ(GURL("https://google.com"), SiteInstance::GetSiteForURL(test_url));
+ EXPECT_EQ(GURL("https://google.com"),
+ SiteInstance::GetSiteForURL(NULL, test_url));
// Javascript URLs have no site.
test_url = GURL("javascript:foo();");
- EXPECT_EQ(GURL(), SiteInstance::GetSiteForURL(test_url));
+ EXPECT_EQ(GURL(), SiteInstance::GetSiteForURL(NULL, test_url));
test_url = GURL("http://foo/a.html");
- EXPECT_EQ(GURL("http://foo"), SiteInstance::GetSiteForURL(test_url));
+ EXPECT_EQ(GURL("http://foo"), SiteInstance::GetSiteForURL(NULL, test_url));
test_url = GURL("file:///C:/Downloads/");
- EXPECT_EQ(GURL(), SiteInstance::GetSiteForURL(test_url));
+ EXPECT_EQ(GURL(), SiteInstance::GetSiteForURL(NULL, test_url));
// TODO(creis): Do we want to special case file URLs to ensure they have
// either no site or a special "file://" site? We currently return
// "file://home/" as the site, which seems broken.
// test_url = GURL("file://home/");
- // EXPECT_EQ(GURL(), SiteInstance::GetSiteForURL(test_url));
+ // EXPECT_EQ(GURL(), SiteInstance::GetSiteForURL(NULL, test_url));
}
// Test of distinguishing URLs from different sites. Most of this logic is
@@ -253,24 +255,24 @@ TEST_F(SiteInstanceTest, IsSameWebSite) {
GURL url_shorthang = GURL(chrome::kAboutShorthangURL);
// Same scheme and port -> same site.
- EXPECT_TRUE(SiteInstance::IsSameWebSite(url_foo, url_foo2));
+ EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_foo, url_foo2));
// Different scheme -> different site.
- EXPECT_FALSE(SiteInstance::IsSameWebSite(url_foo, url_foo_https));
+ EXPECT_FALSE(SiteInstance::IsSameWebSite(NULL, url_foo, url_foo_https));
// Different port -> same site.
// (Changes to document.domain make renderer ignore the port.)
- EXPECT_TRUE(SiteInstance::IsSameWebSite(url_foo, url_foo_port));
+ EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_foo, url_foo_port));
// JavaScript links should be considered same site for anything.
- EXPECT_TRUE(SiteInstance::IsSameWebSite(url_javascript, url_foo));
- EXPECT_TRUE(SiteInstance::IsSameWebSite(url_javascript, url_foo_https));
- EXPECT_TRUE(SiteInstance::IsSameWebSite(url_javascript, url_foo_port));
+ EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_javascript, url_foo));
+ EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_javascript, url_foo_https));
+ EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_javascript, url_foo_port));
// The crash/hang URLs should also be treated as same site. (Bug 1143809.)
- EXPECT_TRUE(SiteInstance::IsSameWebSite(url_crash, url_foo));
- EXPECT_TRUE(SiteInstance::IsSameWebSite(url_hang, url_foo));
- EXPECT_TRUE(SiteInstance::IsSameWebSite(url_shorthang, url_foo));
+ EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_crash, url_foo));
+ EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_hang, url_foo));
+ EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_shorthang, url_foo));
}
// Test to ensure that there is only one SiteInstance per site in a given
diff --git a/chrome/browser/tab_contents/render_view_host_manager.cc b/chrome/browser/tab_contents/render_view_host_manager.cc
index a07f4fb..9c555fb 100644
--- a/chrome/browser/tab_contents/render_view_host_manager.cc
+++ b/chrome/browser/tab_contents/render_view_host_manager.cc
@@ -8,6 +8,8 @@
#include "base/logging.h"
#include "chrome/browser/dom_ui/dom_ui.h"
#include "chrome/browser/dom_ui/dom_ui_factory.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/profile.h"
#include "chrome/browser/renderer_host/render_view_host.h"
#include "chrome/browser/renderer_host/render_view_host_delegate.h"
#include "chrome/browser/renderer_host/render_view_host_factory.h"
@@ -80,13 +82,15 @@ RenderViewHost* RenderViewHostManager::Navigate(const NavigationEntry& entry) {
// its first page. (Bug 1145340)
if (dest_render_view_host != render_view_host_ &&
!render_view_host_->IsRenderViewLive()) {
+ // Note: we don't call InitRenderView here because we are navigating away
+ // soon anyway, and we don't have the NavigationEntry for this host.
delegate_->CreateRenderViewForRenderManager(render_view_host_);
}
// If the renderer crashed, then try to create a new one to satisfy this
// navigation request.
if (!dest_render_view_host->IsRenderViewLive()) {
- if (!delegate_->CreateRenderViewForRenderManager(dest_render_view_host))
+ if (!InitRenderView(dest_render_view_host, entry))
return NULL;
// Now that we've created a new renderer, be sure to hide it if it isn't
@@ -400,8 +404,9 @@ SiteInstance* RenderViewHostManager::GetSiteInstanceForEntry(
// practice.)
const GURL& current_url = (curr_entry) ? curr_entry->url() :
curr_instance->site();
+ Profile* profile = controller.profile();
- if (SiteInstance::IsSameWebSite(current_url, dest_url)) {
+ if (SiteInstance::IsSameWebSite(profile, current_url, dest_url)) {
return curr_instance;
} else if (ShouldSwapProcessesForNavigation(curr_entry, &entry)) {
// When we're swapping, we need to force the site instance AND browsing
@@ -410,8 +415,7 @@ SiteInstance* RenderViewHostManager::GetSiteInstanceForEntry(
// Pages), keeping them in the same process. When you navigate away from
// that page, we want to explicity ignore that BrowsingInstance and group
// this page into the appropriate SiteInstance for its URL.
- return SiteInstance::CreateSiteInstanceForURL(
- delegate_->GetControllerForRenderManager().profile(), dest_url);
+ return SiteInstance::CreateSiteInstanceForURL(profile, dest_url);
} else {
// Start the new renderer in a new SiteInstance, but in the current
// BrowsingInstance. It is important to immediately give this new
@@ -422,7 +426,8 @@ SiteInstance* RenderViewHostManager::GetSiteInstanceForEntry(
}
}
-bool RenderViewHostManager::CreatePendingRenderView(SiteInstance* instance) {
+bool RenderViewHostManager::CreatePendingRenderView(
+ const NavigationEntry& entry, SiteInstance* instance) {
NavigationEntry* curr_entry =
delegate_->GetControllerForRenderManager().GetLastCommittedEntry();
if (curr_entry) {
@@ -439,8 +444,7 @@ bool RenderViewHostManager::CreatePendingRenderView(SiteInstance* instance) {
Source<RenderViewHostManager>(this),
Details<RenderViewHost>(pending_render_view_host_));
- bool success = delegate_->CreateRenderViewForRenderManager(
- pending_render_view_host_);
+ bool success = InitRenderView(pending_render_view_host_, entry);
if (success) {
// Don't show the view until we get a DidNavigate from it.
pending_render_view_host_->view()->Hide();
@@ -450,6 +454,28 @@ bool RenderViewHostManager::CreatePendingRenderView(SiteInstance* instance) {
return success;
}
+bool RenderViewHostManager::InitRenderView(RenderViewHost* render_view_host,
+ const NavigationEntry& entry) {
+ // If the pending navigation is to a DOMUI, tell the RenderView about any
+ // bindings it will need enabled.
+ if (pending_dom_ui_.get())
+ render_view_host->AllowBindings(pending_dom_ui_->bindings());
+
+ // Tell the RenderView whether it will be used for an extension process.
+ Profile* profile = delegate_->GetControllerForRenderManager().profile();
+ bool is_extension_process = false;
+ if (entry.url().SchemeIs(chrome::kExtensionScheme)) {
+ is_extension_process = true;
+ } else if (profile->GetExtensionsService() &&
+ profile->GetExtensionsService()->
+ GetExtensionByWebExtent(entry.url())) {
+ is_extension_process = true;
+ }
+ render_view_host->set_is_extension_process(is_extension_process);
+
+ return delegate_->CreateRenderViewForRenderManager(render_view_host);
+}
+
void RenderViewHostManager::CommitPending() {
// First check whether we're going to want to focus the location bar after
// this commit. We do this now because the navigation hasn't formally
@@ -554,7 +580,7 @@ RenderViewHost* RenderViewHostManager::UpdateRendererStateForNavigate(
DCHECK(!cross_navigation_pending_);
// Create a pending RVH and navigate it.
- bool success = CreatePendingRenderView(new_instance);
+ bool success = CreatePendingRenderView(entry, new_instance);
if (!success)
return NULL;
diff --git a/chrome/browser/tab_contents/render_view_host_manager.h b/chrome/browser/tab_contents/render_view_host_manager.h
index d945f59..d0e88e3 100644
--- a/chrome/browser/tab_contents/render_view_host_manager.h
+++ b/chrome/browser/tab_contents/render_view_host_manager.h
@@ -194,7 +194,13 @@ class RenderViewHostManager
// Helper method to create a pending RenderViewHost for a cross-site
// navigation.
- bool CreatePendingRenderView(SiteInstance* instance);
+ bool CreatePendingRenderView(const NavigationEntry& entry,
+ SiteInstance* instance);
+
+ // Sets up the necessary state for a new RenderViewHost navigating to the
+ // given entry.
+ bool InitRenderView(RenderViewHost* render_view_host,
+ const NavigationEntry& entry);
// Sets the pending RenderViewHost/DOMUI to be the active one. Note that this
// doesn't require the pending render_view_host_ pointer to be non-NULL, since
diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc
index 5c265d9..998eb50 100644
--- a/chrome/browser/tab_contents/tab_contents.cc
+++ b/chrome/browser/tab_contents/tab_contents.cc
@@ -2830,12 +2830,6 @@ TabContents::GetLastCommittedNavigationEntryForRenderManager() {
bool TabContents::CreateRenderViewForRenderManager(
RenderViewHost* render_view_host) {
- // If the pending navigation is to a DOMUI, tell the RenderView about any
- // bindings it will need enabled.
- if (render_manager_.pending_dom_ui())
- render_view_host->AllowBindings(
- render_manager_.pending_dom_ui()->bindings());
-
RenderWidgetHostView* rwh_view = view_->CreateViewForWidget(render_view_host);
scoped_refptr<URLRequestContextGetter> request_context = request_context_;
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index e0982c8..964ff9c 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1250,6 +1250,7 @@
'browser/dom_ui/mediaplayer_browsertest.cc',
'browser/download/save_page_browsertest.cc',
'browser/extensions/alert_apitest.cc',
+ 'browser/extensions/app_process_apitest.cc',
'browser/extensions/autoupdate_interceptor.cc',
'browser/extensions/autoupdate_interceptor.h',
'browser/extensions/browser_action_apitest.cc',
diff --git a/chrome/common/extensions/extension_extent.cc b/chrome/common/extensions/extension_extent.cc
index 3fbb994..f716291 100644
--- a/chrome/common/extensions/extension_extent.cc
+++ b/chrome/common/extensions/extension_extent.cc
@@ -7,7 +7,7 @@
#include "base/string_util.h"
bool ExtensionExtent::ContainsURL(const GURL& url) const {
- if (!url.is_valid())
+ if (origin_.is_empty() || !url.is_valid())
return false;
if (url.GetOrigin() != origin_)
diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h
index aa0ad3c..db6dfe5 100644
--- a/chrome/common/render_messages.h
+++ b/chrome/common/render_messages.h
@@ -20,6 +20,7 @@
#include "chrome/common/css_colors.h"
#include "chrome/common/dom_storage_common.h"
#include "chrome/common/edit_command.h"
+#include "chrome/common/extensions/extension_extent.h"
#include "chrome/common/extensions/url_pattern.h"
#include "chrome/common/filter_policy.h"
#include "chrome/common/navigation_gesture.h"
@@ -619,6 +620,12 @@ struct ViewHostMsg_RunFileChooser_Params {
FilePath default_file_name;
};
+struct ViewMsg_ExtensionExtentsUpdated_Params {
+ // A list of (extension_id, web_extent) pairs that describe the installed
+ // extension apps and the URLs they cover.
+ std::vector< std::pair<std::string, ExtensionExtent> > extension_apps;
+};
+
namespace IPC {
template <>
@@ -2590,6 +2597,46 @@ struct ParamTraits<ViewHostMsg_RunFileChooser_Params> {
}
};
+template <>
+struct ParamTraits<ExtensionExtent> {
+ typedef ExtensionExtent param_type;
+ static void Write(Message* m, const param_type& p) {
+ WriteParam(m, p.origin());
+ WriteParam(m, p.paths());
+ }
+ static bool Read(const Message* m, void** iter, param_type* p) {
+ GURL origin;
+ std::vector<std::string> paths;
+ bool success =
+ ReadParam(m, iter, &origin) &&
+ ReadParam(m, iter, &paths);
+ if (!success)
+ return false;
+
+ p->set_origin(origin);
+ for (size_t i = 0; i < paths.size(); ++i)
+ p->add_path(paths[i]);
+ return true;
+ }
+ static void Log(const param_type& p, std::wstring* l) {
+ LogParam(p.origin(), l);
+ }
+};
+
+template <>
+struct ParamTraits<ViewMsg_ExtensionExtentsUpdated_Params> {
+ typedef ViewMsg_ExtensionExtentsUpdated_Params param_type;
+ static void Write(Message* m, const param_type& p) {
+ WriteParam(m, p.extension_apps);
+ }
+ static bool Read(const Message* m, void** iter, param_type* p) {
+ return ReadParam(m, iter, &p->extension_apps);
+ }
+ static void Log(const param_type& p, std::wstring* l) {
+ LogParam(p.extension_apps, l);
+ }
+};
+
} // namespace IPC
diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h
index 9df2289..4d2b185 100644
--- a/chrome/common/render_messages_internal.h
+++ b/chrome/common/render_messages_internal.h
@@ -930,6 +930,9 @@ IPC_BEGIN_MESSAGES(View)
IPC_MESSAGE_CONTROL1(ViewMsg_SetIsIncognitoProcess,
bool /* is_incognito_processs */)
+ // Notification that the list of extensions with web extents has been updated.
+ IPC_MESSAGE_CONTROL1(ViewMsg_ExtensionExtentsUpdated,
+ ViewMsg_ExtensionExtentsUpdated_Params)
IPC_END_MESSAGES(View)
diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc
index 38e068a..b7fb2af 100644
--- a/chrome/renderer/render_thread.cc
+++ b/chrome/renderer/render_thread.cc
@@ -474,6 +474,15 @@ void RenderThread::OnSetExtensionFunctionNames(
ExtensionProcessBindings::SetFunctionNames(names);
}
+void RenderThread::OnExtensionExtentsUpdated(
+ const ViewMsg_ExtensionExtentsUpdated_Params& params) {
+ extension_extents_.resize(params.extension_apps.size());
+ for (size_t i = 0; i < params.extension_apps.size(); ++i) {
+ extension_extents_[i].extension_id = params.extension_apps[i].first;
+ extension_extents_[i].web_extent = params.extension_apps[i].second;
+ }
+}
+
void RenderThread::OnPageActionsUpdated(
const std::string& extension_id,
const std::vector<std::string>& page_actions) {
@@ -548,6 +557,8 @@ void RenderThread::OnControlMessageReceived(const IPC::Message& msg) {
OnExtensionMessageInvoke)
IPC_MESSAGE_HANDLER(ViewMsg_Extension_SetFunctionNames,
OnSetExtensionFunctionNames)
+ IPC_MESSAGE_HANDLER(ViewMsg_ExtensionExtentsUpdated,
+ OnExtensionExtentsUpdated)
IPC_MESSAGE_HANDLER(ViewMsg_PurgeMemory, OnPurgeMemory)
IPC_MESSAGE_HANDLER(ViewMsg_PurgePluginListCache,
OnPurgePluginListCache)
@@ -1012,3 +1023,15 @@ void RenderThread::OnGpuChannelEstablished(
gpu_channel_ = NULL;
}
}
+
+std::string RenderThread::GetExtensionIdForURL(const GURL& url) {
+ if (url.SchemeIs(chrome::kExtensionScheme))
+ return url.host();
+
+ for (size_t i = 0; i < extension_extents_.size(); ++i) {
+ if (extension_extents_[i].web_extent.ContainsURL(url))
+ return extension_extents_[i].extension_id;
+ }
+
+ return std::string();
+}
diff --git a/chrome/renderer/render_thread.h b/chrome/renderer/render_thread.h
index e8396dd..ba5c849 100644
--- a/chrome/renderer/render_thread.h
+++ b/chrome/renderer/render_thread.h
@@ -18,6 +18,8 @@
#include "chrome/common/child_thread.h"
#include "chrome/common/css_colors.h"
#include "chrome/common/dom_storage_common.h"
+#include "chrome/common/extensions/extension_extent.h"
+#include "chrome/common/render_messages.h"
#include "chrome/renderer/gpu_channel_host.h"
#include "chrome/renderer/renderer_histogram_snapshots.h"
#include "chrome/renderer/visitedlink_slave.h"
@@ -185,7 +187,21 @@ class RenderThread : public RenderThreadBase,
// has been lost.
GpuChannelHost* GetGpuChannel();
+ // Returns the extension ID that the given URL is a part of, or empty if
+ // none. This includes web URLs that are part of an extension's web extent.
+ // TODO(mpcomplete): this doesn't feel like it belongs here. Find a better
+ // place.
+ std::string GetExtensionIdForURL(const GURL& url);
+
private:
+ // Contains extension-related data that the renderer needs to know about.
+ // TODO(mpcomplete): this doesn't feel like it belongs here. Find a better
+ // place.
+ struct ExtensionInfo {
+ std::string extension_id;
+ ExtensionExtent web_extent;
+ };
+
virtual void OnControlMessageReceived(const IPC::Message& msg);
void Init();
@@ -198,6 +214,8 @@ class RenderThread : public RenderThreadBase,
const GURL& url, const ContentSettings& content_settings);
void OnUpdateUserScripts(base::SharedMemoryHandle table);
void OnSetExtensionFunctionNames(const std::vector<std::string>& names);
+ void OnExtensionExtentsUpdated(
+ const ViewMsg_ExtensionExtentsUpdated_Params& params);
void OnPageActionsUpdated(const std::string& extension_id,
const std::vector<std::string>& page_actions);
void OnDOMStorageEvent(const ViewMsg_DOMStorageEvent_Params& params);
@@ -314,6 +332,10 @@ class RenderThread : public RenderThreadBase,
// The channel from the renderer process to the GPU process.
scoped_refptr<GpuChannelHost> gpu_channel_;
+ // A list of extension web extents, which tells us which URLs belong to an
+ // installed app.
+ std::vector<ExtensionInfo> extension_extents_;
+
DISALLOW_COPY_AND_ASSIGN(RenderThread);
};
diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc
index 1aaf0a4..3364d16 100644
--- a/chrome/renderer/render_view.cc
+++ b/chrome/renderer/render_view.cc
@@ -320,6 +320,25 @@ static bool IsWhitelistedForContentSettings(WebFrame* frame) {
return false;
}
+// Returns true if the frame is navigating to an URL either into or out of an
+// extension app's extent.
+static bool CrossesExtensionExtents(WebFrame* frame, const GURL& new_url) {
+ if (!RenderThread::current())
+ return false;
+
+ // If the URL is still empty, this is a window.open navigation. Check the
+ // opener's URL.
+ GURL old_url(frame->url());
+ if (old_url.is_empty() && frame->opener())
+ old_url = frame->opener()->url();
+
+ std::string old_extension =
+ RenderThread::current()->GetExtensionIdForURL(old_url);
+ std::string new_extension =
+ RenderThread::current()->GetExtensionIdForURL(new_url);
+ return (old_extension != new_extension);
+}
+
///////////////////////////////////////////////////////////////////////////////
int32 RenderView::next_page_id_ = 1;
@@ -2210,7 +2229,7 @@ WebNavigationPolicy RenderView::decidePolicyForNavigation(
const GURL& url = request.url();
// If the browser is interested, then give it a chance to look at top level
- // navigations
+ // navigations.
if (ShouldRouteNavigationToBrowser(url, frame, type)) {
last_top_level_navigation_page_id_ = page_id_;
GURL referrer(request.httpHeaderField(WebString::fromUTF8("Referer")));
@@ -2233,7 +2252,8 @@ WebNavigationPolicy RenderView::decidePolicyForNavigation(
!url.SchemeIs(chrome::kAboutScheme)) {
// When we received such unsolicited navigations, we sometimes want to
// punt them up to the browser to handle.
- if (BindingsPolicy::is_dom_ui_enabled(enabled_bindings_) ||
+ if (CrossesExtensionExtents(frame, url) ||
+ BindingsPolicy::is_dom_ui_enabled(enabled_bindings_) ||
BindingsPolicy::is_extension_enabled(enabled_bindings_) ||
frame->isViewSourceModeEnabled() ||
url.SchemeIs(chrome::kViewSourceScheme) ||
diff --git a/chrome/test/data/extensions/api_test/app_process/manifest.json b/chrome/test/data/extensions/api_test/app_process/manifest.json
new file mode 100644
index 0000000..aeaf1bd
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/app_process/manifest.json
@@ -0,0 +1,15 @@
+{
+ "name": "app_process",
+ "version": "0.1",
+ "description": "Tests that app URLs are grouped into the right process",
+ "background_page": "test.html",
+ "permissions": ["tabs"],
+ "web_content": {
+ "enabled": true,
+ "origin": "http://localhost:1337/",
+ "paths": [
+ "files/extensions/api_test/app_process/path1",
+ "files/extensions/api_test/app_process/path2"
+ ]
+ }
+}
diff --git a/chrome/test/data/extensions/api_test/app_process/path1/empty.html b/chrome/test/data/extensions/api_test/app_process/path1/empty.html
new file mode 100644
index 0000000..d3cdf0a
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/app_process/path1/empty.html
@@ -0,0 +1 @@
+<title>Unmodified</title>
diff --git a/chrome/test/data/extensions/api_test/app_process/path2/empty.html b/chrome/test/data/extensions/api_test/app_process/path2/empty.html
new file mode 100644
index 0000000..d3cdf0a
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/app_process/path2/empty.html
@@ -0,0 +1 @@
+<title>Unmodified</title>
diff --git a/chrome/test/data/extensions/api_test/app_process/path3/empty.html b/chrome/test/data/extensions/api_test/app_process/path3/empty.html
new file mode 100644
index 0000000..d3cdf0a
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/app_process/path3/empty.html
@@ -0,0 +1 @@
+<title>Unmodified</title>
diff --git a/chrome/test/data/extensions/api_test/app_process/test.html b/chrome/test/data/extensions/api_test/app_process/test.html
new file mode 100644
index 0000000..8aa5812
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/app_process/test.html
@@ -0,0 +1,25 @@
+<script>
+// Copyright (c) 2010 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.
+
+var origin = "http://localhost:1337/files/extensions/api_test/app_process/";
+var url1 = origin + "path1/empty.html";
+var url2 = origin + "path2/empty.html";
+var url3 = origin + "path3/empty.html";
+
+// Create 3 tabs. The first 2 should be grouped into the same app process.
+chrome.tabs.create({url: url1}, function() {
+ chrome.tabs.create({url: url2}, function() {
+ chrome.tabs.create({url: url3}, function() {
+ chrome.test.notifyPass();
+ });
+ });
+});
+
+function testWindowOpen() {
+ window.open(url1);
+ window.open(url2);
+ window.open(url3);
+}
+</script>