diff options
author | jamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-08 19:15:21 +0000 |
---|---|---|
committer | jamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-08 19:15:21 +0000 |
commit | 423f1fb38b61b9ecb94f7e64fc20e2a9969aaf95 (patch) | |
tree | 8ca74e89e9a59dada913d42ceeea17cdd8b0370d /extensions/browser | |
parent | 24ad156eab71b598312f9935e8e2e4a494567119 (diff) | |
download | chromium_src-423f1fb38b61b9ecb94f7e64fc20e2a9969aaf95.zip chromium_src-423f1fb38b61b9ecb94f7e64fc20e2a9969aaf95.tar.gz chromium_src-423f1fb38b61b9ecb94f7e64fc20e2a9969aaf95.tar.bz2 |
Move ExtensionProcessManager to src/extensions, part 4
* Move c/b/extensions/extension_process_manager.h to
extensions/browser/process_manager.h
* Rename ExtensionsProcessManager to ProcessManager
* Place it in the "extensions" namespace
BUG=313481
TEST=browser_tests, unit_tests
R=miket@chromium.org
TBR=sky@chromium.org for mechanical header file move affecting chrome/browser/
Review URL: https://codereview.chromium.org/62713003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@233956 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'extensions/browser')
-rw-r--r-- | extensions/browser/DEPS | 4 | ||||
-rw-r--r-- | extensions/browser/lazy_background_task_queue.cc | 6 | ||||
-rw-r--r-- | extensions/browser/lazy_background_task_queue_unittest.cc | 30 | ||||
-rw-r--r-- | extensions/browser/process_manager.cc | 836 | ||||
-rw-r--r-- | extensions/browser/process_manager.h | 221 |
5 files changed, 1077 insertions, 20 deletions
diff --git a/extensions/browser/DEPS b/extensions/browser/DEPS index 0f912bf..497895d 100644 --- a/extensions/browser/DEPS +++ b/extensions/browser/DEPS @@ -8,10 +8,12 @@ include_rules = [ # # TODO(jamescook): Remove these. http://crbug.com/162530 "+chrome/browser/chrome_notification_types.h", + "+chrome/browser/extensions/api/runtime/runtime_api.h", "+chrome/browser/extensions/extension_host.h", - "+chrome/browser/extensions/extension_process_manager.h", + "+chrome/browser/extensions/extension_host_mac.h", "+chrome/browser/extensions/extension_service.h", "+chrome/browser/extensions/extension_system.h", + "+chrome/browser/extensions/extension_util.h", "+chrome/browser/extensions/process_map.h", "+chrome/common/extensions/background_info.h", "+chrome/common/extensions/extension.h", diff --git a/extensions/browser/lazy_background_task_queue.cc b/extensions/browser/lazy_background_task_queue.cc index 584d897..77a219b 100644 --- a/extensions/browser/lazy_background_task_queue.cc +++ b/extensions/browser/lazy_background_task_queue.cc @@ -7,7 +7,6 @@ #include "base/callback.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/extension_host.h" -#include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/extensions/process_map.h" @@ -21,6 +20,7 @@ #include "content/public/browser/site_instance.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/extensions_browser_client.h" +#include "extensions/browser/process_manager.h" #include "extensions/common/view_type.h" namespace extensions { @@ -44,7 +44,7 @@ bool LazyBackgroundTaskQueue::ShouldEnqueueTask( const Extension* extension) { DCHECK(extension); if (BackgroundInfo::HasBackgroundPage(extension)) { - ExtensionProcessManager* pm = ExtensionSystem::GetForBrowserContext( + ProcessManager* pm = ExtensionSystem::GetForBrowserContext( browser_context)->process_manager(); DCHECK(pm); ExtensionHost* background_host = @@ -81,7 +81,7 @@ void LazyBackgroundTaskQueue::AddPendingTask( if (extension && BackgroundInfo::HasLazyBackgroundPage(extension)) { // If this is the first enqueued task, and we're not waiting for the // background page to unload, ensure the background page is loaded. - ExtensionProcessManager* pm = ExtensionSystem::GetForBrowserContext( + ProcessManager* pm = ExtensionSystem::GetForBrowserContext( browser_context)->process_manager(); pm->IncrementLazyKeepaliveCount(extension); // Creating the background host may fail, e.g. if |profile| is incognito diff --git a/extensions/browser/lazy_background_task_queue_unittest.cc b/extensions/browser/lazy_background_task_queue_unittest.cc index e355cac..863a520 100644 --- a/extensions/browser/lazy_background_task_queue_unittest.cc +++ b/extensions/browser/lazy_background_task_queue_unittest.cc @@ -6,31 +6,30 @@ #include "base/bind.h" #include "base/command_line.h" -#include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/extension_service_unittest.h" #include "chrome/browser/extensions/test_extension_system.h" #include "chrome/common/extensions/extension.h" #include "chrome/test/base/testing_profile.h" #include "content/public/test/test_browser_thread_bundle.h" +#include "extensions/browser/process_manager.h" #include "extensions/common/extension_builder.h" #include "testing/gtest/include/gtest/gtest.h" namespace extensions { -// An ExtensionProcessManager that doesn't create background host pages. -class TestExtensionProcessManager : public ExtensionProcessManager { +// A ProcessManager that doesn't create background host pages. +class TestProcessManager : public ProcessManager { public: - explicit TestExtensionProcessManager(Profile* profile) - : ExtensionProcessManager(profile, profile->GetOriginalProfile()), + explicit TestProcessManager(Profile* profile) + : ProcessManager(profile, profile->GetOriginalProfile()), create_count_(0) {} - virtual ~TestExtensionProcessManager() {} + virtual ~TestProcessManager() {} int create_count() { return create_count_; } - // ExtensionProcessManager overrides: - virtual extensions::ExtensionHost* CreateBackgroundHost( - const extensions::Extension* extension, - const GURL& url) OVERRIDE { + // ProcessManager overrides: + virtual ExtensionHost* CreateBackgroundHost(const Extension* extension, + const GURL& url) OVERRIDE { // Don't actually try to create a web contents. create_count_++; return NULL; @@ -39,7 +38,7 @@ class TestExtensionProcessManager : public ExtensionProcessManager { private: int create_count_; - DISALLOW_COPY_AND_ASSIGN(TestExtensionProcessManager); + DISALLOW_COPY_AND_ASSIGN(TestProcessManager); }; // Derives from ExtensionServiceTestBase because ExtensionService is difficult @@ -96,7 +95,7 @@ class LazyBackgroundTaskQueueTest : public ExtensionServiceTestBase { // Tests that only extensions with background pages should have tasks queued. TEST_F(LazyBackgroundTaskQueueTest, ShouldEnqueueTask) { InitializeEmptyExtensionService(); - InitializeExtensionProcessManager(); + InitializeProcessManager(); LazyBackgroundTaskQueue queue(profile_.get()); @@ -114,14 +113,13 @@ TEST_F(LazyBackgroundTaskQueueTest, ShouldEnqueueTask) { TEST_F(LazyBackgroundTaskQueueTest, AddPendingTask) { InitializeEmptyExtensionService(); - // Swap in our stub TestExtensionProcessManager. + // Swap in our stub TestProcessManager. TestExtensionSystem* extension_system = static_cast<extensions::TestExtensionSystem*>( ExtensionSystem::Get(profile_.get())); // Owned by |extension_system|. - TestExtensionProcessManager* process_manager = - new TestExtensionProcessManager(profile_.get()); - extension_system->SetExtensionProcessManager(process_manager); + TestProcessManager* process_manager = new TestProcessManager(profile_.get()); + extension_system->SetProcessManager(process_manager); LazyBackgroundTaskQueue queue(profile_.get()); diff --git a/extensions/browser/process_manager.cc b/extensions/browser/process_manager.cc new file mode 100644 index 0000000..f5134d1 --- /dev/null +++ b/extensions/browser/process_manager.cc @@ -0,0 +1,836 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/process_manager.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/time/time.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/extensions/api/runtime/runtime_api.h" +#include "chrome/browser/extensions/extension_host.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/extensions/extension_util.h" +#include "chrome/common/extensions/background_info.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_messages.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/devtools_manager.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/site_instance.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_delegate.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" +#include "content/public/common/renderer_preferences.h" +#include "extensions/browser/extensions_browser_client.h" +#include "extensions/browser/view_type_utils.h" +#include "extensions/common/manifest_handlers/incognito_info.h" +#include "extensions/common/switches.h" + +#if defined(OS_MACOSX) +#include "chrome/browser/extensions/extension_host_mac.h" +#endif + +using content::BrowserContext; +using content::RenderViewHost; +using content::SiteInstance; +using content::WebContents; + +namespace extensions { +class RenderViewHostDestructionObserver; +} +DEFINE_WEB_CONTENTS_USER_DATA_KEY( + extensions::RenderViewHostDestructionObserver); + +namespace extensions { + +namespace { + +std::string GetExtensionID(RenderViewHost* render_view_host) { + // This works for both apps and extensions because the site has been + // normalized to the extension URL for apps. + if (!render_view_host->GetSiteInstance()) + return std::string(); + + return render_view_host->GetSiteInstance()->GetSiteURL().host(); +} + +void OnRenderViewHostUnregistered(BrowserContext* context, + RenderViewHost* render_view_host) { + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_EXTENSION_VIEW_UNREGISTERED, + content::Source<BrowserContext>(context), + content::Details<RenderViewHost>(render_view_host)); +} + +// Incognito profiles use this process manager. It is mostly a shim that decides +// whether to fall back on the original profile's ProcessManager based +// on whether a given extension uses "split" or "spanning" incognito behavior. +class IncognitoProcessManager : public ProcessManager { + public: + IncognitoProcessManager(BrowserContext* incognito_context, + BrowserContext* original_context); + virtual ~IncognitoProcessManager(); + virtual ExtensionHost* CreateBackgroundHost(const Extension* extension, + const GURL& url) OVERRIDE; + virtual SiteInstance* GetSiteInstanceForURL(const GURL& url) OVERRIDE; + + private: + // Returns true if the extension is allowed to run in incognito mode. + bool IsIncognitoEnabled(const Extension* extension); + + ProcessManager* original_manager_; + + DISALLOW_COPY_AND_ASSIGN(IncognitoProcessManager); +}; + +static void CreateBackgroundHostForExtensionLoad( + ProcessManager* manager, const Extension* extension) { + DVLOG(1) << "CreateBackgroundHostForExtensionLoad"; + if (BackgroundInfo::HasPersistentBackgroundPage(extension)) + manager->CreateBackgroundHost(extension, + BackgroundInfo::GetBackgroundURL(extension)); +} + +} // namespace + +class RenderViewHostDestructionObserver + : public content::WebContentsObserver, + public content::WebContentsUserData<RenderViewHostDestructionObserver> { + public: + virtual ~RenderViewHostDestructionObserver() {} + + private: + explicit RenderViewHostDestructionObserver(WebContents* web_contents) + : WebContentsObserver(web_contents) { + BrowserContext* context = web_contents->GetBrowserContext(); + process_manager_ = + ExtensionSystem::GetForBrowserContext(context)->process_manager(); + } + + friend class content::WebContentsUserData<RenderViewHostDestructionObserver>; + + // content::WebContentsObserver overrides. + virtual void RenderViewDeleted(RenderViewHost* render_view_host) OVERRIDE { + process_manager_->UnregisterRenderViewHost(render_view_host); + } + + ProcessManager* process_manager_; + + DISALLOW_COPY_AND_ASSIGN(RenderViewHostDestructionObserver); +}; + +struct ProcessManager::BackgroundPageData { + // The count of things keeping the lazy background page alive. + int lazy_keepalive_count; + + // This is used with the ShouldSuspend message, to ensure that the extension + // remained idle between sending the message and receiving the ack. + int close_sequence_id; + + // True if the page responded to the ShouldSuspend message and is currently + // dispatching the suspend event. During this time any events that arrive will + // cancel the suspend process and an onSuspendCanceled event will be + // dispatched to the page. + bool is_closing; + + // Keeps track of when this page was last suspended. Used for perf metrics. + linked_ptr<base::ElapsedTimer> since_suspended; + + BackgroundPageData() + : lazy_keepalive_count(0), close_sequence_id(0), is_closing(false) {} +}; + +// +// ProcessManager +// + +// static +ProcessManager* ProcessManager::Create(BrowserContext* context) { + if (context->IsOffTheRecord()) { + BrowserContext* original_context = + ExtensionsBrowserClient::Get()->GetOriginalContext(context); + return new IncognitoProcessManager(context, original_context); + } + return new ProcessManager(context, context); +} + +ProcessManager::ProcessManager(BrowserContext* context, + BrowserContext* original_context) + : site_instance_(SiteInstance::Create(context)), + defer_background_host_creation_(false), + startup_background_hosts_created_(false), + devtools_callback_(base::Bind( + &ProcessManager::OnDevToolsStateChanged, + base::Unretained(this))), + weak_ptr_factory_(this) { + registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY, + content::Source<BrowserContext>(original_context)); + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, + content::Source<BrowserContext>(original_context)); + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, + content::Source<BrowserContext>(original_context)); + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED, + content::Source<BrowserContext>(context)); + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE, + content::Source<BrowserContext>(context)); + registrar_.Add(this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, + content::NotificationService::AllSources()); + registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_CONNECTED, + content::NotificationService::AllSources()); + registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED, + content::Source<BrowserContext>(original_context)); + registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED, + content::Source<BrowserContext>(context)); + if (context->IsOffTheRecord()) { + registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED, + content::Source<BrowserContext>(original_context)); + } + + event_page_idle_time_ = base::TimeDelta::FromSeconds(10); + unsigned idle_time_sec = 0; + if (base::StringToUint(CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + extensions::switches::kEventPageIdleTime), &idle_time_sec)) { + event_page_idle_time_ = base::TimeDelta::FromSeconds(idle_time_sec); + } + event_page_suspending_time_ = base::TimeDelta::FromSeconds(5); + unsigned suspending_time_sec = 0; + if (base::StringToUint(CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + extensions::switches::kEventPageSuspendingTime), + &suspending_time_sec)) { + event_page_suspending_time_ = + base::TimeDelta::FromSeconds(suspending_time_sec); + } + + content::DevToolsManager::GetInstance()->AddAgentStateCallback( + devtools_callback_); +} + +ProcessManager::~ProcessManager() { + CloseBackgroundHosts(); + DCHECK(background_hosts_.empty()); + content::DevToolsManager::GetInstance()->RemoveAgentStateCallback( + devtools_callback_); +} + +const ProcessManager::ViewSet ProcessManager::GetAllViews() const { + ViewSet result; + for (ExtensionRenderViews::const_iterator iter = + all_extension_views_.begin(); + iter != all_extension_views_.end(); ++iter) { + result.insert(iter->first); + } + return result; +} + +ExtensionHost* ProcessManager::CreateBackgroundHost(const Extension* extension, + const GURL& url) { + DVLOG(1) << "CreateBackgroundHost " << url.spec(); + // Hosted apps are taken care of from BackgroundContentsService. Ignore them + // here. + if (extension->is_hosted_app()) + return NULL; + + // Don't create multiple background hosts for an extension. + if (ExtensionHost* host = GetBackgroundHostForExtension(extension->id())) + return host; // TODO(kalman): return NULL here? It might break things... + + ExtensionHost* host = +#if defined(OS_MACOSX) + new ExtensionHostMac( + extension, GetSiteInstanceForURL(url), url, + VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); +#else + new ExtensionHost(extension, GetSiteInstanceForURL(url), url, + VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); +#endif + + host->CreateRenderViewSoon(); + OnBackgroundHostCreated(host); + return host; +} + +ExtensionHost* ProcessManager::GetBackgroundHostForExtension( + const std::string& extension_id) { + for (ExtensionHostSet::iterator iter = background_hosts_.begin(); + iter != background_hosts_.end(); ++iter) { + ExtensionHost* host = *iter; + if (host->extension_id() == extension_id) + return host; + } + return NULL; +} + +std::set<RenderViewHost*> ProcessManager::GetRenderViewHostsForExtension( + const std::string& extension_id) { + std::set<RenderViewHost*> result; + + SiteInstance* site_instance = GetSiteInstanceForURL( + Extension::GetBaseURLFromExtensionId(extension_id)); + if (!site_instance) + return result; + + // Gather up all the views for that site. + for (ExtensionRenderViews::iterator view = all_extension_views_.begin(); + view != all_extension_views_.end(); ++view) { + if (view->first->GetSiteInstance() == site_instance) + result.insert(view->first); + } + + return result; +} + +const Extension* ProcessManager::GetExtensionForRenderViewHost( + RenderViewHost* render_view_host) { + if (!render_view_host->GetSiteInstance()) + return NULL; + + ExtensionService* service = ExtensionSystem::GetForBrowserContext( + GetBrowserContext())->extension_service(); + if (!service) + return NULL; + + return service->extensions()->GetByID(GetExtensionID(render_view_host)); +} + +void ProcessManager::UnregisterRenderViewHost( + RenderViewHost* render_view_host) { + ExtensionRenderViews::iterator view = + all_extension_views_.find(render_view_host); + if (view == all_extension_views_.end()) + return; + + OnRenderViewHostUnregistered(GetBrowserContext(), render_view_host); + ViewType view_type = view->second; + all_extension_views_.erase(view); + + // Keepalive count, balanced in RegisterRenderViewHost. + if (view_type != VIEW_TYPE_INVALID && + view_type != VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) { + const Extension* extension = GetExtensionForRenderViewHost( + render_view_host); + if (extension) + DecrementLazyKeepaliveCount(extension); + } +} + +void ProcessManager::RegisterRenderViewHost(RenderViewHost* render_view_host) { + const Extension* extension = GetExtensionForRenderViewHost( + render_view_host); + if (!extension) + return; + + WebContents* web_contents = WebContents::FromRenderViewHost(render_view_host); + all_extension_views_[render_view_host] = GetViewType(web_contents); + + // Keep the lazy background page alive as long as any non-background-page + // extension views are visible. Keepalive count balanced in + // UnregisterRenderViewHost. + IncrementLazyKeepaliveCountForView(render_view_host); +} + +SiteInstance* ProcessManager::GetSiteInstanceForURL(const GURL& url) { + return site_instance_->GetRelatedSiteInstance(url); +} + +bool ProcessManager::IsBackgroundHostClosing(const std::string& extension_id) { + ExtensionHost* host = GetBackgroundHostForExtension(extension_id); + return (host && background_page_data_[extension_id].is_closing); +} + +int ProcessManager::GetLazyKeepaliveCount(const Extension* extension) { + if (!BackgroundInfo::HasLazyBackgroundPage(extension)) + return 0; + + return background_page_data_[extension->id()].lazy_keepalive_count; +} + +int ProcessManager::IncrementLazyKeepaliveCount(const Extension* extension) { + if (!BackgroundInfo::HasLazyBackgroundPage(extension)) + return 0; + + int& count = background_page_data_[extension->id()].lazy_keepalive_count; + if (++count == 1) + OnLazyBackgroundPageActive(extension->id()); + + return count; +} + +int ProcessManager::DecrementLazyKeepaliveCount(const Extension* extension) { + if (!BackgroundInfo::HasLazyBackgroundPage(extension)) + return 0; + + int& count = background_page_data_[extension->id()].lazy_keepalive_count; + DCHECK_GT(count, 0); + + // If we reach a zero keepalive count when the lazy background page is about + // to be closed, incrementing close_sequence_id will cancel the close + // sequence and cause the background page to linger. So check is_closing + // before initiating another close sequence. + if (--count == 0 && !background_page_data_[extension->id()].is_closing) { + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&ProcessManager::OnLazyBackgroundPageIdle, + weak_ptr_factory_.GetWeakPtr(), extension->id(), + ++background_page_data_[extension->id()].close_sequence_id), + event_page_idle_time_); + } + + return count; +} + +void ProcessManager::IncrementLazyKeepaliveCountForView( + RenderViewHost* render_view_host) { + WebContents* web_contents = + WebContents::FromRenderViewHost(render_view_host); + ViewType view_type = GetViewType(web_contents); + if (view_type != VIEW_TYPE_INVALID && + view_type != VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) { + const Extension* extension = GetExtensionForRenderViewHost( + render_view_host); + if (extension) + IncrementLazyKeepaliveCount(extension); + } +} + +void ProcessManager::OnLazyBackgroundPageIdle(const std::string& extension_id, + int sequence_id) { + ExtensionHost* host = GetBackgroundHostForExtension(extension_id); + if (host && !background_page_data_[extension_id].is_closing && + sequence_id == background_page_data_[extension_id].close_sequence_id) { + // Tell the renderer we are about to close. This is a simple ping that the + // renderer will respond to. The purpose is to control sequencing: if the + // extension remains idle until the renderer responds with an ACK, then we + // know that the extension process is ready to shut down. If our + // close_sequence_id has already changed, then we would ignore the + // ShouldSuspendAck, so we don't send the ping. + host->render_view_host()->Send(new ExtensionMsg_ShouldSuspend( + extension_id, sequence_id)); + } +} + +void ProcessManager::OnLazyBackgroundPageActive( + const std::string& extension_id) { + ExtensionHost* host = GetBackgroundHostForExtension(extension_id); + if (host && !background_page_data_[extension_id].is_closing) { + // Cancel the current close sequence by changing the close_sequence_id, + // which causes us to ignore the next ShouldSuspendAck. + ++background_page_data_[extension_id].close_sequence_id; + } +} + +void ProcessManager::OnShouldSuspendAck(const std::string& extension_id, + int sequence_id) { + ExtensionHost* host = GetBackgroundHostForExtension(extension_id); + if (host && + sequence_id == background_page_data_[extension_id].close_sequence_id) { + host->render_view_host()->Send(new ExtensionMsg_Suspend(extension_id)); + } +} + +void ProcessManager::OnSuspendAck(const std::string& extension_id) { + background_page_data_[extension_id].is_closing = true; + int sequence_id = background_page_data_[extension_id].close_sequence_id; + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&ProcessManager::CloseLazyBackgroundPageNow, + weak_ptr_factory_.GetWeakPtr(), extension_id, sequence_id), + event_page_suspending_time_); +} + +void ProcessManager::CloseLazyBackgroundPageNow(const std::string& extension_id, + int sequence_id) { + ExtensionHost* host = GetBackgroundHostForExtension(extension_id); + if (host && + sequence_id == background_page_data_[extension_id].close_sequence_id) { + ExtensionHost* host = GetBackgroundHostForExtension(extension_id); + if (host) + CloseBackgroundHost(host); + } +} + +void ProcessManager::OnNetworkRequestStarted(RenderViewHost* render_view_host) { + ExtensionHost* host = GetBackgroundHostForExtension( + GetExtensionID(render_view_host)); + if (host && host->render_view_host() == render_view_host) + IncrementLazyKeepaliveCount(host->extension()); +} + +void ProcessManager::OnNetworkRequestDone(RenderViewHost* render_view_host) { + ExtensionHost* host = GetBackgroundHostForExtension( + GetExtensionID(render_view_host)); + if (host && host->render_view_host() == render_view_host) + DecrementLazyKeepaliveCount(host->extension()); +} + +void ProcessManager::CancelSuspend(const Extension* extension) { + bool& is_closing = background_page_data_[extension->id()].is_closing; + ExtensionHost* host = GetBackgroundHostForExtension(extension->id()); + if (host && is_closing) { + is_closing = false; + host->render_view_host()->Send( + new ExtensionMsg_CancelSuspend(extension->id())); + // This increment / decrement is to simulate an instantaneous event. This + // has the effect of invalidating close_sequence_id, preventing any in + // progress closes from completing and starting a new close process if + // necessary. + IncrementLazyKeepaliveCount(extension); + DecrementLazyKeepaliveCount(extension); + } +} + +void ProcessManager::DeferBackgroundHostCreation(bool defer) { + bool previous = defer_background_host_creation_; + defer_background_host_creation_ = defer; + + // If we were deferred, and we switch to non-deferred, then create the + // background hosts. + if (previous && !defer_background_host_creation_) + CreateBackgroundHostsForProfileStartup(); +} + +void ProcessManager::OnBrowserWindowReady() { + ExtensionService* service = ExtensionSystem::GetForBrowserContext( + GetBrowserContext())->extension_service(); + // On Chrome OS, a login screen is implemented as a browser. + // This browser has no extension service. In this case, + // service will be NULL. + if (!service || !service->is_ready()) + return; + + CreateBackgroundHostsForProfileStartup(); +} + +content::BrowserContext* ProcessManager::GetBrowserContext() const { + return site_instance_->GetBrowserContext(); +} + +void ProcessManager::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case chrome::NOTIFICATION_EXTENSIONS_READY: + case chrome::NOTIFICATION_PROFILE_CREATED: { + CreateBackgroundHostsForProfileStartup(); + break; + } + + case chrome::NOTIFICATION_EXTENSION_LOADED: { + BrowserContext* context = content::Source<BrowserContext>(source).ptr(); + ExtensionService* service = + ExtensionSystem::GetForBrowserContext(context)->extension_service(); + if (service->is_ready()) { + const Extension* extension = + content::Details<const Extension>(details).ptr(); + CreateBackgroundHostForExtensionLoad(this, extension); + } + break; + } + + case chrome::NOTIFICATION_EXTENSION_UNLOADED: { + const Extension* extension = + content::Details<UnloadedExtensionInfo>(details)->extension; + for (ExtensionHostSet::iterator iter = background_hosts_.begin(); + iter != background_hosts_.end(); ++iter) { + ExtensionHost* host = *iter; + if (host->extension_id() == extension->id()) { + CloseBackgroundHost(host); + break; + } + } + UnregisterExtension(extension->id()); + break; + } + + case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: { + ExtensionHost* host = content::Details<ExtensionHost>(details).ptr(); + if (background_hosts_.erase(host)) { + ClearBackgroundPageData(host->extension()->id()); + background_page_data_[host->extension()->id()].since_suspended.reset( + new base::ElapsedTimer()); + } + break; + } + + case chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE: { + ExtensionHost* host = content::Details<ExtensionHost>(details).ptr(); + if (host->extension_host_type() == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) { + CloseBackgroundHost(host); + } + break; + } + + case content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED: { + // We get this notification both for new WebContents and when one + // has its RenderViewHost replaced (e.g. when a user does a cross-site + // navigation away from an extension URL). For the replaced case, we must + // unregister the old RVH so it doesn't count as an active view that would + // keep the event page alive. + WebContents* contents = content::Source<WebContents>(source).ptr(); + if (contents->GetBrowserContext() != GetBrowserContext()) + break; + + typedef std::pair<RenderViewHost*, RenderViewHost*> RVHPair; + RVHPair* switched_details = content::Details<RVHPair>(details).ptr(); + if (switched_details->first) + UnregisterRenderViewHost(switched_details->first); + + // The above will unregister a RVH when it gets swapped out with a new + // one. However we need to watch the WebContents to know when a RVH is + // deleted because the WebContents has gone away. + RenderViewHostDestructionObserver::CreateForWebContents(contents); + RegisterRenderViewHost(switched_details->second); + break; + } + + case content::NOTIFICATION_WEB_CONTENTS_CONNECTED: { + WebContents* contents = content::Source<WebContents>(source).ptr(); + if (contents->GetBrowserContext() != GetBrowserContext()) + break; + const Extension* extension = GetExtensionForRenderViewHost( + contents->GetRenderViewHost()); + if (!extension) + return; + + // RegisterRenderViewHost is called too early (before the process is + // available), so we need to wait until now to notify. + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_EXTENSION_VIEW_REGISTERED, + content::Source<BrowserContext>(GetBrowserContext()), + content::Details<RenderViewHost>(contents->GetRenderViewHost())); + break; + } + + case chrome::NOTIFICATION_PROFILE_DESTROYED: { + // Close background hosts when the last browser is closed so that they + // have time to shutdown various objects on different threads. Our + // destructor is called too late in the shutdown sequence. + CloseBackgroundHosts(); + break; + } + + default: + NOTREACHED(); + } +} + +void ProcessManager::OnDevToolsStateChanged( + content::DevToolsAgentHost* agent_host, + bool attached) { + RenderViewHost* rvh = agent_host->GetRenderViewHost(); + // Ignore unrelated notifications. + if (!rvh || + rvh->GetSiteInstance()->GetProcess()->GetBrowserContext() != + GetBrowserContext()) + return; + if (GetViewType(WebContents::FromRenderViewHost(rvh)) != + VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) + return; + const Extension* extension = GetExtensionForRenderViewHost(rvh); + if (!extension) + return; + if (attached) { + // Keep the lazy background page alive while it's being inspected. + CancelSuspend(extension); + IncrementLazyKeepaliveCount(extension); + } else { + DecrementLazyKeepaliveCount(extension); + } +} + +void ProcessManager::CreateBackgroundHostsForProfileStartup() { + if (startup_background_hosts_created_) + return; + + // Don't load background hosts now if the loading should be deferred. + // Instead they will be loaded when a browser window for this profile + // (or an incognito profile from this profile) is ready, or when + // DeferBackgroundHostCreation is called with false. + if (DeferLoadingBackgroundHosts()) + return; + + ExtensionService* service = ExtensionSystem::GetForBrowserContext( + GetBrowserContext())->extension_service(); + DCHECK(service); + for (ExtensionSet::const_iterator extension = service->extensions()->begin(); + extension != service->extensions()->end(); ++extension) { + CreateBackgroundHostForExtensionLoad(this, extension->get()); + + RuntimeEventRouter::DispatchOnStartupEvent(GetBrowserContext(), + (*extension)->id()); + } + startup_background_hosts_created_ = true; + + // Background pages should only be loaded once. To prevent any further loads + // occurring, we remove the notification listeners. + BrowserContext* original_context = + ExtensionsBrowserClient::Get()->GetOriginalContext(GetBrowserContext()); + if (registrar_.IsRegistered( + this, + chrome::NOTIFICATION_PROFILE_CREATED, + content::Source<BrowserContext>(original_context))) { + registrar_.Remove(this, + chrome::NOTIFICATION_PROFILE_CREATED, + content::Source<BrowserContext>(original_context)); + } + if (registrar_.IsRegistered( + this, + chrome::NOTIFICATION_EXTENSIONS_READY, + content::Source<BrowserContext>(original_context))) { + registrar_.Remove(this, + chrome::NOTIFICATION_EXTENSIONS_READY, + content::Source<BrowserContext>(original_context)); + } +} + +void ProcessManager::OnBackgroundHostCreated(ExtensionHost* host) { + DCHECK_EQ(GetBrowserContext(), host->browser_context()); + background_hosts_.insert(host); + + if (BackgroundInfo::HasLazyBackgroundPage(host->extension())) { + linked_ptr<base::ElapsedTimer> since_suspended( + background_page_data_[host->extension()->id()]. + since_suspended.release()); + if (since_suspended.get()) { + UMA_HISTOGRAM_LONG_TIMES("Extensions.EventPageIdleTime", + since_suspended->Elapsed()); + } + } +} + +void ProcessManager::CloseBackgroundHost(ExtensionHost* host) { + CHECK(host->extension_host_type() == + VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); + delete host; + // |host| should deregister itself from our structures. + CHECK(background_hosts_.find(host) == background_hosts_.end()); +} + +void ProcessManager::CloseBackgroundHosts() { + for (ExtensionHostSet::iterator iter = background_hosts_.begin(); + iter != background_hosts_.end(); ) { + ExtensionHostSet::iterator current = iter++; + delete *current; + } +} + +void ProcessManager::UnregisterExtension(const std::string& extension_id) { + // The lazy_keepalive_count may be greater than zero at this point because + // RenderViewHosts are still alive. During extension reloading, they will + // decrement the lazy_keepalive_count to negative for the new extension + // instance when they are destroyed. Since we are erasing the background page + // data for the unloaded extension, unregister the RenderViewHosts too. + BrowserContext* context = GetBrowserContext(); + for (ExtensionRenderViews::iterator it = all_extension_views_.begin(); + it != all_extension_views_.end(); ) { + if (GetExtensionID(it->first) == extension_id) { + OnRenderViewHostUnregistered(context, it->first); + all_extension_views_.erase(it++); + } else { + ++it; + } + } + + background_page_data_.erase(extension_id); +} + +void ProcessManager::ClearBackgroundPageData(const std::string& extension_id) { + background_page_data_.erase(extension_id); + + // Re-register all RenderViews for this extension. We do this to restore + // the lazy_keepalive_count (if any) to properly reflect the number of open + // views. + for (ExtensionRenderViews::const_iterator it = all_extension_views_.begin(); + it != all_extension_views_.end(); ++it) { + if (GetExtensionID(it->first) == extension_id) + IncrementLazyKeepaliveCountForView(it->first); + } +} + +bool ProcessManager::DeferLoadingBackgroundHosts() const { + // Don't load background hosts now if the loading should be deferred. + if (defer_background_host_creation_) + return true; + + // The extensions embedder may have special rules about background hosts. + return ExtensionsBrowserClient::Get()->DeferLoadingBackgroundHosts( + GetBrowserContext()); +} + +// +// IncognitoProcessManager +// + +IncognitoProcessManager::IncognitoProcessManager( + BrowserContext* incognito_context, + BrowserContext* original_context) + : ProcessManager(incognito_context, original_context), + original_manager_(ExtensionSystem::GetForBrowserContext( + original_context)->process_manager()) { + DCHECK(incognito_context->IsOffTheRecord()); + + // The original profile will have its own ProcessManager to + // load the background pages of the spanning extensions. This process + // manager need only worry about the split mode extensions, which is handled + // in the NOTIFICATION_BROWSER_WINDOW_READY notification handler. + registrar_.Remove(this, chrome::NOTIFICATION_EXTENSIONS_READY, + content::Source<BrowserContext>(original_context)); + registrar_.Remove(this, chrome::NOTIFICATION_PROFILE_CREATED, + content::Source<BrowserContext>(original_context)); +} + +IncognitoProcessManager::~IncognitoProcessManager() { + // TODO(yoz): This cleanup code belongs in the MenuManager. + // Remove "incognito" "split" mode context menu items. + ExtensionService* service = ExtensionSystem::GetForBrowserContext( + GetBrowserContext())->extension_service(); + if (service) + service->menu_manager()->RemoveAllIncognitoContextItems(); +} + +ExtensionHost* IncognitoProcessManager::CreateBackgroundHost( + const Extension* extension, const GURL& url) { + if (IncognitoInfo::IsSplitMode(extension)) { + if (IsIncognitoEnabled(extension)) + return ProcessManager::CreateBackgroundHost(extension, url); + } else { + // Do nothing. If an extension is spanning, then its original-profile + // background page is shared with incognito, so we don't create another. + } + return NULL; +} + +SiteInstance* IncognitoProcessManager::GetSiteInstanceForURL(const GURL& url) { + ExtensionService* service = ExtensionSystem::GetForBrowserContext( + GetBrowserContext())->extension_service(); + if (service) { + const Extension* extension = + service->extensions()->GetExtensionOrAppByURL(url); + if (extension && !IncognitoInfo::IsSplitMode(extension)) { + return original_manager_->GetSiteInstanceForURL(url); + } + } + return ProcessManager::GetSiteInstanceForURL(url); +} + +bool IncognitoProcessManager::IsIncognitoEnabled(const Extension* extension) { + // Keep in sync with duplicate in extension_info_map.cc. + ExtensionService* service = ExtensionSystem::GetForBrowserContext( + GetBrowserContext())->extension_service(); + return extension_util::IsIncognitoEnabled(extension->id(), service); +} + +} // namespace extensions diff --git a/extensions/browser/process_manager.h b/extensions/browser/process_manager.h new file mode 100644 index 0000000..be47f0d --- /dev/null +++ b/extensions/browser/process_manager.h @@ -0,0 +1,221 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_PROCESS_MANAGER_H_ +#define EXTENSIONS_BROWSER_PROCESS_MANAGER_H_ + +#include <map> +#include <set> +#include <string> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "extensions/common/view_type.h" + +class GURL; + +namespace content { +class BrowserContext; +class DevToolsAgentHost; +class RenderViewHost; +class SiteInstance; +}; + +namespace extensions { + +class Extension; +class ExtensionHost; + +// Manages dynamic state of running Chromium extensions. There is one instance +// of this class per Profile. OTR Profiles have a separate instance that keeps +// track of split-mode extensions only. +class ProcessManager : public content::NotificationObserver { + public: + typedef std::set<extensions::ExtensionHost*> ExtensionHostSet; + typedef ExtensionHostSet::const_iterator const_iterator; + + static ProcessManager* Create(content::BrowserContext* context); + virtual ~ProcessManager(); + + const ExtensionHostSet& background_hosts() const { + return background_hosts_; + } + + typedef std::set<content::RenderViewHost*> ViewSet; + const ViewSet GetAllViews() const; + + // Creates a new UI-less extension instance. Like CreateViewHost, but not + // displayed anywhere. + virtual ExtensionHost* CreateBackgroundHost(const Extension* extension, + const GURL& url); + + // Gets the ExtensionHost for the background page for an extension, or NULL if + // the extension isn't running or doesn't have a background page. + ExtensionHost* GetBackgroundHostForExtension(const std::string& extension_id); + + // Returns the SiteInstance that the given URL belongs to. + // TODO(aa): This only returns correct results for extensions and packaged + // apps, not hosted apps. + virtual content::SiteInstance* GetSiteInstanceForURL(const GURL& url); + + // Unregisters a RenderViewHost as hosting any extension. + void UnregisterRenderViewHost(content::RenderViewHost* render_view_host); + + // Returns all RenderViewHosts that are registered for the specified + // extension. + std::set<content::RenderViewHost*> GetRenderViewHostsForExtension( + const std::string& extension_id); + + // Returns the extension associated with the specified RenderViewHost, or + // NULL. + const Extension* GetExtensionForRenderViewHost( + content::RenderViewHost* render_view_host); + + // Returns true if the (lazy) background host for the given extension has + // already been sent the unload event and is shutting down. + bool IsBackgroundHostClosing(const std::string& extension_id); + + // Getter and setter for the lazy background page's keepalive count. This is + // the count of how many outstanding "things" are keeping the page alive. + // When this reaches 0, we will begin the process of shutting down the page. + // "Things" include pending events, resource loads, and API calls. + int GetLazyKeepaliveCount(const Extension* extension); + int IncrementLazyKeepaliveCount(const Extension* extension); + int DecrementLazyKeepaliveCount(const Extension* extension); + + void IncrementLazyKeepaliveCountForView( + content::RenderViewHost* render_view_host); + + // Handles a response to the ShouldSuspend message, used for lazy background + // pages. + void OnShouldSuspendAck(const std::string& extension_id, int sequence_id); + + // Same as above, for the Suspend message. + void OnSuspendAck(const std::string& extension_id); + + // Tracks network requests for a given RenderViewHost, used to know + // when network activity is idle for lazy background pages. + void OnNetworkRequestStarted(content::RenderViewHost* render_view_host); + void OnNetworkRequestDone(content::RenderViewHost* render_view_host); + + // Prevents |extension|'s background page from being closed and sends the + // onSuspendCanceled() event to it. + void CancelSuspend(const Extension* extension); + + // If |defer| is true background host creation is to be deferred until this is + // called again with |defer| set to false, at which point all deferred + // background hosts will be created. Defaults to false. + void DeferBackgroundHostCreation(bool defer); + + // Ensures background hosts are loaded for a new browser window. + void OnBrowserWindowReady(); + + // Gets the BrowserContext associated with site_instance_ and all other + // related SiteInstances. + content::BrowserContext* GetBrowserContext() const; + + protected: + // If |context| is incognito pass the master context as |original_context|. + // Otherwise pass the same context for both. + ProcessManager(content::BrowserContext* context, + content::BrowserContext* original_context); + + // Called on browser shutdown to close our extension hosts. + void CloseBackgroundHosts(); + + // content::NotificationObserver: + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // Load all background pages once the profile data is ready and the pages + // should be loaded. + void CreateBackgroundHostsForProfileStartup(); + + content::NotificationRegistrar registrar_; + + // The set of ExtensionHosts running viewless background extensions. + ExtensionHostSet background_hosts_; + + // A SiteInstance related to the SiteInstance for all extensions in + // this profile. We create it in such a way that a new + // browsing instance is created. This controls process grouping. + scoped_refptr<content::SiteInstance> site_instance_; + + private: + friend class ProcessManagerTest; + + // Extra information we keep for each extension's background page. + struct BackgroundPageData; + typedef std::string ExtensionId; + typedef std::map<ExtensionId, BackgroundPageData> BackgroundPageDataMap; + typedef std::map<content::RenderViewHost*, + extensions::ViewType> ExtensionRenderViews; + + // Called just after |host| is created so it can be registered in our lists. + void OnBackgroundHostCreated(ExtensionHost* host); + + // Close the given |host| iff it's a background page. + void CloseBackgroundHost(ExtensionHost* host); + + // These are called when the extension transitions between idle and active. + // They control the process of closing the background page when idle. + void OnLazyBackgroundPageIdle(const std::string& extension_id, + int sequence_id); + void OnLazyBackgroundPageActive(const std::string& extension_id); + void CloseLazyBackgroundPageNow(const std::string& extension_id, + int sequence_id); + + // Potentially registers a RenderViewHost, if it is associated with an + // extension. Does nothing if this is not an extension renderer. + void RegisterRenderViewHost(content::RenderViewHost* render_view_host); + + // Unregister RenderViewHosts and clear background page data for an extension + // which has been unloaded. + void UnregisterExtension(const std::string& extension_id); + + // Clears background page data for this extension. + void ClearBackgroundPageData(const std::string& extension_id); + + // Returns true if loading background pages should be deferred. + bool DeferLoadingBackgroundHosts() const; + + void OnDevToolsStateChanged(content::DevToolsAgentHost*, bool attached); + + // Contains all active extension-related RenderViewHost instances for all + // extensions. We also keep a cache of the host's view type, because that + // information is not accessible at registration/deregistration time. + ExtensionRenderViews all_extension_views_; + + BackgroundPageDataMap background_page_data_; + + // The time to delay between an extension becoming idle and + // sending a ShouldSuspend message; read from command-line switch. + base::TimeDelta event_page_idle_time_; + + // The time to delay between sending a ShouldSuspend message and + // sending a Suspend message; read from command-line switch. + base::TimeDelta event_page_suspending_time_; + + // If true, then creation of background hosts is suspended. + bool defer_background_host_creation_; + + // True if we have created the startup set of background hosts. + bool startup_background_hosts_created_; + + base::Callback<void(content::DevToolsAgentHost*, bool)> devtools_callback_; + + base::WeakPtrFactory<ProcessManager> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(ProcessManager); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_PROCESS_MANAGER_H_ |