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
25namespace extensions {
26
27LazyBackgroundTaskQueue::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
40LazyBackgroundTaskQueue::~LazyBackgroundTaskQueue() {
41}
42
43bool 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
64void 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
103void 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
138void 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
174void 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