| // 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/lazy_background_task_queue.h" |
| |
| #include "base/callback.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "content/public/browser/browser_context.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 "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/process_manager.h" |
| #include "extensions/browser/process_map.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_messages.h" |
| #include "extensions/common/manifest_handlers/background_info.h" |
| #include "extensions/common/view_type.h" |
| |
| namespace extensions { |
| |
| LazyBackgroundTaskQueue::LazyBackgroundTaskQueue( |
| content::BrowserContext* browser_context) |
| : browser_context_(browser_context) { |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED, |
| content::Source<content::BrowserContext>(browser_context)); |
| } |
| |
| LazyBackgroundTaskQueue::~LazyBackgroundTaskQueue() { |
| } |
| |
| bool LazyBackgroundTaskQueue::ShouldEnqueueTask( |
| content::BrowserContext* browser_context, |
| const Extension* extension) { |
| DCHECK(extension); |
| if (BackgroundInfo::HasBackgroundPage(extension)) { |
| ProcessManager* pm = ExtensionSystem::Get( |
| browser_context)->process_manager(); |
| DCHECK(pm); |
| ExtensionHost* background_host = |
| pm->GetBackgroundHostForExtension(extension->id()); |
| if (!background_host || !background_host->did_stop_loading()) |
| return true; |
| if (pm->IsBackgroundHostClosing(extension->id())) |
| pm->CancelSuspend(extension); |
| } |
| |
| return false; |
| } |
| |
| void LazyBackgroundTaskQueue::AddPendingTask( |
| content::BrowserContext* browser_context, |
| const std::string& extension_id, |
| const PendingTask& task) { |
| if (ExtensionsBrowserClient::Get()->IsShuttingDown()) { |
| task.Run(NULL); |
| return; |
| } |
| PendingTasksList* tasks_list = NULL; |
| PendingTasksKey key(browser_context, extension_id); |
| PendingTasksMap::iterator it = pending_tasks_.find(key); |
| if (it == pending_tasks_.end()) { |
| tasks_list = new PendingTasksList(); |
| pending_tasks_[key] = linked_ptr<PendingTasksList>(tasks_list); |
| |
| const Extension* extension = |
| ExtensionRegistry::Get(browser_context)->enabled_extensions().GetByID( |
| extension_id); |
| if (extension && BackgroundInfo::HasLazyBackgroundPage(extension)) { |
| // If this is the first enqueued task, and we're not waiting for the |
| // background page to unload, ensure the background page is loaded. |
| ProcessManager* pm = ExtensionSystem::Get( |
| browser_context)->process_manager(); |
| pm->IncrementLazyKeepaliveCount(extension); |
| // Creating the background host may fail, e.g. if |profile| is incognito |
| // but the extension isn't enabled in incognito mode. |
| if (!pm->CreateBackgroundHost( |
| extension, BackgroundInfo::GetBackgroundURL(extension))) { |
| task.Run(NULL); |
| return; |
| } |
| } |
| } else { |
| tasks_list = it->second.get(); |
| } |
| |
| tasks_list->push_back(task); |
| } |
| |
| void LazyBackgroundTaskQueue::ProcessPendingTasks( |
| ExtensionHost* host, |
| content::BrowserContext* browser_context, |
| const Extension* extension) { |
| if (!ExtensionsBrowserClient::Get()->IsSameContext(browser_context, |
| browser_context_)) |
| return; |
| |
| PendingTasksKey key(browser_context, extension->id()); |
| PendingTasksMap::iterator map_it = pending_tasks_.find(key); |
| if (map_it == pending_tasks_.end()) { |
| if (BackgroundInfo::HasLazyBackgroundPage(extension)) |
| CHECK(!host); // lazy page should not load without any pending tasks |
| return; |
| } |
| |
| // Swap the pending tasks to a temporary, to avoid problems if the task |
| // list is modified during processing. |
| PendingTasksList tasks; |
| tasks.swap(*map_it->second); |
| for (PendingTasksList::const_iterator it = tasks.begin(); |
| it != tasks.end(); ++it) { |
| it->Run(host); |
| } |
| |
| pending_tasks_.erase(key); |
| |
| // Balance the keepalive in AddPendingTask. Note we don't do this on a |
| // failure to load, because the keepalive count is reset in that case. |
| if (host && BackgroundInfo::HasLazyBackgroundPage(extension)) { |
| ExtensionSystem::Get(browser_context)->process_manager()-> |
| DecrementLazyKeepaliveCount(extension); |
| } |
| } |
| |
| void LazyBackgroundTaskQueue::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| switch (type) { |
| case chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING: { |
| // If an on-demand background page finished loading, dispatch queued up |
| // events for it. |
| ExtensionHost* host = |
| content::Details<ExtensionHost>(details).ptr(); |
| if (host->extension_host_type() == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) { |
| CHECK(host->did_stop_loading()); |
| ProcessPendingTasks(host, host->browser_context(), host->extension()); |
| } |
| break; |
| } |
| case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: { |
| // Notify consumers about the load failure when the background host dies. |
| // This can happen if the extension crashes. This is not strictly |
| // necessary, since we also unload the extension in that case (which |
| // dispatches the tasks below), but is a good extra precaution. |
| content::BrowserContext* browser_context = |
| content::Source<content::BrowserContext>(source).ptr(); |
| ExtensionHost* host = |
| content::Details<ExtensionHost>(details).ptr(); |
| if (host->extension_host_type() == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) { |
| ProcessPendingTasks(NULL, browser_context, host->extension()); |
| } |
| break; |
| } |
| case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: { |
| // Notify consumers that the page failed to load. |
| content::BrowserContext* browser_context = |
| content::Source<content::BrowserContext>(source).ptr(); |
| UnloadedExtensionInfo* unloaded = |
| content::Details<UnloadedExtensionInfo>(details).ptr(); |
| ProcessPendingTasks(NULL, browser_context, unloaded->extension); |
| // If this extension is also running in an off-the-record context, |
| // notify that task queue as well. |
| ExtensionsBrowserClient* browser_client = ExtensionsBrowserClient::Get(); |
| if (browser_client->HasOffTheRecordContext(browser_context)) { |
| ProcessPendingTasks( |
| NULL, |
| browser_client->GetOffTheRecordContext(browser_context), |
| unloaded->extension); |
| } |
| break; |
| } |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| } // namespace extensions |