| // 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 "chrome/browser/extensions/extension_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/browser_process.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_info_map.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_system.h" |
| #include "chrome/browser/extensions/extension_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/extensions/background_info.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "chrome/common/extensions/extension_messages.h" |
| #include "chrome/common/extensions/incognito_handler.h" |
| #include "chrome/common/extensions/manifest_url_handler.h" |
| #include "chrome/common/url_constants.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/view_type_utils.h" |
| |
| #if defined(OS_MACOSX) |
| #include "chrome/browser/extensions/extension_host_mac.h" |
| #endif |
| |
| using content::RenderViewHost; |
| using content::SiteInstance; |
| using content::WebContents; |
| using extensions::BackgroundInfo; |
| using extensions::BackgroundManifestHandler; |
| using extensions::Extension; |
| using extensions::ExtensionHost; |
| |
| class RenderViewHostDestructionObserver; |
| DEFINE_WEB_CONTENTS_USER_DATA_KEY(RenderViewHostDestructionObserver); |
| |
| 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(Profile* profile, |
| RenderViewHost* render_view_host) { |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_VIEW_UNREGISTERED, |
| content::Source<Profile>(profile), |
| 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 ExtensionProcessManager based |
| // on whether a given extension uses "split" or "spanning" incognito behavior. |
| class IncognitoExtensionProcessManager : public ExtensionProcessManager { |
| public: |
| explicit IncognitoExtensionProcessManager(Profile* profile); |
| virtual ~IncognitoExtensionProcessManager(); |
| virtual ExtensionHost* CreateViewHost( |
| const Extension* extension, |
| const GURL& url, |
| Browser* browser, |
| extensions::ViewType view_type) OVERRIDE; |
| virtual ExtensionHost* CreateBackgroundHost(const Extension* extension, |
| const GURL& url) OVERRIDE; |
| virtual SiteInstance* GetSiteInstanceForURL(const GURL& url) OVERRIDE; |
| |
| private: |
| // content::NotificationObserver: |
| virtual void Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) OVERRIDE; |
| |
| // Returns true if the extension is allowed to run in incognito mode. |
| bool IsIncognitoEnabled(const Extension* extension); |
| |
| ExtensionProcessManager* original_manager_; |
| }; |
| |
| static void CreateBackgroundHostForExtensionLoad( |
| ExtensionProcessManager* manager, const Extension* extension) { |
| 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) { |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| process_manager_ = |
| extensions::ExtensionSystem::Get(profile)->process_manager(); |
| } |
| |
| friend class content::WebContentsUserData<RenderViewHostDestructionObserver>; |
| |
| // content::WebContentsObserver overrides. |
| virtual void RenderViewDeleted(RenderViewHost* render_view_host) OVERRIDE { |
| process_manager_->UnregisterRenderViewHost(render_view_host); |
| } |
| |
| ExtensionProcessManager* process_manager_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RenderViewHostDestructionObserver); |
| }; |
| |
| struct ExtensionProcessManager::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) {} |
| }; |
| |
| // |
| // ExtensionProcessManager |
| // |
| |
| // static |
| ExtensionProcessManager* ExtensionProcessManager::Create(Profile* profile) { |
| return (profile->IsOffTheRecord()) ? |
| new IncognitoExtensionProcessManager(profile) : |
| new ExtensionProcessManager(profile); |
| } |
| |
| ExtensionProcessManager::ExtensionProcessManager(Profile* profile) |
| : site_instance_(SiteInstance::Create(profile)), |
| defer_background_host_creation_(false), |
| weak_ptr_factory_(this), |
| devtools_callback_(base::Bind( |
| &ExtensionProcessManager::OnDevToolsStateChanged, |
| base::Unretained(this))) { |
| Profile* original_profile = profile->GetOriginalProfile(); |
| registrar_.Add(this, chrome::NOTIFICATION_BROWSER_WINDOW_READY, |
| content::NotificationService::AllSources()); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY, |
| content::Source<Profile>(original_profile)); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, |
| content::Source<Profile>(original_profile)); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, |
| content::Source<Profile>(original_profile)); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED, |
| content::Source<Profile>(profile)); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE, |
| content::Source<Profile>(profile)); |
| 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<Profile>(original_profile)); |
| registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED, |
| content::Source<Profile>(profile)); |
| if (profile->IsOffTheRecord()) { |
| registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED, |
| content::Source<Profile>(original_profile)); |
| } |
| |
| event_page_idle_time_ = base::TimeDelta::FromSeconds(10); |
| unsigned idle_time_sec = 0; |
| if (base::StringToUint(CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| 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( |
| switches::kEventPageSuspendingTime), &suspending_time_sec)) { |
| event_page_suspending_time_ = base::TimeDelta::FromSeconds( |
| suspending_time_sec); |
| } |
| |
| content::DevToolsManager::GetInstance()->AddAgentStateCallback( |
| devtools_callback_); |
| } |
| |
| ExtensionProcessManager::~ExtensionProcessManager() { |
| CloseBackgroundHosts(); |
| DCHECK(background_hosts_.empty()); |
| content::DevToolsManager::GetInstance()->RemoveAgentStateCallback( |
| devtools_callback_); |
| } |
| |
| const ExtensionProcessManager::ViewSet |
| ExtensionProcessManager::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; |
| } |
| |
| void ExtensionProcessManager::EnsureBrowserWhenRequired( |
| Browser* browser, |
| extensions::ViewType view_type) { |
| if (!browser) { |
| #if defined (OS_CHROMEOS) |
| // On ChromeOS we'll only use ExtensionView, which |
| // does not use the browser parameter. |
| // TODO(rkc): Remove all this once we create a new host for |
| // screensaver extensions (crosbug.com/28211). |
| DCHECK(view_type == extensions::VIEW_TYPE_EXTENSION_POPUP || |
| view_type == extensions::VIEW_TYPE_EXTENSION_DIALOG); |
| #else |
| // A NULL browser may only be given for pop-up views. |
| DCHECK(view_type == extensions::VIEW_TYPE_EXTENSION_POPUP); |
| #endif |
| } |
| } |
| |
| ExtensionHost* ExtensionProcessManager::CreateViewHost( |
| const Extension* extension, |
| const GURL& url, |
| Browser* browser, |
| extensions::ViewType view_type) { |
| DCHECK(extension); |
| EnsureBrowserWhenRequired(browser, view_type); |
| ExtensionHost* host = |
| #if defined(OS_MACOSX) |
| new extensions::ExtensionHostMac( |
| extension, GetSiteInstanceForURL(url), url, view_type); |
| #else |
| new ExtensionHost(extension, GetSiteInstanceForURL(url), url, view_type); |
| #endif |
| host->CreateView(browser); |
| OnExtensionHostCreated(host, false); |
| return host; |
| } |
| |
| ExtensionHost* ExtensionProcessManager::CreateViewHost( |
| const GURL& url, Browser* browser, extensions::ViewType view_type) { |
| EnsureBrowserWhenRequired(browser, view_type); |
| ExtensionService* service = GetProfile()->GetExtensionService(); |
| if (service) { |
| std::string extension_id = url.host(); |
| if (url.SchemeIs(chrome::kChromeUIScheme) && |
| url.host() == chrome::kChromeUIExtensionInfoHost) |
| extension_id = url.path().substr(1); |
| const Extension* extension = |
| service->extensions()->GetByID(extension_id); |
| if (extension) |
| return CreateViewHost(extension, url, browser, view_type); |
| } |
| return NULL; |
| } |
| |
| ExtensionHost* ExtensionProcessManager::CreatePopupHost( |
| const Extension* extension, const GURL& url, Browser* browser) { |
| return CreateViewHost( |
| extension, url, browser, extensions::VIEW_TYPE_EXTENSION_POPUP); |
| } |
| |
| ExtensionHost* ExtensionProcessManager::CreatePopupHost( |
| const GURL& url, Browser* browser) { |
| return CreateViewHost(url, browser, extensions::VIEW_TYPE_EXTENSION_POPUP); |
| } |
| |
| ExtensionHost* ExtensionProcessManager::CreateDialogHost(const GURL& url) { |
| return CreateViewHost(url, NULL, extensions::VIEW_TYPE_EXTENSION_DIALOG); |
| } |
| |
| ExtensionHost* ExtensionProcessManager::CreateInfobarHost( |
| const Extension* extension, const GURL& url, Browser* browser) { |
| return CreateViewHost( |
| extension, url, browser, extensions::VIEW_TYPE_EXTENSION_INFOBAR); |
| } |
| |
| ExtensionHost* ExtensionProcessManager::CreateInfobarHost( |
| const GURL& url, Browser* browser) { |
| return CreateViewHost(url, browser, extensions::VIEW_TYPE_EXTENSION_INFOBAR); |
| } |
| |
| ExtensionHost* ExtensionProcessManager::CreateBackgroundHost( |
| const Extension* extension, const GURL& url) { |
| // 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 extensions::ExtensionHostMac( |
| extension, GetSiteInstanceForURL(url), url, |
| extensions::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); |
| #else |
| new ExtensionHost(extension, GetSiteInstanceForURL(url), url, |
| extensions::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); |
| #endif |
| |
| host->CreateRenderViewSoon(); |
| OnExtensionHostCreated(host, true); |
| return host; |
| } |
| |
| ExtensionHost* ExtensionProcessManager::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*> |
| ExtensionProcessManager::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* ExtensionProcessManager::GetExtensionForRenderViewHost( |
| RenderViewHost* render_view_host) { |
| if (!render_view_host->GetSiteInstance()) |
| return NULL; |
| |
| ExtensionService* service = |
| extensions::ExtensionSystem::Get(GetProfile())->extension_service(); |
| if (!service) |
| return NULL; |
| |
| return service->extensions()->GetByID(GetExtensionID(render_view_host)); |
| } |
| |
| void ExtensionProcessManager::UnregisterRenderViewHost( |
| RenderViewHost* render_view_host) { |
| ExtensionRenderViews::iterator view = |
| all_extension_views_.find(render_view_host); |
| if (view == all_extension_views_.end()) |
| return; |
| |
| OnRenderViewHostUnregistered(GetProfile(), render_view_host); |
| extensions::ViewType view_type = view->second; |
| all_extension_views_.erase(view); |
| |
| // Keepalive count, balanced in RegisterRenderViewHost. |
| if (view_type != extensions::VIEW_TYPE_INVALID && |
| view_type != extensions::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) { |
| const Extension* extension = GetExtensionForRenderViewHost( |
| render_view_host); |
| if (extension) |
| DecrementLazyKeepaliveCount(extension); |
| } |
| } |
| |
| void ExtensionProcessManager::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] = |
| extensions::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* ExtensionProcessManager::GetSiteInstanceForURL(const GURL& url) { |
| return site_instance_->GetRelatedSiteInstance(url); |
| } |
| |
| bool ExtensionProcessManager::IsBackgroundHostClosing( |
| const std::string& extension_id) { |
| ExtensionHost* host = GetBackgroundHostForExtension(extension_id); |
| return (host && background_page_data_[extension_id].is_closing); |
| } |
| |
| int ExtensionProcessManager::GetLazyKeepaliveCount(const Extension* extension) { |
| if (!BackgroundInfo::HasLazyBackgroundPage(extension)) |
| return 0; |
| |
| return background_page_data_[extension->id()].lazy_keepalive_count; |
| } |
| |
| int ExtensionProcessManager::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 ExtensionProcessManager::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(&ExtensionProcessManager::OnLazyBackgroundPageIdle, |
| weak_ptr_factory_.GetWeakPtr(), extension->id(), |
| ++background_page_data_[extension->id()].close_sequence_id), |
| event_page_idle_time_); |
| } |
| |
| return count; |
| } |
| |
| void ExtensionProcessManager::IncrementLazyKeepaliveCountForView( |
| RenderViewHost* render_view_host) { |
| WebContents* web_contents = |
| WebContents::FromRenderViewHost(render_view_host); |
| extensions::ViewType view_type = extensions::GetViewType(web_contents); |
| if (view_type != extensions::VIEW_TYPE_INVALID && |
| view_type != extensions::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) { |
| const Extension* extension = GetExtensionForRenderViewHost( |
| render_view_host); |
| if (extension) |
| IncrementLazyKeepaliveCount(extension); |
| } |
| } |
| |
| void ExtensionProcessManager::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 ExtensionProcessManager::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 ExtensionProcessManager::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 ExtensionProcessManager::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(&ExtensionProcessManager::CloseLazyBackgroundPageNow, |
| weak_ptr_factory_.GetWeakPtr(), extension_id, sequence_id), |
| event_page_suspending_time_); |
| } |
| |
| void ExtensionProcessManager::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 ExtensionProcessManager::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 ExtensionProcessManager::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 ExtensionProcessManager::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 ExtensionProcessManager::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 ExtensionProcessManager::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| switch (type) { |
| case chrome::NOTIFICATION_BROWSER_WINDOW_READY: { |
| // If a window for this profile, or this profile's incognito profile, |
| // has been opened, make sure this profile's background hosts have |
| // been loaded. |
| Browser* browser = content::Source<Browser>(source).ptr(); |
| if (browser->profile() != GetProfile() && |
| !(GetProfile()->HasOffTheRecordProfile() && |
| browser->profile() == GetProfile()->GetOffTheRecordProfile())) |
| break; |
| |
| ExtensionService* service = GetProfile()->GetExtensionService(); |
| if (!service || !service->is_ready()) |
| break; |
| |
| CreateBackgroundHostsForProfileStartup(); |
| break; |
| } |
| case chrome::NOTIFICATION_EXTENSIONS_READY: |
| case chrome::NOTIFICATION_PROFILE_CREATED: { |
| CreateBackgroundHostsForProfileStartup(); |
| break; |
| } |
| |
| case chrome::NOTIFICATION_EXTENSION_LOADED: { |
| Profile* profile = content::Source<Profile>(source).ptr(); |
| ExtensionService* service = |
| extensions::ExtensionSystem::Get(profile)->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<extensions::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() == |
| extensions::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() != GetProfile()) |
| 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() != GetProfile()) |
| 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<Profile>(GetProfile()), |
| 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 ExtensionProcessManager::OnDevToolsStateChanged( |
| content::DevToolsAgentHost* agent_host, bool attached) { |
| RenderViewHost* rvh = agent_host->GetRenderViewHost(); |
| // Ignore unrelated notifications. |
| if (!rvh || |
| rvh->GetSiteInstance()->GetProcess()->GetBrowserContext() != GetProfile()) |
| return; |
| if (extensions::GetViewType(WebContents::FromRenderViewHost(rvh)) != |
| extensions::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 ExtensionProcessManager::CreateBackgroundHostsForProfileStartup() { |
| // 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 = GetProfile()->GetExtensionService(); |
| for (ExtensionSet::const_iterator extension = service->extensions()->begin(); |
| extension != service->extensions()->end(); ++extension) { |
| CreateBackgroundHostForExtensionLoad(this, extension->get()); |
| |
| extensions::RuntimeEventRouter::DispatchOnStartupEvent( |
| GetProfile(), (*extension)->id()); |
| } |
| |
| // Background pages should only be loaded once. To prevent any further loads |
| // occurring, we remove the notification listeners. |
| Profile* original_profile = GetProfile()->GetOriginalProfile(); |
| registrar_.Remove(this, chrome::NOTIFICATION_BROWSER_WINDOW_READY, |
| content::NotificationService::AllSources()); |
| if (registrar_.IsRegistered(this, chrome::NOTIFICATION_PROFILE_CREATED, |
| content::Source<Profile>(original_profile))) |
| registrar_.Remove(this, chrome::NOTIFICATION_PROFILE_CREATED, |
| content::Source<Profile>(original_profile)); |
| if (registrar_.IsRegistered(this, chrome::NOTIFICATION_EXTENSIONS_READY, |
| content::Source<Profile>(original_profile))) |
| registrar_.Remove(this, chrome::NOTIFICATION_EXTENSIONS_READY, |
| content::Source<Profile>(original_profile)); |
| } |
| |
| Profile* ExtensionProcessManager::GetProfile() const { |
| return Profile::FromBrowserContext(site_instance_->GetBrowserContext()); |
| } |
| |
| void ExtensionProcessManager::OnExtensionHostCreated(ExtensionHost* host, |
| bool is_background) { |
| DCHECK_EQ(site_instance_->GetBrowserContext(), host->profile()); |
| if (is_background) { |
| 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 ExtensionProcessManager::CloseBackgroundHost(ExtensionHost* host) { |
| CHECK(host->extension_host_type() == |
| extensions::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); |
| delete host; |
| // |host| should deregister itself from our structures. |
| CHECK(background_hosts_.find(host) == background_hosts_.end()); |
| } |
| |
| void ExtensionProcessManager::CloseBackgroundHosts() { |
| for (ExtensionHostSet::iterator iter = background_hosts_.begin(); |
| iter != background_hosts_.end(); ) { |
| ExtensionHostSet::iterator current = iter++; |
| delete *current; |
| } |
| } |
| |
| void ExtensionProcessManager::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. |
| Profile* profile = GetProfile(); |
| for (ExtensionRenderViews::iterator it = all_extension_views_.begin(); |
| it != all_extension_views_.end(); ) { |
| if (GetExtensionID(it->first) == extension_id) { |
| OnRenderViewHostUnregistered(profile, it->first); |
| all_extension_views_.erase(it++); |
| } else { |
| ++it; |
| } |
| } |
| |
| background_page_data_.erase(extension_id); |
| } |
| |
| void ExtensionProcessManager::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 ExtensionProcessManager::DeferLoadingBackgroundHosts() const { |
| // Don't load background hosts now if the loading should be deferred. |
| if (defer_background_host_creation_) |
| return true; |
| |
| // The profile may not be valid yet if it is still being initialized. |
| // In that case, defer loading, since it depends on an initialized profile. |
| // http://crbug.com/222473 |
| if (!g_browser_process->profile_manager()->IsValidProfile(GetProfile())) |
| return true; |
| |
| #if defined(OS_ANDROID) |
| return false; |
| #else |
| return chrome::GetTotalBrowserCountForProfile(GetProfile()) == 0 && |
| CommandLine::ForCurrentProcess()->HasSwitch(switches::kShowAppList); |
| #endif |
| } |
| |
| // |
| // IncognitoExtensionProcessManager |
| // |
| |
| IncognitoExtensionProcessManager::IncognitoExtensionProcessManager( |
| Profile* profile) |
| : ExtensionProcessManager(profile), |
| original_manager_(extensions::ExtensionSystem::Get( |
| profile->GetOriginalProfile())->process_manager()) { |
| DCHECK(profile->IsOffTheRecord()); |
| |
| // The original profile will have its own ExtensionProcessManager 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<Profile>(profile->GetOriginalProfile())); |
| registrar_.Remove(this, chrome::NOTIFICATION_PROFILE_CREATED, |
| content::Source<Profile>(profile->GetOriginalProfile())); |
| } |
| |
| IncognitoExtensionProcessManager::~IncognitoExtensionProcessManager() { |
| // TODO(yoz): This cleanup code belongs in the MenuManager. |
| // Remove "incognito" "split" mode context menu items. |
| ExtensionService* service = |
| extensions::ExtensionSystem::Get(GetProfile())->extension_service(); |
| if (service) |
| service->menu_manager()->RemoveAllIncognitoContextItems(); |
| } |
| |
| ExtensionHost* IncognitoExtensionProcessManager::CreateViewHost( |
| const Extension* extension, |
| const GURL& url, |
| Browser* browser, |
| extensions::ViewType view_type) { |
| if (extensions::IncognitoInfo::IsSplitMode(extension)) { |
| if (IsIncognitoEnabled(extension)) { |
| return ExtensionProcessManager::CreateViewHost(extension, url, |
| browser, view_type); |
| } else { |
| NOTREACHED() << |
| "We shouldn't be trying to create an incognito extension view unless " |
| "it has been enabled for incognito."; |
| return NULL; |
| } |
| } else { |
| return original_manager_->CreateViewHost(extension, url, |
| browser, view_type); |
| } |
| } |
| |
| ExtensionHost* IncognitoExtensionProcessManager::CreateBackgroundHost( |
| const Extension* extension, const GURL& url) { |
| if (extensions::IncognitoInfo::IsSplitMode(extension)) { |
| if (IsIncognitoEnabled(extension)) |
| return ExtensionProcessManager::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* IncognitoExtensionProcessManager::GetSiteInstanceForURL( |
| const GURL& url) { |
| ExtensionService* service = GetProfile()->GetExtensionService(); |
| if (service) { |
| const Extension* extension = |
| service->extensions()->GetExtensionOrAppByURL(url); |
| if (extension && |
| !extensions::IncognitoInfo::IsSplitMode(extension)) { |
| return original_manager_->GetSiteInstanceForURL(url); |
| } |
| } |
| return ExtensionProcessManager::GetSiteInstanceForURL(url); |
| } |
| |
| bool IncognitoExtensionProcessManager::IsIncognitoEnabled( |
| const Extension* extension) { |
| // Keep in sync with duplicate in extension_info_map.cc. |
| ExtensionService* service = GetProfile()->GetExtensionService(); |
| return extension_util::IsIncognitoEnabled(extension->id(), service); |
| } |
| |
| void IncognitoExtensionProcessManager::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| switch (type) { |
| // Do not use ExtensionProcessManager's handler for |
| // NOTIFICATION_BROWSER_WINDOW_READY. |
| case chrome::NOTIFICATION_BROWSER_WINDOW_READY: { |
| // We want to spawn our background hosts as soon as the user opens an |
| // incognito window. Watch for new browsers and create the hosts if |
| // it matches our profile. |
| Browser* browser = content::Source<Browser>(source).ptr(); |
| if (browser->profile() == site_instance_->GetBrowserContext()) { |
| // On Chrome OS, a login screen is implemented as a browser. |
| // This browser has no extension service. In this case, |
| // service will be NULL. |
| ExtensionService* service = GetProfile()->GetExtensionService(); |
| if (service && service->is_ready()) |
| CreateBackgroundHostsForProfileStartup(); |
| } |
| break; |
| } |
| default: |
| ExtensionProcessManager::Observe(type, source, details); |
| break; |
| } |
| } |