blob: 55dcd62be9fcdb43269a458c1e4875ba891255bb [file] [log] [blame]
// 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_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::CreateSingleThreadTaskRunner({content::BrowserThread::IO})),
startup_background_hosts_created_(false),
last_background_close_sequence_id_(0) {
// 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