// 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(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(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(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(source).ptr(); ExtensionHost* host = content::Details(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(source).ptr(); UnloadedExtensionInfo* unloaded = content::Details(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