diff options
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> |