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
26namespace extensions {
27
28LazyBackgroundTaskQueue::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
39LazyBackgroundTaskQueue::~LazyBackgroundTaskQueue() {
40}
41
42bool 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
61void 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
100void 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
135void 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