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