Home | History | Annotate | Download | only in browser
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "extensions/browser/lazy_background_task_queue.h"
      6 
      7 #include "base/callback.h"
      8 #include "chrome/browser/chrome_notification_types.h"
      9 #include "content/public/browser/browser_context.h"
     10 #include "content/public/browser/notification_service.h"
     11 #include "content/public/browser/render_process_host.h"
     12 #include "content/public/browser/render_view_host.h"
     13 #include "content/public/browser/site_instance.h"
     14 #include "content/public/browser/web_contents.h"
     15 #include "extensions/browser/extension_host.h"
     16 #include "extensions/browser/extension_registry.h"
     17 #include "extensions/browser/extension_system.h"
     18 #include "extensions/browser/extensions_browser_client.h"
     19 #include "extensions/browser/process_manager.h"
     20 #include "extensions/browser/process_map.h"
     21 #include "extensions/common/extension.h"
     22 #include "extensions/common/extension_messages.h"
     23 #include "extensions/common/manifest_handlers/background_info.h"
     24 #include "extensions/common/view_type.h"
     25 
     26 namespace extensions {
     27 
     28 LazyBackgroundTaskQueue::LazyBackgroundTaskQueue(
     29     content::BrowserContext* browser_context)
     30     : browser_context_(browser_context) {
     31   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
     32                  content::NotificationService::AllBrowserContextsAndSources());
     33   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED,
     34                  content::NotificationService::AllBrowserContextsAndSources());
     35   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
     36                  content::Source<content::BrowserContext>(browser_context));
     37 }
     38 
     39 LazyBackgroundTaskQueue::~LazyBackgroundTaskQueue() {
     40 }
     41 
     42 bool LazyBackgroundTaskQueue::ShouldEnqueueTask(
     43     content::BrowserContext* browser_context,
     44     const Extension* extension) {
     45   DCHECK(extension);
     46   if (BackgroundInfo::HasBackgroundPage(extension)) {
     47     ProcessManager* pm = ExtensionSystem::Get(
     48         browser_context)->process_manager();
     49     DCHECK(pm);
     50     ExtensionHost* background_host =
     51         pm->GetBackgroundHostForExtension(extension->id());
     52     if (!background_host || !background_host->did_stop_loading())
     53       return true;
     54     if (pm->IsBackgroundHostClosing(extension->id()))
     55       pm->CancelSuspend(extension);
     56   }
     57 
     58   return false;
     59 }
     60 
     61 void LazyBackgroundTaskQueue::AddPendingTask(
     62     content::BrowserContext* browser_context,
     63     const std::string& extension_id,
     64     const PendingTask& task) {
     65   if (ExtensionsBrowserClient::Get()->IsShuttingDown()) {
     66     task.Run(NULL);
     67     return;
     68   }
     69   PendingTasksList* tasks_list = NULL;
     70   PendingTasksKey key(browser_context, extension_id);
     71   PendingTasksMap::iterator it = pending_tasks_.find(key);
     72   if (it == pending_tasks_.end()) {
     73     tasks_list = new PendingTasksList();
     74     pending_tasks_[key] = linked_ptr<PendingTasksList>(tasks_list);
     75 
     76     const Extension* extension =
     77         ExtensionRegistry::Get(browser_context)->enabled_extensions().GetByID(
     78             extension_id);
     79     if (extension && BackgroundInfo::HasLazyBackgroundPage(extension)) {
     80       // If this is the first enqueued task, and we're not waiting for the
     81       // background page to unload, ensure the background page is loaded.
     82       ProcessManager* pm = ExtensionSystem::Get(
     83           browser_context)->process_manager();
     84       pm->IncrementLazyKeepaliveCount(extension);
     85       // Creating the background host may fail, e.g. if |profile| is incognito
     86       // but the extension isn't enabled in incognito mode.
     87       if (!pm->CreateBackgroundHost(
     88             extension, BackgroundInfo::GetBackgroundURL(extension))) {
     89         task.Run(NULL);
     90         return;
     91       }
     92     }
     93   } else {
     94     tasks_list = it->second.get();
     95   }
     96 
     97   tasks_list->push_back(task);
     98 }
     99 
    100 void LazyBackgroundTaskQueue::ProcessPendingTasks(
    101     ExtensionHost* host,
    102     content::BrowserContext* browser_context,
    103     const Extension* extension) {
    104   if (!ExtensionsBrowserClient::Get()->IsSameContext(browser_context,
    105                                                      browser_context_))
    106     return;
    107 
    108   PendingTasksKey key(browser_context, extension->id());
    109   PendingTasksMap::iterator map_it = pending_tasks_.find(key);
    110   if (map_it == pending_tasks_.end()) {
    111     if (BackgroundInfo::HasLazyBackgroundPage(extension))
    112       CHECK(!host);  // lazy page should not load without any pending tasks
    113     return;
    114   }
    115 
    116   // Swap the pending tasks to a temporary, to avoid problems if the task
    117   // list is modified during processing.
    118   PendingTasksList tasks;
    119   tasks.swap(*map_it->second);
    120   for (PendingTasksList::const_iterator it = tasks.begin();
    121        it != tasks.end(); ++it) {
    122     it->Run(host);
    123   }
    124 
    125   pending_tasks_.erase(key);
    126 
    127   // Balance the keepalive in AddPendingTask. Note we don't do this on a
    128   // failure to load, because the keepalive count is reset in that case.
    129   if (host && BackgroundInfo::HasLazyBackgroundPage(extension)) {
    130     ExtensionSystem::Get(browser_context)->process_manager()->
    131         DecrementLazyKeepaliveCount(extension);
    132   }
    133 }
    134 
    135 void LazyBackgroundTaskQueue::Observe(
    136     int type,
    137     const content::NotificationSource& source,
    138     const content::NotificationDetails& details) {
    139   switch (type) {
    140     case chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING: {
    141       // If an on-demand background page finished loading, dispatch queued up
    142       // events for it.
    143       ExtensionHost* host =
    144           content::Details<ExtensionHost>(details).ptr();
    145       if (host->extension_host_type() == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) {
    146         CHECK(host->did_stop_loading());
    147         ProcessPendingTasks(host, host->browser_context(), host->extension());
    148       }
    149       break;
    150     }
    151     case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: {
    152       // Notify consumers about the load failure when the background host dies.
    153       // This can happen if the extension crashes. This is not strictly
    154       // necessary, since we also unload the extension in that case (which
    155       // dispatches the tasks below), but is a good extra precaution.
    156       content::BrowserContext* browser_context =
    157           content::Source<content::BrowserContext>(source).ptr();
    158       ExtensionHost* host =
    159            content::Details<ExtensionHost>(details).ptr();
    160       if (host->extension_host_type() == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) {
    161         ProcessPendingTasks(NULL, browser_context, host->extension());
    162       }
    163       break;
    164     }
    165     case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: {
    166       // Notify consumers that the page failed to load.
    167       content::BrowserContext* browser_context =
    168           content::Source<content::BrowserContext>(source).ptr();
    169       UnloadedExtensionInfo* unloaded =
    170           content::Details<UnloadedExtensionInfo>(details).ptr();
    171       ProcessPendingTasks(NULL, browser_context, unloaded->extension);
    172       // If this extension is also running in an off-the-record context,
    173       // notify that task queue as well.
    174       ExtensionsBrowserClient* browser_client = ExtensionsBrowserClient::Get();
    175       if (browser_client->HasOffTheRecordContext(browser_context)) {
    176         ProcessPendingTasks(
    177             NULL,
    178             browser_client->GetOffTheRecordContext(browser_context),
    179             unloaded->extension);
    180       }
    181       break;
    182     }
    183     default:
    184       NOTREACHED();
    185       break;
    186   }
    187 }
    188 
    189 }  // namespace extensions
    190