| // Copyright 2013 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 <memory> |
| #include <unordered_set> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/guid.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/one_shot_event.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/task/post_task.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/devtools_agent_host.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/service_worker_context.h" |
| #include "content/public/browser/site_instance.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/url_constants.h" |
| #include "extensions/browser/extension_host.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/browser/extensions_browser_client.h" |
| #include "extensions/browser/lazy_context_id.h" |
| #include "extensions/browser/lazy_context_task_queue.h" |
| #include "extensions/browser/notification_types.h" |
| #include "extensions/browser/process_manager_delegate.h" |
| #include "extensions/browser/process_manager_factory.h" |
| #include "extensions/browser/process_manager_observer.h" |
| #include "extensions/browser/view_type_utils.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_messages.h" |
| #include "extensions/common/manifest_handlers/background_info.h" |
| #include "extensions/common/manifest_handlers/incognito_info.h" |
| |
| using content::BrowserContext; |
| |
| namespace extensions { |
| |
| namespace { |
| |
| // The time to delay between an extension becoming idle and |
| // sending a ShouldSuspend message. |
| unsigned g_event_page_idle_time_msec = 10000; |
| |
| // The time to delay between sending a ShouldSuspend message and |
| // sending a Suspend message. |
| unsigned g_event_page_suspending_time_msec = 5000; |
| |
| std::string GetExtensionIdForSiteInstance( |
| content::SiteInstance* site_instance) { |
| if (!site_instance) |
| return std::string(); |
| |
| // This works for both apps and extensions because the site has been |
| // normalized to the extension URL for hosted apps. |
| const GURL& site_url = site_instance->GetSiteURL(); |
| |
| if (!site_url.SchemeIs(kExtensionScheme) && |
| !site_url.SchemeIs(content::kGuestScheme)) |
| return std::string(); |
| |
| return site_url.host(); |
| } |
| |
| std::string GetExtensionID(content::RenderFrameHost* render_frame_host) { |
| CHECK(render_frame_host); |
| return GetExtensionIdForSiteInstance(render_frame_host->GetSiteInstance()); |
| } |
| |
| bool IsFrameInExtensionHost(ExtensionHost* extension_host, |
| content::RenderFrameHost* render_frame_host) { |
| return content::WebContents::FromRenderFrameHost(render_frame_host) == |
| extension_host->host_contents(); |
| } |
| |
| // 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. |
| // TODO(devlin): Given how little this does and the amount of cruft it adds to |
| // the .h file (in the form of protected members), we should consider just |
| // moving the incognito logic into the base class. |
| class IncognitoProcessManager : public ProcessManager { |
| public: |
| IncognitoProcessManager(BrowserContext* incognito_context, |
| BrowserContext* original_context, |
| ExtensionRegistry* extension_registry); |
| ~IncognitoProcessManager() override {} |
| bool CreateBackgroundHost(const Extension* extension, |
| const GURL& url) override; |
| scoped_refptr<content::SiteInstance> GetSiteInstanceForURL(const GURL& url) |
| override; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(IncognitoProcessManager); |
| }; |
| |
| static void CreateBackgroundHostForExtensionLoad( |
| ProcessManager* manager, const Extension* extension) { |
| if (BackgroundInfo::HasPersistentBackgroundPage(extension)) |
| manager->CreateBackgroundHost(extension, |
| BackgroundInfo::GetBackgroundURL(extension)); |
| } |
| |
| void PropagateExtensionWakeResult( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<LazyContextTaskQueue::ContextInfo> context_info) { |
| std::move(callback).Run(context_info != nullptr); |
| } |
| |
| void StartServiceWorkerExternalRequest(content::ServiceWorkerContext* context, |
| int64_t service_worker_version_id, |
| const std::string& request_uuid) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| context->StartingExternalRequest(service_worker_version_id, request_uuid); |
| } |
| |
| void FinishServiceWorkerExternalRequest(content::ServiceWorkerContext* context, |
| int64_t service_worker_version_id, |
| const std::string& request_uuid) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| bool status = |
| context->FinishedExternalRequest(service_worker_version_id, request_uuid); |
| DCHECK(status); |
| } |
| |
| } // namespace |
| |
| struct ProcessManager::BackgroundPageData { |
| // The count of things keeping the lazy background page alive. |
| // TODO(crbug.com://695711): Work on a plan to remove this and rely |
| // on activities.size() instead. |
| int lazy_keepalive_count = 0; |
| |
| // 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 = false; |
| |
| // Stores the value of the incremented |
| // ProcessManager::last_background_close_sequence_id_ whenever the extension |
| // is active. A copy of the ID is also passed in the callbacks and IPC |
| // messages leading up to CloseLazyBackgroundPageNow. The process is aborted |
| // if the IDs ever differ due to new activity. |
| uint64_t close_sequence_id = 0ull; |
| |
| // Keeps track of when this page was last suspended. Used for perf metrics. |
| std::unique_ptr<base::ElapsedTimer> since_suspended; |
| |
| ActivitiesMultiset activities; |
| }; |
| |
| // Data of a RenderFrameHost associated with an extension. |
| struct ProcessManager::ExtensionRenderFrameData { |
| // The type of the view. |
| extensions::ViewType view_type = VIEW_TYPE_INVALID; |
| |
| // Whether the view is keeping the lazy background page alive or not. |
| bool has_keepalive = false; |
| |
| // Returns whether the view can keep the lazy background page alive or not. |
| bool CanKeepalive() const { |
| switch (view_type) { |
| case VIEW_TYPE_APP_WINDOW: |
| case VIEW_TYPE_BACKGROUND_CONTENTS: |
| case VIEW_TYPE_COMPONENT: |
| case VIEW_TYPE_EXTENSION_DIALOG: |
| case VIEW_TYPE_EXTENSION_GUEST: |
| case VIEW_TYPE_EXTENSION_POPUP: |
| case VIEW_TYPE_TAB_CONTENTS: |
| return true; |
| |
| case VIEW_TYPE_INVALID: |
| case VIEW_TYPE_EXTENSION_BACKGROUND_PAGE: |
| return false; |
| } |
| NOTREACHED(); |
| return false; |
| } |
| }; |
| |
| // |
| // ProcessManager |
| // |
| |
| // static |
| ProcessManager* ProcessManager::Get(BrowserContext* context) { |
| return ProcessManagerFactory::GetForBrowserContext(context); |
| } |
| |
| // static |
| ProcessManager* ProcessManager::Create(BrowserContext* context) { |
| ExtensionRegistry* extension_registry = ExtensionRegistry::Get(context); |
| ExtensionsBrowserClient* client = ExtensionsBrowserClient::Get(); |
| if (client->IsGuestSession(context)) { |
| // In the guest session, there is a single off-the-record context. Unlike |
| // a regular incognito mode, background pages of extensions must be |
| // created regardless of whether extensions use "spanning" or "split" |
| // incognito behavior. |
| BrowserContext* original_context = client->GetOriginalContext(context); |
| return new ProcessManager(context, original_context, extension_registry); |
| } |
| |
| if (context->IsOffTheRecord()) { |
| BrowserContext* original_context = client->GetOriginalContext(context); |
| return new IncognitoProcessManager( |
| context, original_context, extension_registry); |
| } |
| |
| return new ProcessManager(context, context, extension_registry); |
| } |
| |
| // static |
| ProcessManager* ProcessManager::CreateForTesting( |
| BrowserContext* context, |
| ExtensionRegistry* extension_registry) { |
| DCHECK(!context->IsOffTheRecord()); |
| return new ProcessManager(context, context, extension_registry); |
| } |
| |
| // static |
| ProcessManager* ProcessManager::CreateIncognitoForTesting( |
| BrowserContext* incognito_context, |
| BrowserContext* original_context, |
| ExtensionRegistry* extension_registry) { |
| DCHECK(incognito_context->IsOffTheRecord()); |
| DCHECK(!original_context->IsOffTheRecord()); |
| return new IncognitoProcessManager(incognito_context, |
| original_context, |
| extension_registry); |
| } |
| |
| ProcessManager::ProcessManager(BrowserContext* context, |
| BrowserContext* original_context, |
| ExtensionRegistry* extension_registry) |
| : extension_registry_(extension_registry), |
| site_instance_(content::SiteInstance::Create(context)), |
| browser_context_(context), |
| worker_task_runner_(base::CreateSingleThreadTaskRunnerWithTraits( |
| {content::BrowserThread::IO})), |
| startup_background_hosts_created_(false), |
| last_background_close_sequence_id_(0), |
| process_observer_(this) { |
| // ExtensionRegistry is shared between incognito and regular contexts. |
| DCHECK_EQ(original_context, extension_registry_->browser_context()); |
| extension_registry_->AddObserver(this); |
| |
| if (!context->IsOffTheRecord()) { |
| // Only the original profile needs to listen for ready to create background |
| // pages for all spanning extensions. |
| registrar_.Add(this, |
| extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, |
| content::Source<BrowserContext>(original_context)); |
| } |
| registrar_.Add(this, |
| extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED, |
| content::Source<BrowserContext>(context)); |
| registrar_.Add(this, |
| extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE, |
| content::Source<BrowserContext>(context)); |
| content::DevToolsAgentHost::AddObserver(this); |
| } |
| |
| ProcessManager::~ProcessManager() { |
| content::DevToolsAgentHost::RemoveObserver(this); |
| } |
| |
| void ProcessManager::Shutdown() { |
| extension_registry_->RemoveObserver(this); |
| CloseBackgroundHosts(); |
| DCHECK(background_hosts_.empty()); |
| content::DevToolsAgentHost::RemoveObserver(this); |
| site_instance_ = nullptr; |
| } |
| |
| void ProcessManager::RegisterRenderFrameHost( |
| content::WebContents* web_contents, |
| content::RenderFrameHost* render_frame_host, |
| const Extension* extension) { |
| DCHECK(render_frame_host->IsRenderFrameLive()); |
| ExtensionRenderFrameData* data = &all_extension_frames_[render_frame_host]; |
| data->view_type = GetViewType(web_contents); |
| |
| // Keep the lazy background page alive as long as any non-background-page |
| // extension views are visible. Keepalive count balanced in |
| // UnregisterRenderFrame. |
| AcquireLazyKeepaliveCountForFrame(render_frame_host); |
| |
| for (auto& observer : observer_list_) |
| observer.OnExtensionFrameRegistered(extension->id(), render_frame_host); |
| } |
| |
| void ProcessManager::UnregisterRenderFrameHost( |
| content::RenderFrameHost* render_frame_host) { |
| auto frame = all_extension_frames_.find(render_frame_host); |
| |
| if (frame != all_extension_frames_.end()) { |
| std::string extension_id = GetExtensionID(render_frame_host); |
| // Keepalive count, balanced in RegisterRenderFrame. |
| ReleaseLazyKeepaliveCountForFrame(render_frame_host); |
| all_extension_frames_.erase(frame); |
| |
| for (auto& observer : observer_list_) |
| observer.OnExtensionFrameUnregistered(extension_id, render_frame_host); |
| } |
| } |
| |
| scoped_refptr<content::SiteInstance> ProcessManager::GetSiteInstanceForURL( |
| const GURL& url) { |
| return site_instance_->GetRelatedSiteInstance(url); |
| } |
| |
| const ProcessManager::FrameSet ProcessManager::GetAllFrames() const { |
| FrameSet result; |
| for (const auto& key_value : all_extension_frames_) |
| result.insert(key_value.first); |
| return result; |
| } |
| |
| ProcessManager::FrameSet ProcessManager::GetRenderFrameHostsForExtension( |
| const std::string& extension_id) { |
| FrameSet result; |
| for (const auto& key_value : all_extension_frames_) { |
| if (GetExtensionID(key_value.first) == extension_id) |
| result.insert(key_value.first); |
| } |
| return result; |
| } |
| |
| bool ProcessManager::IsRenderFrameHostRegistered( |
| content::RenderFrameHost* render_frame_host) { |
| return all_extension_frames_.find(render_frame_host) != |
| all_extension_frames_.end(); |
| } |
| |
| void ProcessManager::AddObserver(ProcessManagerObserver* observer) { |
| observer_list_.AddObserver(observer); |
| } |
| |
| void ProcessManager::RemoveObserver(ProcessManagerObserver* observer) { |
| observer_list_.RemoveObserver(observer); |
| } |
| |
| bool ProcessManager::CreateBackgroundHost(const Extension* extension, |
| const GURL& url) { |
| DCHECK(!BackgroundInfo::IsServiceWorkerBased(extension)) |
| << "CreateBackgroundHostForExtensionLoad called for service worker based" |
| "background page"; |
| // Hosted apps are taken care of from BackgroundContentsService. Ignore them |
| // here. |
| if (extension->is_hosted_app()) |
| return false; |
| |
| // Don't create hosts if the embedder doesn't allow it. |
| ProcessManagerDelegate* delegate = |
| ExtensionsBrowserClient::Get()->GetProcessManagerDelegate(); |
| if (delegate && |
| !delegate->IsExtensionBackgroundPageAllowed(browser_context_, *extension)) |
| return false; |
| |
| // Don't create multiple background hosts for an extension. |
| if (GetBackgroundHostForExtension(extension->id())) |
| return true; // TODO(kalman): return false here? It might break things... |
| |
| DVLOG(1) << "CreateBackgroundHost " << extension->id(); |
| ExtensionHost* host = |
| new ExtensionHost(extension, GetSiteInstanceForURL(url).get(), url, |
| VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); |
| host->CreateRenderViewSoon(); |
| OnBackgroundHostCreated(host); |
| return true; |
| } |
| |
| void ProcessManager::MaybeCreateStartupBackgroundHosts() { |
| if (startup_background_hosts_created_) |
| return; |
| |
| // The embedder might disallow background pages entirely. |
| ProcessManagerDelegate* delegate = |
| ExtensionsBrowserClient::Get()->GetProcessManagerDelegate(); |
| if (delegate && |
| !delegate->AreBackgroundPagesAllowedForContext(browser_context_)) |
| return; |
| |
| // The embedder might want to defer background page loading. For example, |
| // Chrome defers background page loading when it is launched to show the app |
| // list, then triggers a load later when a browser window opens. |
| if (delegate && |
| delegate->DeferCreatingStartupBackgroundHosts(browser_context_)) |
| return; |
| |
| CreateStartupBackgroundHosts(); |
| 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(browser_context_); |
| if (registrar_.IsRegistered( |
| this, |
| extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, |
| content::Source<BrowserContext>(original_context))) { |
| registrar_.Remove(this, |
| extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, |
| content::Source<BrowserContext>(original_context)); |
| } |
| } |
| |
| ExtensionHost* ProcessManager::GetBackgroundHostForExtension( |
| const std::string& extension_id) { |
| for (ExtensionHost* host : background_hosts_) { |
| if (host->extension_id() == extension_id) |
| return host; |
| } |
| return nullptr; |
| } |
| |
| ExtensionHost* ProcessManager::GetExtensionHostForRenderFrameHost( |
| content::RenderFrameHost* render_frame_host) { |
| content::WebContents* web_contents = |
| content::WebContents::FromRenderFrameHost(render_frame_host); |
| for (ExtensionHost* extension_host : background_hosts_) { |
| if (extension_host->host_contents() == web_contents) |
| return extension_host; |
| } |
| return nullptr; |
| } |
| |
| bool ProcessManager::IsEventPageSuspended(const std::string& extension_id) { |
| return GetBackgroundHostForExtension(extension_id) == nullptr; |
| } |
| |
| bool ProcessManager::WakeEventPage(const std::string& extension_id, |
| base::OnceCallback<void(bool)> callback) { |
| if (GetBackgroundHostForExtension(extension_id)) { |
| // The extension is already awake. |
| return false; |
| } |
| const LazyContextId context_id(browser_context_, extension_id); |
| context_id.GetTaskQueue()->AddPendingTask( |
| context_id, |
| base::BindOnce(&PropagateExtensionWakeResult, std::move(callback))); |
| return true; |
| } |
| |
| bool ProcessManager::IsBackgroundHostClosing(const std::string& extension_id) { |
| ExtensionHost* host = GetBackgroundHostForExtension(extension_id); |
| return (host && background_page_data_[extension_id].is_closing); |
| } |
| |
| const Extension* ProcessManager::GetExtensionForRenderFrameHost( |
| content::RenderFrameHost* render_frame_host) { |
| return extension_registry_->enabled_extensions().GetByID( |
| GetExtensionID(render_frame_host)); |
| } |
| |
| const Extension* ProcessManager::GetExtensionForWebContents( |
| content::WebContents* web_contents) { |
| if (!web_contents->GetSiteInstance()) |
| return nullptr; |
| const Extension* extension = |
| extension_registry_->enabled_extensions().GetByID( |
| GetExtensionIdForSiteInstance(web_contents->GetSiteInstance())); |
| if (extension && extension->is_hosted_app()) { |
| // For hosted apps, be sure to exclude URLs outside of the app that might |
| // be loaded in the same SiteInstance (extensions guarantee that only |
| // extension urls are loaded in that SiteInstance). |
| content::NavigationController& controller = web_contents->GetController(); |
| content::NavigationEntry* entry = controller.GetLastCommittedEntry(); |
| // If there is no last committed entry, check the pending entry. This can |
| // happen in cases where we query this before any entry is fully committed, |
| // such as when attributing a WebContents for the TaskManager. If there is |
| // a committed navigation, use that instead. |
| if (!entry) |
| entry = controller.GetPendingEntry(); |
| if (!entry || |
| extension_registry_->enabled_extensions().GetExtensionOrAppByURL( |
| entry->GetURL()) != extension) { |
| return nullptr; |
| } |
| } |
| |
| return extension; |
| } |
| |
| int ProcessManager::GetLazyKeepaliveCount(const Extension* extension) { |
| if (!BackgroundInfo::HasLazyBackgroundPage(extension)) |
| return -1; |
| |
| return background_page_data_[extension->id()].lazy_keepalive_count; |
| } |
| |
| void ProcessManager::IncrementLazyKeepaliveCount( |
| const Extension* extension, |
| Activity::Type activity_type, |
| const std::string& extra_data) { |
| if (BackgroundInfo::HasLazyBackgroundPage(extension)) { |
| BackgroundPageData& data = background_page_data_[extension->id()]; |
| if (++data.lazy_keepalive_count == 1) |
| OnLazyBackgroundPageActive(extension->id()); |
| data.activities.insert(std::make_pair(activity_type, extra_data)); |
| } |
| } |
| |
| void ProcessManager::DecrementLazyKeepaliveCount( |
| const Extension* extension, |
| Activity::Type activity_type, |
| const std::string& extra_data) { |
| if (BackgroundInfo::HasLazyBackgroundPage(extension)) |
| DecrementLazyKeepaliveCount(extension->id(), activity_type, extra_data); |
| } |
| |
| ProcessManager::ActivitiesMultiset ProcessManager::GetLazyKeepaliveActivities( |
| const Extension* extension) { |
| ProcessManager::ActivitiesMultiset result; |
| if (BackgroundInfo::HasLazyBackgroundPage(extension)) |
| result = background_page_data_[extension->id()].activities; |
| return result; |
| } |
| |
| void ProcessManager::OnShouldSuspendAck(const std::string& extension_id, |
| uint64_t sequence_id) { |
| ExtensionHost* host = GetBackgroundHostForExtension(extension_id); |
| if (host && |
| sequence_id == background_page_data_[extension_id].close_sequence_id) { |
| host->render_process_host()->Send(new ExtensionMsg_Suspend(extension_id)); |
| } |
| } |
| |
| void ProcessManager::OnSuspendAck(const std::string& extension_id) { |
| background_page_data_[extension_id].is_closing = true; |
| uint64_t sequence_id = background_page_data_[extension_id].close_sequence_id; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&ProcessManager::CloseLazyBackgroundPageNow, |
| weak_ptr_factory_.GetWeakPtr(), extension_id, sequence_id), |
| base::TimeDelta::FromMilliseconds(g_event_page_suspending_time_msec)); |
| } |
| |
| void ProcessManager::OnNetworkRequestStarted( |
| content::RenderFrameHost* render_frame_host, |
| uint64_t request_id) { |
| ExtensionHost* host = GetBackgroundHostForExtension( |
| GetExtensionID(render_frame_host)); |
| if (!host || !IsFrameInExtensionHost(host, render_frame_host)) |
| return; |
| |
| auto result = |
| pending_network_requests_.insert(std::make_pair(request_id, host)); |
| DCHECK(result.second) << "Duplicate network request IDs."; |
| |
| IncrementLazyKeepaliveCount(host->extension(), Activity::NETWORK, |
| base::NumberToString(request_id)); |
| host->OnNetworkRequestStarted(request_id); |
| } |
| |
| void ProcessManager::OnNetworkRequestDone( |
| content::RenderFrameHost* render_frame_host, |
| uint64_t request_id) { |
| auto result = pending_network_requests_.find(request_id); |
| if (result == pending_network_requests_.end()) |
| return; |
| |
| // The cached |host| can be invalid, if it was deleted between the time it |
| // was inserted in the map and the look up. It is checked to ensure it is in |
| // the list of existing background_hosts_. |
| ExtensionHost* host = result->second; |
| pending_network_requests_.erase(result); |
| |
| if (background_hosts_.find(host) == background_hosts_.end()) |
| return; |
| |
| DCHECK(IsFrameInExtensionHost(host, render_frame_host)); |
| |
| host->OnNetworkRequestDone(request_id); |
| DecrementLazyKeepaliveCount(host->extension(), Activity::NETWORK, |
| base::NumberToString(request_id)); |
| } |
| |
| 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_process_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, Activity::PROCESS_MANAGER, |
| Activity::kCancelSuspend); |
| DecrementLazyKeepaliveCount(extension, Activity::PROCESS_MANAGER, |
| Activity::kCancelSuspend); |
| } |
| } |
| |
| void ProcessManager::CloseBackgroundHosts() { |
| // Delete from a copy because deletion of the ExtensionHosts will trigger |
| // callbacks to modify the |background_hosts_| set. |
| ExtensionHostSet hosts_copy = background_hosts_; |
| for (auto* host : hosts_copy) { |
| // Deleting the host will cause a NOTIFICATION_EXTENSION_HOST_DESTROYED |
| // which will cause the removal of the host from the |background_hosts_| set |
| // in the Observe() method below. |
| delete host; |
| DCHECK_EQ(0u, background_hosts_.count(host)); |
| } |
| |
| // At this point there should be nothing left in |background_hosts_|. |
| DCHECK(background_hosts_.empty()); |
| } |
| |
| // static |
| void ProcessManager::SetEventPageIdleTimeForTesting(unsigned idle_time_msec) { |
| CHECK_GT(idle_time_msec, 0u); |
| g_event_page_idle_time_msec = idle_time_msec; |
| } |
| |
| // static |
| void ProcessManager::SetEventPageSuspendingTimeForTesting( |
| unsigned suspending_time_msec) { |
| g_event_page_suspending_time_msec = suspending_time_msec; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Private |
| |
| void ProcessManager::Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| TRACE_EVENT0("browser,startup", "ProcessManager::Observe"); |
| switch (type) { |
| case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED: { |
| // TODO(jamescook): Convert this to use ExtensionSystem::ready() instead |
| // of a notification. |
| SCOPED_UMA_HISTOGRAM_TIMER("Extensions.ProcessManagerStartupHostsTime"); |
| MaybeCreateStartupBackgroundHosts(); |
| break; |
| } |
| case extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED: { |
| ExtensionHost* host = content::Details<ExtensionHost>(details).ptr(); |
| if (background_hosts_.erase(host)) { |
| // Note: |host->extension()| may be null at this point. |
| ClearBackgroundPageData(host->extension_id()); |
| background_page_data_[host->extension_id()].since_suspended.reset( |
| new base::ElapsedTimer()); |
| } |
| break; |
| } |
| case extensions::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; |
| } |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void ProcessManager::OnExtensionLoaded(BrowserContext* browser_context, |
| const Extension* extension) { |
| if (ExtensionSystem::Get(browser_context)->ready().is_signaled()) { |
| // The extension system is ready, so create the background host. |
| CreateBackgroundHostForExtensionLoad(this, extension); |
| } |
| } |
| |
| void ProcessManager::OnExtensionUnloaded(BrowserContext* browser_context, |
| const Extension* extension, |
| UnloadedExtensionReason reason) { |
| ExtensionHost* host = GetBackgroundHostForExtension(extension->id()); |
| if (host != nullptr) |
| CloseBackgroundHost(host); |
| UnregisterExtension(extension->id()); |
| } |
| |
| void ProcessManager::CreateStartupBackgroundHosts() { |
| DCHECK(!startup_background_hosts_created_); |
| for (const scoped_refptr<const Extension>& extension : |
| extension_registry_->enabled_extensions()) { |
| CreateBackgroundHostForExtensionLoad(this, extension.get()); |
| for (auto& observer : observer_list_) |
| observer.OnBackgroundHostStartup(extension.get()); |
| } |
| } |
| |
| void ProcessManager::OnBackgroundHostCreated(ExtensionHost* host) { |
| DCHECK_EQ(browser_context_, host->browser_context()); |
| background_hosts_.insert(host); |
| |
| if (BackgroundInfo::HasLazyBackgroundPage(host->extension())) { |
| std::unique_ptr<base::ElapsedTimer> since_suspended = std::move( |
| background_page_data_[host->extension()->id()].since_suspended); |
| if (since_suspended.get()) { |
| UMA_HISTOGRAM_LONG_TIMES("Extensions.EventPageIdleTime", |
| since_suspended->Elapsed()); |
| } |
| } |
| for (auto& observer : observer_list_) |
| observer.OnBackgroundHostCreated(host); |
| } |
| |
| void ProcessManager::CloseBackgroundHost(ExtensionHost* host) { |
| ExtensionId extension_id = host->extension_id(); |
| 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()); |
| |
| for (auto& observer : observer_list_) |
| observer.OnBackgroundHostClose(extension_id); |
| } |
| |
| void ProcessManager::AcquireLazyKeepaliveCountForFrame( |
| content::RenderFrameHost* render_frame_host) { |
| auto it = all_extension_frames_.find(render_frame_host); |
| if (it == all_extension_frames_.end()) |
| return; |
| |
| ExtensionRenderFrameData& data = it->second; |
| if (data.CanKeepalive() && !data.has_keepalive) { |
| const Extension* extension = |
| GetExtensionForRenderFrameHost(render_frame_host); |
| if (extension) { |
| IncrementLazyKeepaliveCount(extension, Activity::PROCESS_MANAGER, |
| Activity::kRenderFrame); |
| data.has_keepalive = true; |
| } |
| } |
| } |
| |
| void ProcessManager::ReleaseLazyKeepaliveCountForFrame( |
| content::RenderFrameHost* render_frame_host) { |
| auto iter = all_extension_frames_.find(render_frame_host); |
| if (iter == all_extension_frames_.end()) |
| return; |
| |
| ExtensionRenderFrameData& data = iter->second; |
| if (data.CanKeepalive() && data.has_keepalive) { |
| const Extension* extension = |
| GetExtensionForRenderFrameHost(render_frame_host); |
| if (extension) { |
| DecrementLazyKeepaliveCount(extension, Activity::PROCESS_MANAGER, |
| Activity::kRenderFrame); |
| data.has_keepalive = false; |
| } |
| } |
| } |
| |
| std::string ProcessManager::IncrementServiceWorkerKeepaliveCount( |
| const WorkerId& worker_id, |
| Activity::Type activity_type, |
| const std::string& extra_data) { |
| // TODO(lazyboy): Use |activity_type| and |extra_data|. |
| int64_t service_worker_version_id = worker_id.version_id; |
| DCHECK(!worker_id.extension_id.empty()); |
| const Extension* extension = |
| extension_registry_->enabled_extensions().GetByID(worker_id.extension_id); |
| |
| DCHECK(extension); |
| DCHECK(BackgroundInfo::IsServiceWorkerBased(extension)); |
| |
| std::string request_uuid = base::GenerateGUID(); |
| content::ServiceWorkerContext* service_worker_context = |
| content::BrowserContext::GetStoragePartitionForSite(browser_context_, |
| extension->url()) |
| ->GetServiceWorkerContext(); |
| |
| content::ServiceWorkerContext::RunTask( |
| worker_task_runner_, FROM_HERE, service_worker_context, |
| base::BindOnce(&StartServiceWorkerExternalRequest, service_worker_context, |
| service_worker_version_id, request_uuid)); |
| return request_uuid; |
| } |
| |
| void ProcessManager::DecrementLazyKeepaliveCount( |
| const std::string& extension_id, |
| Activity::Type activity_type, |
| const std::string& extra_data) { |
| BackgroundPageData& data = background_page_data_[extension_id]; |
| |
| DCHECK(data.lazy_keepalive_count > 0 || |
| !extension_registry_->enabled_extensions().Contains(extension_id)); |
| --data.lazy_keepalive_count; |
| const auto it = |
| data.activities.find(std::make_pair(activity_type, extra_data)); |
| if (it != data.activities.end()) { |
| data.activities.erase(it); |
| } |
| |
| // 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 (data.lazy_keepalive_count == 0) { |
| // Clear any leftover activities. |
| data.activities.clear(); |
| if (!background_page_data_[extension_id].is_closing) { |
| data.close_sequence_id = ++last_background_close_sequence_id_; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&ProcessManager::OnLazyBackgroundPageIdle, |
| weak_ptr_factory_.GetWeakPtr(), extension_id, |
| last_background_close_sequence_id_), |
| base::TimeDelta::FromMilliseconds(g_event_page_idle_time_msec)); |
| } |
| } |
| } |
| |
| void ProcessManager::DecrementServiceWorkerKeepaliveCount( |
| const WorkerId& worker_id, |
| const std::string& request_uuid, |
| Activity::Type activity_type, |
| const std::string& extra_data) { |
| DCHECK(!worker_id.extension_id.empty()); |
| const Extension* extension = |
| extension_registry_->enabled_extensions().GetByID(worker_id.extension_id); |
| if (!extension) |
| return; |
| |
| DCHECK(BackgroundInfo::IsServiceWorkerBased(extension)); |
| |
| int64_t service_worker_version_id = worker_id.version_id; |
| content::ServiceWorkerContext* service_worker_context = |
| content::BrowserContext::GetStoragePartitionForSite(browser_context_, |
| extension->url()) |
| ->GetServiceWorkerContext(); |
| |
| content::ServiceWorkerContext::RunTask( |
| worker_task_runner_, FROM_HERE, service_worker_context, |
| base::BindOnce(&FinishServiceWorkerExternalRequest, |
| service_worker_context, service_worker_version_id, |
| request_uuid)); |
| } |
| |
| void ProcessManager::OnLazyBackgroundPageIdle(const std::string& extension_id, |
| uint64_t 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_process_host()->Send(new ExtensionMsg_ShouldSuspend( |
| extension_id, sequence_id)); |
| } |
| } |
| |
| void ProcessManager::OnLazyBackgroundPageActive( |
| const std::string& extension_id) { |
| if (!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 = |
| ++last_background_close_sequence_id_; |
| } |
| } |
| |
| void ProcessManager::CloseLazyBackgroundPageNow(const std::string& extension_id, |
| uint64_t sequence_id) { |
| ExtensionHost* host = GetBackgroundHostForExtension(extension_id); |
| if (host && |
| sequence_id == background_page_data_[extension_id].close_sequence_id) { |
| // Handle the case where the keepalive count was increased after the |
| // OnSuspend event was sent. |
| if (background_page_data_[extension_id].lazy_keepalive_count > 0) { |
| CancelSuspend(host->extension()); |
| return; |
| } |
| |
| // Close remaining views. |
| std::vector<content::RenderFrameHost*> frames_to_close; |
| for (const auto& key_value : all_extension_frames_) { |
| if (key_value.second.CanKeepalive() && |
| GetExtensionID(key_value.first) == extension_id) { |
| DCHECK(!key_value.second.has_keepalive); |
| frames_to_close.push_back(key_value.first); |
| } |
| } |
| for (content::RenderFrameHost* frame : frames_to_close) { |
| content::WebContents::FromRenderFrameHost(frame)->ClosePage(); |
| // WebContents::ClosePage() may result in calling |
| // UnregisterRenderFrameHost() asynchronously and may cause race |
| // conditions when the background page is reloaded. |
| // To avoid this, unregister the view now. |
| UnregisterRenderFrameHost(frame); |
| } |
| |
| ExtensionHost* host = GetBackgroundHostForExtension(extension_id); |
| if (host) |
| CloseBackgroundHost(host); |
| } |
| } |
| |
| const Extension* ProcessManager::GetExtensionForAgentHost( |
| content::DevToolsAgentHost* agent_host) { |
| content::WebContents* web_contents = agent_host->GetWebContents(); |
| // Ignore unrelated notifications. |
| if (!web_contents || web_contents->GetBrowserContext() != browser_context_) |
| return nullptr; |
| if (GetViewType(web_contents) != VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) |
| return nullptr; |
| return GetExtensionForWebContents(web_contents); |
| } |
| |
| void ProcessManager::DevToolsAgentHostAttached( |
| content::DevToolsAgentHost* agent_host) { |
| if (const Extension* extension = GetExtensionForAgentHost(agent_host)) { |
| // Keep the lazy background page alive while it's being inspected. |
| CancelSuspend(extension); |
| IncrementLazyKeepaliveCount(extension, Activity::DEV_TOOLS, std::string()); |
| } |
| } |
| |
| void ProcessManager::DevToolsAgentHostDetached( |
| content::DevToolsAgentHost* agent_host) { |
| if (const Extension* extension = GetExtensionForAgentHost(agent_host)) |
| DecrementLazyKeepaliveCount(extension, Activity::DEV_TOOLS, ""); |
| } |
| |
| void ProcessManager::UnregisterExtension(const std::string& extension_id) { |
| // The lazy_keepalive_count may be greater than zero at this point because |
| // RenderFrameHosts 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 RenderFrameHosts too. |
| for (auto it = all_extension_frames_.begin(); |
| it != all_extension_frames_.end();) { |
| content::RenderFrameHost* host = it->first; |
| if (GetExtensionID(host) == extension_id) { |
| all_extension_frames_.erase(it++); |
| for (auto& observer : observer_list_) |
| observer.OnExtensionFrameUnregistered(extension_id, host); |
| } else { |
| ++it; |
| } |
| } |
| |
| background_page_data_.erase(extension_id); |
| |
| all_extension_workers_.RemoveAllForExtension(extension_id); |
| } |
| |
| void ProcessManager::RegisterServiceWorker(const WorkerId& worker_id) { |
| all_extension_workers_.Add(worker_id); |
| |
| // Observe the RenderProcessHost for cleaning up on process shutdown. |
| int render_process_id = worker_id.render_process_id; |
| bool inserted = worker_process_to_extension_ids_[render_process_id] |
| .insert(worker_id.extension_id) |
| .second; |
| if (inserted) { |
| content::RenderProcessHost* render_process_host = |
| content::RenderProcessHost::FromID(render_process_id); |
| DCHECK(render_process_host); |
| if (!process_observer_.IsObserving(render_process_host)) { |
| // These will be cleaned up in RenderProcessExited(). |
| process_observer_.Add(render_process_host); |
| } |
| } |
| } |
| |
| void ProcessManager::RenderProcessExited( |
| content::RenderProcessHost* host, |
| const content::ChildProcessTerminationInfo& info) { |
| DCHECK(process_observer_.IsObserving(host)); |
| process_observer_.Remove(host); |
| const int render_process_id = host->GetID(); |
| // Look up and then clean up the entries that are affected by |
| // |render_process_id| destruction. |
| // |
| // TODO(lazyboy): Revisit this once incognito is tested for extension SWs, as |
| // the cleanup below only works because regular and OTR ProcessManagers are |
| // separate. The conclusive approach would be to have a |
| // all_extension_workers_.RemoveAllForProcess(render_process_id) method: |
| // Pros: We won't need worker_process_to_extension_ids_ anymore. |
| // Cons: We would require traversing all workers within |
| // |all_extension_workers_| (slow) as things stand right now. |
| auto iter = worker_process_to_extension_ids_.find(render_process_id); |
| if (iter == worker_process_to_extension_ids_.end()) |
| return; |
| for (const ExtensionId& extension_id : iter->second) |
| all_extension_workers_.RemoveAllForExtension(extension_id); |
| worker_process_to_extension_ids_.erase(iter); |
| } |
| |
| void ProcessManager::UnregisterServiceWorker(const WorkerId& worker_id) { |
| // TODO(lazyboy): DCHECK that |worker_id| exists in |all_extension_workers_|. |
| all_extension_workers_.Remove(worker_id); |
| } |
| |
| bool ProcessManager::HasServiceWorker(const WorkerId& worker_id) const { |
| return all_extension_workers_.Contains(worker_id); |
| } |
| |
| std::vector<WorkerId> ProcessManager::GetServiceWorkers( |
| const ExtensionId& extension_id, |
| int render_process_id) const { |
| return all_extension_workers_.GetAllForExtension(extension_id, |
| render_process_id); |
| } |
| |
| std::vector<WorkerId> ProcessManager::GetAllWorkersIdsForTesting() { |
| return all_extension_workers_.GetAllForTesting(); |
| } |
| |
| void ProcessManager::ClearBackgroundPageData(const std::string& extension_id) { |
| background_page_data_.erase(extension_id); |
| |
| // Re-register all RenderFrames for this extension. We do this to restore |
| // the lazy_keepalive_count (if any) to properly reflect the number of open |
| // views. |
| for (const auto& key_value : all_extension_frames_) { |
| // Do not increment the count when |has_keepalive| is false |
| // (i.e. ReleaseLazyKeepaliveCountForView() was called). |
| if (GetExtensionID(key_value.first) == extension_id && |
| key_value.second.has_keepalive) { |
| const Extension* extension = |
| GetExtensionForRenderFrameHost(key_value.first); |
| if (extension) |
| IncrementLazyKeepaliveCount(extension, Activity::PROCESS_MANAGER, |
| Activity::kRenderFrame); |
| } |
| } |
| } |
| |
| // |
| // IncognitoProcessManager |
| // |
| |
| IncognitoProcessManager::IncognitoProcessManager( |
| BrowserContext* incognito_context, |
| BrowserContext* original_context, |
| ExtensionRegistry* extension_registry) |
| : ProcessManager(incognito_context, original_context, extension_registry) { |
| DCHECK(incognito_context->IsOffTheRecord()); |
| } |
| |
| bool IncognitoProcessManager::CreateBackgroundHost(const Extension* extension, |
| const GURL& url) { |
| if (IncognitoInfo::IsSplitMode(extension)) { |
| if (ExtensionsBrowserClient::Get()->IsExtensionIncognitoEnabled( |
| extension->id(), browser_context())) |
| 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 false; |
| } |
| |
| scoped_refptr<content::SiteInstance> |
| IncognitoProcessManager::GetSiteInstanceForURL(const GURL& url) { |
| const Extension* extension = |
| extension_registry_->enabled_extensions().GetExtensionOrAppByURL(url); |
| if (extension && !IncognitoInfo::IsSplitMode(extension)) { |
| BrowserContext* original_context = |
| ExtensionsBrowserClient::Get()->GetOriginalContext(browser_context()); |
| return ProcessManager::Get(original_context)->GetSiteInstanceForURL(url); |
| } |
| |
| return ProcessManager::GetSiteInstanceForURL(url); |
| } |
| |
| } // namespace extensions |