summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-16 18:55:23 +0000
committermpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-16 18:55:23 +0000
commitb6536df0203bcb40613d40a6ae28a82c6e8a2e95 (patch)
treed12ed781ce6a46063613a5f6123e8707c9cb0690
parent950ee84c28a20172764da99ed03a4f0d4c9cc371 (diff)
downloadchromium_src-b6536df0203bcb40613d40a6ae28a82c6e8a2e95.zip
chromium_src-b6536df0203bcb40613d40a6ae28a82c6e8a2e95.tar.gz
chromium_src-b6536df0203bcb40613d40a6ae28a82c6e8a2e95.tar.bz2
Lazy background pages now load in response to message passing.
I refactored the pending event stuff so that it handles generic tasks. The first enqueued task for an extension will start its lazy background page, and tasks are run once the page finishes loading. Events and messages now share this mechanism so that either one can activate the page. BUG=81752 TEST=no Review URL: https://chromiumcodereview.appspot.com/9704031 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@127216 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/extensions/extension_event_router.cc86
-rw-r--r--chrome/browser/extensions/extension_event_router.h18
-rw-r--r--chrome/browser/extensions/extension_message_service.cc212
-rw-r--r--chrome/browser/extensions/extension_message_service.h50
-rw-r--r--chrome/browser/extensions/lazy_background_page_apitest.cc39
-rw-r--r--chrome/browser/extensions/lazy_background_task_queue.cc120
-rw-r--r--chrome/browser/extensions/lazy_background_task_queue.h64
-rw-r--r--chrome/browser/profiles/off_the_record_profile_impl.cc4
-rw-r--r--chrome/browser/profiles/off_the_record_profile_impl.h1
-rw-r--r--chrome/browser/profiles/profile.h5
-rw-r--r--chrome/browser/profiles/profile_impl.cc9
-rw-r--r--chrome/browser/profiles/profile_impl.h3
-rw-r--r--chrome/chrome_browser_extensions.gypi2
-rw-r--r--chrome/test/base/testing_profile.cc4
-rw-r--r--chrome/test/base/testing_profile.h1
-rw-r--r--chrome/test/data/extensions/api_test/lazy_background_page/messaging/background.js9
-rw-r--r--chrome/test/data/extensions/api_test/lazy_background_page/messaging/content.js6
-rw-r--r--chrome/test/data/extensions/api_test/lazy_background_page/messaging/manifest.json17
18 files changed, 510 insertions, 140 deletions
diff --git a/chrome/browser/extensions/extension_event_router.cc b/chrome/browser/extensions/extension_event_router.cc
index 701d3d0..13d88e9 100644
--- a/chrome/browser/extensions/extension_event_router.cc
+++ b/chrome/browser/extensions/extension_event_router.cc
@@ -7,6 +7,7 @@
#include "base/bind.h"
#include "base/command_line.h"
#include "base/values.h"
+#include "chrome/browser/extensions/api/webrequest/webrequest_api.h"
#include "chrome/browser/extensions/extension_devtools_manager.h"
#include "chrome/browser/extensions/extension_host.h"
#include "chrome/browser/extensions/extension_process_manager.h"
@@ -14,7 +15,7 @@
#include "chrome/browser/extensions/extension_processes_api_constants.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_tabs_module.h"
-#include "chrome/browser/extensions/api/webrequest/webrequest_api.h"
+#include "chrome/browser/extensions/lazy_background_task_queue.h"
#include "chrome/browser/extensions/process_map.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_notification_types.h"
@@ -105,8 +106,6 @@ ExtensionEventRouter::ExtensionEventRouter(Profile* profile)
content::NotificationService::AllSources());
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
content::NotificationService::AllSources());
- registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
- content::NotificationService::AllSources());
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
content::Source<Profile>(profile_));
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
@@ -393,15 +392,11 @@ void ExtensionEventRouter::MaybeLoadLazyBackgroundPage(
if (!CanDispatchEventToProfile(profile, extension, event, &event_args))
return;
- ExtensionProcessManager* pm = profile->GetExtensionProcessManager();
-
if (!CanDispatchEventNow(profile, extension)) {
- AppendEvent(profile, extension->id(), event);
- if (!pm->GetBackgroundHostForExtension(extension->id())) {
- // Balanced in DispatchPendingEvents, after the page has loaded.
- pm->IncrementLazyKeepaliveCount(extension);
- pm->CreateBackgroundHost(extension, extension->GetBackgroundURL());
- }
+ profile->GetLazyBackgroundTaskQueue()->AddPendingTask(
+ profile, extension->id(),
+ base::Bind(&ExtensionEventRouter::DispatchPendingEvent,
+ base::Unretained(this), event));
}
}
@@ -438,48 +433,12 @@ void ExtensionEventRouter::OnExtensionEventAck(
}
}
-void ExtensionEventRouter::AppendEvent(
- Profile* profile,
- const std::string& extension_id,
- const linked_ptr<ExtensionEvent>& event) {
- PendingEventsList* events_list = NULL;
- PendingEventsKey key(extension_id, profile);
- PendingEventsPerExtMap::iterator it = pending_events_.find(key);
- if (it == pending_events_.end()) {
- events_list = new PendingEventsList();
- pending_events_[key] = linked_ptr<PendingEventsList>(events_list);
- } else {
- events_list = it->second.get();
- }
-
- events_list->push_back(event);
-}
-
-void ExtensionEventRouter::DispatchPendingEvents(
- content::RenderProcessHost* process, const Extension* extension) {
- Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext());
- DCHECK(profile);
-
- PendingEventsPerExtMap::iterator map_it =
- pending_events_.find(PendingEventsKey(extension->id(), profile));
- if (map_it == pending_events_.end()) {
- NOTREACHED(); // lazy page should not load without any pending events
- return;
- }
-
- ListenerProcess listener(process, extension->id());
- PendingEventsList* events_list = map_it->second.get();
- for (PendingEventsList::const_iterator it = events_list->begin();
- it != events_list->end(); ++it) {
- if (listeners_[(*it)->event_name].count(listener) > 0u)
- DispatchEventToListener(listener, *it);
- }
-
- events_list->clear();
- pending_events_.erase(map_it);
-
- // Balance the keepalive addref in LoadLazyBackgroundPagesForEvent.
- profile->GetExtensionProcessManager()->DecrementLazyKeepaliveCount(extension);
+void ExtensionEventRouter::DispatchPendingEvent(
+ const linked_ptr<ExtensionEvent>& event, ExtensionHost* host) {
+ ListenerProcess listener(host->render_process_host(),
+ host->extension()->id());
+ if (listeners_[event->event_name].count(listener) > 0u)
+ DispatchEventToListener(listener, event);
}
void ExtensionEventRouter::Observe(
@@ -508,19 +467,6 @@ void ExtensionEventRouter::Observe(
}
break;
}
- case chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING: {
- // If an on-demand background page finished loading, dispatch queued up
- // events for it.
- ExtensionHost* eh = content::Details<ExtensionHost>(details).ptr();
- if (profile_->IsSameProfile(eh->profile()) &&
- eh->extension_host_type() ==
- chrome::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE &&
- !eh->extension()->background_page_persists()) {
- CHECK(eh->did_stop_loading());
- DispatchPendingEvents(eh->render_process_host(), eh->extension());
- }
- break;
- }
case chrome::NOTIFICATION_EXTENSION_LOADED: {
// Add all registered lazy listeners to our cache.
const Extension* extension =
@@ -544,14 +490,6 @@ void ExtensionEventRouter::Observe(
it != lazy_listeners_.end(); ++it) {
it->second.erase(lazy_listener);
}
-
- // Clear pending events as well.
- pending_events_.erase(PendingEventsKey(
- unloaded->extension->id(), profile_));
- if (profile_->HasOffTheRecordProfile())
- pending_events_.erase(PendingEventsKey(
- unloaded->extension->id(), profile_->GetOffTheRecordProfile()));
-
break;
}
case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
diff --git a/chrome/browser/extensions/extension_event_router.h b/chrome/browser/extensions/extension_event_router.h
index deaae42..74f2c93 100644
--- a/chrome/browser/extensions/extension_event_router.h
+++ b/chrome/browser/extensions/extension_event_router.h
@@ -19,6 +19,7 @@
class GURL;
class Extension;
+class ExtensionHost;
class ExtensionDevToolsManager;
class Profile;
@@ -189,13 +190,8 @@ class ExtensionEventRouter : public content::NotificationObserver {
// ACK from the renderer.
void IncrementInFlightEvents(Profile* profile, const Extension* extension);
- // Store the event so that it can be dispatched (in order received)
- // when the background page is done loading.
- void AppendEvent(Profile* profile,
- const std::string& extension_id,
- const linked_ptr<ExtensionEvent>& event);
- void DispatchPendingEvents(content::RenderProcessHost* process,
- const Extension* extension);
+ void DispatchPendingEvent(const linked_ptr<ExtensionEvent>& event,
+ ExtensionHost* host);
Profile* profile_;
@@ -211,14 +207,6 @@ class ExtensionEventRouter : public content::NotificationObserver {
// stored on disk in the extension prefs.
ListenerMap lazy_listeners_;
- // A map between an extension_id,Profile pair and the queue of events pending
- // the load of its background page.
- typedef std::pair<std::string, Profile*> PendingEventsKey;
- typedef std::vector<linked_ptr<ExtensionEvent> > PendingEventsList;
- typedef std::map<PendingEventsKey,
- linked_ptr<PendingEventsList> > PendingEventsPerExtMap;
- PendingEventsPerExtMap pending_events_;
-
DISALLOW_COPY_AND_ASSIGN(ExtensionEventRouter);
};
diff --git a/chrome/browser/extensions/extension_message_service.cc b/chrome/browser/extensions/extension_message_service.cc
index f0fef50..c2f81df 100644
--- a/chrome/browser/extensions/extension_message_service.cc
+++ b/chrome/browser/extensions/extension_message_service.cc
@@ -5,19 +5,25 @@
#include "chrome/browser/extensions/extension_message_service.h"
#include "base/atomic_sequence_num.h"
+#include "base/bind.h"
+#include "base/callback.h"
#include "base/json/json_writer.h"
#include "base/stl_util.h"
#include "base/values.h"
+#include "chrome/browser/extensions/extension_host.h"
#include "chrome/browser/extensions/extension_process_manager.h"
+#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_tab_util.h"
+#include "chrome/browser/extensions/lazy_background_task_queue.h"
#include "chrome/browser/extensions/process_map.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_contents/tab_util.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_view_type.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_messages.h"
#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/site_instance.h"
@@ -49,6 +55,33 @@ struct ExtensionMessageService::MessagePort {
struct ExtensionMessageService::MessageChannel {
ExtensionMessageService::MessagePort opener;
ExtensionMessageService::MessagePort receiver;
+ std::string source_extension_id;
+ std::string target_extension_id;
+};
+
+struct ExtensionMessageService::OpenChannelParams {
+ content::RenderProcessHost* source;
+ std::string tab_json;
+ MessagePort receiver;
+ int receiver_port_id;
+ std::string source_extension_id;
+ std::string target_extension_id;
+ std::string channel_name;
+
+ OpenChannelParams(content::RenderProcessHost* source,
+ const std::string& tab_json,
+ const MessagePort& receiver,
+ int receiver_port_id,
+ const std::string& source_extension_id,
+ const std::string& target_extension_id,
+ const std::string& channel_name)
+ : source(source),
+ tab_json(tab_json),
+ receiver(receiver),
+ receiver_port_id(receiver_port_id),
+ source_extension_id(source_extension_id),
+ target_extension_id(target_extension_id),
+ channel_name(channel_name) {}
};
const char ExtensionMessageService::kDispatchOnConnect[] =
@@ -99,8 +132,8 @@ static void DispatchOnMessage(const ExtensionMessageService::MessagePort& port,
port.routing_id, target_port_id, message));
}
-static content::RenderProcessHost* GetExtensionProcess(Profile* profile,
- const std::string& extension_id) {
+static content::RenderProcessHost* GetExtensionProcess(
+ Profile* profile, const std::string& extension_id) {
SiteInstance* site_instance =
profile->GetExtensionProcessManager()->GetSiteInstanceForURL(
Extension::GetBaseURLFromExtensionId(extension_id));
@@ -111,6 +144,26 @@ static content::RenderProcessHost* GetExtensionProcess(Profile* profile,
return site_instance->GetProcess();
}
+static void IncrementLazyKeepaliveCount(content::RenderProcessHost* process,
+ const std::string& extension_id) {
+ Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext());
+ const Extension* extension = profile->GetExtensionService()->extensions()->
+ GetByID(extension_id);
+ if (extension)
+ profile->GetExtensionProcessManager()->IncrementLazyKeepaliveCount(
+ extension);
+}
+
+static void DecrementLazyKeepaliveCount(content::RenderProcessHost* process,
+ const std::string& extension_id) {
+ Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext());
+ const Extension* extension = profile->GetExtensionService()->extensions()->
+ GetByID(extension_id);
+ if (extension)
+ profile->GetExtensionProcessManager()->DecrementLazyKeepaliveCount(
+ extension);
+}
+
} // namespace
// static
@@ -132,7 +185,8 @@ void ExtensionMessageService::AllocatePortIdPair(int* port1, int* port2) {
*port2 = port2_id;
}
-ExtensionMessageService::ExtensionMessageService(Profile* profile) {
+ExtensionMessageService::ExtensionMessageService(LazyBackgroundTaskQueue* queue)
+ : lazy_background_task_queue_(queue) {
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
content::NotificationService::AllBrowserContextsAndSources());
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
@@ -172,8 +226,16 @@ void ExtensionMessageService::OpenChannelToExtension(
base::JSONWriter::Write(tab_value.get(), &tab_json);
}
- OpenChannelImpl(source, tab_json, receiver, receiver_port_id,
- source_extension_id, target_extension_id, channel_name);
+ OpenChannelParams params(source, tab_json, receiver, receiver_port_id,
+ source_extension_id, target_extension_id,
+ channel_name);
+
+ // If the target process doesn't exist, it might be a lazy background page.
+ // In that case, queue up the task and load the page.
+ if (!receiver.process && MaybeAddPendingOpenChannelTask(profile, params))
+ return;
+
+ OpenChannelImpl(params);
}
void ExtensionMessageService::OpenChannelToTab(
@@ -214,76 +276,110 @@ void ExtensionMessageService::OpenChannelToTab(
base::JSONWriter::Write(tab_value.get(), &tab_json);
}
- OpenChannelImpl(source, tab_json, receiver, receiver_port_id,
- extension_id, extension_id, channel_name);
+ OpenChannelParams params(source, tab_json, receiver, receiver_port_id,
+ extension_id, extension_id, channel_name);
+ OpenChannelImpl(params);
}
-bool ExtensionMessageService::OpenChannelImpl(
- content::RenderProcessHost* source,
- const std::string& tab_json,
- const MessagePort& receiver, int receiver_port_id,
- const std::string& source_extension_id,
- const std::string& target_extension_id,
- const std::string& channel_name) {
- if (!source)
+bool ExtensionMessageService::OpenChannelImpl(const OpenChannelParams& params) {
+ if (!params.source)
return false; // Closed while in flight.
- if (!receiver.process) {
+ if (!params.receiver.process) {
// Treat it as a disconnect.
- DispatchOnDisconnect(MessagePort(source, MSG_ROUTING_CONTROL),
- GET_OPPOSITE_PORT_ID(receiver_port_id), true);
+ DispatchOnDisconnect(MessagePort(params.source, MSG_ROUTING_CONTROL),
+ GET_OPPOSITE_PORT_ID(params.receiver_port_id), true);
return false;
}
// Add extra paranoid CHECKs, since we have crash reports of this being NULL.
// http://code.google.com/p/chromium/issues/detail?id=19067
- CHECK(receiver.process);
+ CHECK(params.receiver.process);
MessageChannel* channel(new MessageChannel);
- channel->opener = MessagePort(source, MSG_ROUTING_CONTROL);
- channel->receiver = receiver;
+ channel->opener = MessagePort(params.source, MSG_ROUTING_CONTROL);
+ channel->receiver = params.receiver;
+ channel->source_extension_id = params.source_extension_id;
+ channel->target_extension_id = params.target_extension_id;
- CHECK(receiver.process);
+ CHECK(params.receiver.process);
- CHECK(channels_.find(GET_CHANNEL_ID(receiver_port_id)) == channels_.end());
- channels_[GET_CHANNEL_ID(receiver_port_id)] = channel;
+ int channel_id = GET_CHANNEL_ID(params.receiver_port_id);
+ CHECK(channels_.find(channel_id) == channels_.end());
+ channels_[channel_id] = channel;
+ pending_channels_.erase(channel_id);
- CHECK(receiver.process);
+ CHECK(params.receiver.process);
// Send the connect event to the receiver. Give it the opener's port ID (the
// opener has the opposite port ID).
- DispatchOnConnect(receiver, receiver_port_id, channel_name, tab_json,
- source_extension_id, target_extension_id);
-
+ DispatchOnConnect(params.receiver, params.receiver_port_id,
+ params.channel_name, params.tab_json,
+ params.source_extension_id, params.target_extension_id);
+
+ // Keep both ends of the channel alive until the channel is closed.
+ IncrementLazyKeepaliveCount(channel->opener.process,
+ channel->source_extension_id);
+ IncrementLazyKeepaliveCount(channel->receiver.process,
+ channel->target_extension_id);
return true;
}
void ExtensionMessageService::CloseChannel(int port_id) {
// Note: The channel might be gone already, if the other side closed first.
- MessageChannelMap::iterator it = channels_.find(GET_CHANNEL_ID(port_id));
- if (it != channels_.end())
- CloseChannelImpl(it, port_id, true);
+ int channel_id = GET_CHANNEL_ID(port_id);
+ MessageChannelMap::iterator it = channels_.find(channel_id);
+ if (it == channels_.end()) {
+ PendingChannelMap::iterator pending = pending_channels_.find(channel_id);
+ if (pending != pending_channels_.end()) {
+ lazy_background_task_queue_->AddPendingTask(
+ pending->second.first, pending->second.second,
+ base::Bind(&ExtensionMessageService::PendingCloseChannel,
+ base::Unretained(this), port_id));
+ }
+ return;
+ }
+ CloseChannelImpl(it, port_id, true);
}
void ExtensionMessageService::CloseChannelImpl(
MessageChannelMap::iterator channel_iter, int closing_port_id,
bool notify_other_port) {
- // Notify the other side.
- const MessagePort& port = IS_OPENER_PORT_ID(closing_port_id) ?
- channel_iter->second->receiver : channel_iter->second->opener;
+ MessageChannel* channel = channel_iter->second;
- if (notify_other_port)
+ // Notify the other side.
+ if (notify_other_port) {
+ const MessagePort& port = IS_OPENER_PORT_ID(closing_port_id) ?
+ channel->receiver : channel->opener;
DispatchOnDisconnect(port, GET_OPPOSITE_PORT_ID(closing_port_id), false);
+ }
+
+ // Balance the addrefs in OpenChannelImpl.
+ DecrementLazyKeepaliveCount(channel->opener.process,
+ channel->source_extension_id);
+ DecrementLazyKeepaliveCount(channel->receiver.process,
+ channel->target_extension_id);
+
delete channel_iter->second;
channels_.erase(channel_iter);
}
void ExtensionMessageService::PostMessageFromRenderer(
int source_port_id, const std::string& message) {
- MessageChannelMap::iterator iter =
- channels_.find(GET_CHANNEL_ID(source_port_id));
- if (iter == channels_.end())
+ int channel_id = GET_CHANNEL_ID(source_port_id);
+ MessageChannelMap::iterator iter = channels_.find(channel_id);
+ if (iter == channels_.end()) {
+ // If this channel is pending, queue up the PostMessage to run once
+ // the channel opens.
+ PendingChannelMap::iterator pending = pending_channels_.find(channel_id);
+ if (pending != pending_channels_.end()) {
+ lazy_background_task_queue_->AddPendingTask(
+ pending->second.first, pending->second.second,
+ base::Bind(&ExtensionMessageService::PendingPostMessage,
+ base::Unretained(this), source_port_id, message));
+ }
return;
+ }
// Figure out which port the ID corresponds to.
int dest_port_id = GET_OPPOSITE_PORT_ID(source_port_id);
@@ -332,3 +428,41 @@ void ExtensionMessageService::OnProcessClosed(
}
}
}
+
+bool ExtensionMessageService::MaybeAddPendingOpenChannelTask(
+ Profile* profile,
+ const OpenChannelParams& params) {
+ ExtensionService* service = profile->GetExtensionService();
+ const std::string& extension_id = params.target_extension_id;
+ const Extension* extension = service->extensions()->GetByID(extension_id);
+ if (!extension->background_page_persists()) {
+ // If the extension uses spanning incognito mode, make sure we're always
+ // using the original profile since that is what the extension process
+ // will use.
+ if (!extension->incognito_split_mode())
+ profile = profile->GetOriginalProfile();
+ lazy_background_task_queue_->AddPendingTask(profile, extension_id,
+ base::Bind(&ExtensionMessageService::PendingOpenChannel,
+ base::Unretained(this), params, params.source->GetID()));
+ pending_channels_[GET_CHANNEL_ID(params.receiver_port_id)] =
+ PendingChannel(profile, extension_id);
+ return true;
+ }
+
+ return false;
+}
+
+void ExtensionMessageService::PendingOpenChannel(
+ const OpenChannelParams& params_in,
+ int source_process_id,
+ ExtensionHost* host) {
+ // Re-lookup the source process since it may no longer be valid.
+ OpenChannelParams params = params_in;
+ params.source = content::RenderProcessHost::FromID(source_process_id);
+ if (!params.source)
+ return;
+
+ params.receiver = MessagePort(host->render_process_host(),
+ MSG_ROUTING_CONTROL);
+ OpenChannelImpl(params);
+}
diff --git a/chrome/browser/extensions/extension_message_service.h b/chrome/browser/extensions/extension_message_service.h
index 1ef1012..9dad907 100644
--- a/chrome/browser/extensions/extension_message_service.h
+++ b/chrome/browser/extensions/extension_message_service.h
@@ -11,9 +11,12 @@
#include <string>
#include "base/compiler_specific.h"
+#include "base/memory/linked_ptr.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
+class ExtensionHost;
+class LazyBackgroundTaskQueue;
class Profile;
namespace content {
@@ -58,7 +61,7 @@ class ExtensionMessageService : public content::NotificationObserver {
// NOTE: this can be called from any thread.
static void AllocatePortIdPair(int* port1, int* port2);
- explicit ExtensionMessageService(Profile* profile);
+ explicit ExtensionMessageService(LazyBackgroundTaskQueue* queue);
virtual ~ExtensionMessageService();
// Given an extension's ID, opens a channel between the given renderer "port"
@@ -87,20 +90,22 @@ class ExtensionMessageService : public content::NotificationObserver {
private:
friend class MockExtensionMessageService;
+ struct OpenChannelParams;
// A map of channel ID to its channel object.
typedef std::map<int, MessageChannel*> MessageChannelMap;
- // Common among Open(Special)Channel* variants.
- bool OpenChannelImpl(
- content::RenderProcessHost* source,
- const std::string& tab_json,
- const MessagePort& receiver, int receiver_port_id,
- const std::string& source_extension_id,
- const std::string& target_extension_id,
- const std::string& channel_name);
+ // A map of channel ID to information about the extension that is waiting
+ // for that channel to open. Used for lazy background pages.
+ typedef std::string ExtensionID;
+ typedef std::pair<Profile*, ExtensionID> PendingChannel;
+ typedef std::map<int, PendingChannel> PendingChannelMap;
+
+ // Common among OpenChannel* variants.
+ bool OpenChannelImpl(const OpenChannelParams& params);
- void CloseChannelImpl(MessageChannelMap::iterator channel_iter, int port_id,
+ void CloseChannelImpl(MessageChannelMap::iterator channel_iter,
+ int port_id,
bool notify_other_port);
// content::NotificationObserver interface.
@@ -111,9 +116,32 @@ class ExtensionMessageService : public content::NotificationObserver {
// A process that might be in our list of channels has closed.
void OnProcessClosed(content::RenderProcessHost* process);
- content::NotificationRegistrar registrar_;
+ // Potentially registers a pending task with the LazyBackgroundTaskQueue
+ // to open a channel. Returns true if a task was queued.
+ bool MaybeAddPendingOpenChannelTask(Profile* profile,
+ const OpenChannelParams& params);
+
+ // Callbacks for LazyBackgroundTaskQueue tasks. The queue passes in an
+ // ExtensionHost to its task callbacks, though some of our callbacks don't
+ // use that argument.
+ void PendingOpenChannel(const OpenChannelParams& params,
+ int source_process_id,
+ ExtensionHost* host);
+ void PendingCloseChannel(int port_id, ExtensionHost*) {
+ CloseChannel(port_id);
+ }
+ void PendingPostMessage(int port_id,
+ const std::string& message,
+ ExtensionHost*) {
+ PostMessageFromRenderer(port_id, message);
+ }
+ content::NotificationRegistrar registrar_;
MessageChannelMap channels_;
+ PendingChannelMap pending_channels_;
+
+ // Weak pointer. Guaranteed to outlive this class.
+ LazyBackgroundTaskQueue* lazy_background_task_queue_;
DISALLOW_COPY_AND_ASSIGN(ExtensionMessageService);
};
diff --git a/chrome/browser/extensions/lazy_background_page_apitest.cc b/chrome/browser/extensions/lazy_background_page_apitest.cc
index 45fef79..5c93462 100644
--- a/chrome/browser/extensions/lazy_background_page_apitest.cc
+++ b/chrome/browser/extensions/lazy_background_page_apitest.cc
@@ -47,6 +47,13 @@ class LazyBackgroundObserver {
page_closed_.Wait();
}
+ void WaitUntilLoaded() {
+ page_created_.Wait();
+ }
+ void WaitUntilClosed() {
+ page_closed_.Wait();
+ }
+
private:
ui_test_utils::WindowedNotificationObserver page_created_;
ui_test_utils::WindowedNotificationObserver page_closed_;
@@ -327,6 +334,38 @@ IN_PROC_BROWSER_TEST_F(LazyBackgroundPageApiTest, IncognitoSplitMode) {
}
}
+// Tests that messages from the content script activate the lazy background
+// page, and keep it alive until all channels are closed.
+IN_PROC_BROWSER_TEST_F(LazyBackgroundPageApiTest, Messaging) {
+ ASSERT_TRUE(StartTestServer());
+ ASSERT_TRUE(LoadExtensionAndWait("messaging"));
+
+ // Lazy Background Page doesn't exist yet.
+ ExtensionProcessManager* pm =
+ browser()->profile()->GetExtensionProcessManager();
+ EXPECT_FALSE(pm->GetBackgroundHostForExtension(last_loaded_extension_id_));
+ EXPECT_EQ(1, browser()->tab_count());
+
+ // Navigate to a page that opens a message channel to the background page.
+ ResultCatcher catcher;
+ LazyBackgroundObserver lazybg;
+ ui_test_utils::NavigateToURL(
+ browser(), test_server()->GetURL("files/extensions/test_file.html"));
+ lazybg.WaitUntilLoaded();
+
+ // Background page got the content script's message and is still loaded
+ // until we close the channel.
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+ EXPECT_TRUE(pm->GetBackgroundHostForExtension(last_loaded_extension_id_));
+
+ // Navigate away, closing the message channel and therefore the background
+ // page.
+ ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
+ lazybg.WaitUntilClosed();
+
+ EXPECT_FALSE(pm->GetBackgroundHostForExtension(last_loaded_extension_id_));
+}
+
// TODO: background page with timer.
// TODO: background page that interacts with popup.
// TODO: background page with menu.
diff --git a/chrome/browser/extensions/lazy_background_task_queue.cc b/chrome/browser/extensions/lazy_background_task_queue.cc
new file mode 100644
index 0000000..a4a5a2c
--- /dev/null
+++ b/chrome/browser/extensions/lazy_background_task_queue.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 2012 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 "chrome/browser/extensions/lazy_background_task_queue.h"
+
+#include "base/callback.h"
+#include "chrome/browser/extensions/extension_host.h"
+#include "chrome/browser/extensions/extension_process_manager.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_tab_util.h"
+#include "chrome/browser/extensions/process_map.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/tab_contents/tab_util.h"
+#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_view_type.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_messages.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"
+
+LazyBackgroundTaskQueue::LazyBackgroundTaskQueue(Profile* profile)
+ : profile_(profile) {
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
+ content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
+ content::Source<Profile>(profile));
+}
+
+LazyBackgroundTaskQueue::~LazyBackgroundTaskQueue() {
+}
+
+void LazyBackgroundTaskQueue::AddPendingTask(
+ Profile* profile,
+ const std::string& extension_id,
+ const PendingTask& task) {
+ PendingTasksList* tasks_list = NULL;
+ PendingTasksKey key(profile, extension_id);
+ PendingTasksMap::iterator it = pending_tasks_.find(key);
+ if (it == pending_tasks_.end()) {
+ tasks_list = new PendingTasksList();
+ pending_tasks_[key] = linked_ptr<PendingTasksList>(tasks_list);
+
+ // If this is the first enqueued task, ensure the background page
+ // is loaded.
+ const Extension* extension = profile->GetExtensionService()->
+ extensions()->GetByID(extension_id);
+ DCHECK(!extension->background_page_persists());
+ ExtensionProcessManager* pm = profile->GetExtensionProcessManager();
+ pm->IncrementLazyKeepaliveCount(extension);
+ pm->CreateBackgroundHost(extension, extension->GetBackgroundURL());
+ } else {
+ tasks_list = it->second.get();
+ }
+
+ tasks_list->push_back(task);
+}
+
+void LazyBackgroundTaskQueue::ProcessPendingTasks(
+ ExtensionHost* host) {
+ PendingTasksKey key(host->profile(), host->extension()->id());
+ PendingTasksMap::iterator map_it = pending_tasks_.find(key);
+ if (map_it == pending_tasks_.end()) {
+ NOTREACHED(); // lazy page should not load without any pending tasks
+ return;
+ }
+
+ PendingTasksList* tasks = map_it->second.get();
+ for (PendingTasksList::const_iterator it = tasks->begin();
+ it != tasks->end(); ++it) {
+ it->Run(host);
+ }
+
+ tasks->clear();
+ pending_tasks_.erase(map_it);
+
+ // Balance the keepalive in AddPendingTask.
+ host->profile()->GetExtensionProcessManager()->
+ DecrementLazyKeepaliveCount(host->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<ExtensionHost>(details).ptr();
+ if (host->profile()->IsSameProfile(profile_) &&
+ host->extension_host_type() ==
+ chrome::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE &&
+ !host->extension()->background_page_persists()) {
+ CHECK(host->did_stop_loading());
+ ProcessPendingTasks(host);
+ }
+ break;
+ }
+ case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
+ // Clear pending tasks for this extension.
+ Profile* profile = content::Source<Profile>(source).ptr();
+ UnloadedExtensionInfo* unloaded =
+ content::Details<UnloadedExtensionInfo>(details).ptr();
+ pending_tasks_.erase(PendingTasksKey(
+ profile, unloaded->extension->id()));
+ if (profile->HasOffTheRecordProfile())
+ pending_tasks_.erase(PendingTasksKey(
+ profile->GetOffTheRecordProfile(), unloaded->extension->id()));
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+}
diff --git a/chrome/browser/extensions/lazy_background_task_queue.h b/chrome/browser/extensions/lazy_background_task_queue.h
new file mode 100644
index 0000000..63936b6
--- /dev/null
+++ b/chrome/browser/extensions/lazy_background_task_queue.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_LAZY_BACKGROUND_TASK_QUEUE_H_
+#define CHROME_BROWSER_EXTENSIONS_LAZY_BACKGROUND_TASK_QUEUE_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/callback_forward.h"
+#include "base/memory/linked_ptr.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+class ExtensionHost;
+class Profile;
+
+// This class maintains a queue of tasks that should execute when an
+// extension's lazy background page is loaded. It is also in charge of loading
+// the page when the first task is queued.
+//
+// It is the consumer's responsibility to use this class when appropriate, i.e.
+// only with extensions that have not-yet-loaded lazy background pages.
+class LazyBackgroundTaskQueue : public content::NotificationObserver {
+ public:
+ typedef base::Callback<void(ExtensionHost*)> PendingTask;
+
+ explicit LazyBackgroundTaskQueue(Profile* profile);
+ virtual ~LazyBackgroundTaskQueue();
+
+ // Adds a task to the queue for a given extension. If this is the first
+ // task added for the extension, its lazy background page will be loaded.
+ void AddPendingTask(
+ Profile* profile,
+ const std::string& extension_id,
+ const PendingTask& task);
+
+ private:
+ // A map between an extension_id,Profile pair and the queue of tasks pending
+ // the load of its background page.
+ typedef std::string ExtensionID;
+ typedef std::pair<Profile*, ExtensionID> PendingTasksKey;
+ typedef std::vector<PendingTask> PendingTasksList;
+ typedef std::map<PendingTasksKey,
+ linked_ptr<PendingTasksList> > PendingTasksMap;
+
+ // content::NotificationObserver interface.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // Called when a lazy background page has finished loading. All enqueued
+ // tasks are run in order.
+ void ProcessPendingTasks(ExtensionHost* host);
+
+ Profile* profile_;
+ content::NotificationRegistrar registrar_;
+ PendingTasksMap pending_tasks_;
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_LAZY_BACKGROUND_TASK_QUEUE_H_
diff --git a/chrome/browser/profiles/off_the_record_profile_impl.cc b/chrome/browser/profiles/off_the_record_profile_impl.cc
index 455714f..5562cb8 100644
--- a/chrome/browser/profiles/off_the_record_profile_impl.cc
+++ b/chrome/browser/profiles/off_the_record_profile_impl.cc
@@ -230,6 +230,10 @@ ExtensionSpecialStoragePolicy*
return GetOriginalProfile()->GetExtensionSpecialStoragePolicy();
}
+LazyBackgroundTaskQueue* OffTheRecordProfileImpl::GetLazyBackgroundTaskQueue() {
+ return GetOriginalProfile()->GetLazyBackgroundTaskQueue();
+}
+
GAIAInfoUpdateService* OffTheRecordProfileImpl::GetGAIAInfoUpdateService() {
return NULL;
}
diff --git a/chrome/browser/profiles/off_the_record_profile_impl.h b/chrome/browser/profiles/off_the_record_profile_impl.h
index 6f1faa5..6868cef 100644
--- a/chrome/browser/profiles/off_the_record_profile_impl.h
+++ b/chrome/browser/profiles/off_the_record_profile_impl.h
@@ -49,6 +49,7 @@ class OffTheRecordProfileImpl : public Profile,
virtual ExtensionEventRouter* GetExtensionEventRouter() OVERRIDE;
virtual ExtensionSpecialStoragePolicy*
GetExtensionSpecialStoragePolicy() OVERRIDE;
+ virtual LazyBackgroundTaskQueue* GetLazyBackgroundTaskQueue() OVERRIDE;
virtual GAIAInfoUpdateService* GetGAIAInfoUpdateService() OVERRIDE;
virtual HistoryService* GetHistoryService(ServiceAccessType sat) OVERRIDE;
virtual HistoryService* GetHistoryServiceWithoutCreating() OVERRIDE;
diff --git a/chrome/browser/profiles/profile.h b/chrome/browser/profiles/profile.h
index c208bce..49037f2 100644
--- a/chrome/browser/profiles/profile.h
+++ b/chrome/browser/profiles/profile.h
@@ -34,6 +34,7 @@ class FaviconService;
class GAIAInfoUpdateService;
class HistoryService;
class HostContentSettingsMap;
+class LazyBackgroundTaskQueue;
class PasswordStore;
class PrefService;
class PromoCounter;
@@ -239,6 +240,10 @@ class Profile : public content::BrowserContext {
virtual ExtensionSpecialStoragePolicy*
GetExtensionSpecialStoragePolicy() = 0;
+ // Accessor. The instance is created at startup.
+ // TODO(yoz): this belongs with the ExtensionSystem.
+ virtual LazyBackgroundTaskQueue* GetLazyBackgroundTaskQueue() = 0;
+
// Retrieves a pointer to the FaviconService associated with this
// profile. The FaviconService is lazily created the first time
// that this method is called.
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index 23f0d49..ee5f5c0 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -42,6 +42,7 @@
#include "chrome/browser/extensions/extension_process_manager.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_special_storage_policy.h"
+#include "chrome/browser/extensions/lazy_background_task_queue.h"
#include "chrome/browser/extensions/unpacked_installer.h"
#include "chrome/browser/extensions/user_script_master.h"
#include "chrome/browser/favicon/favicon_service.h"
@@ -422,8 +423,10 @@ void ProfileImpl::InitExtensions(bool extensions_enabled) {
// ExtensionProcessManager.
extension_info_map_ = new ExtensionInfoMap();
extension_process_manager_.reset(ExtensionProcessManager::Create(this));
+ lazy_background_task_queue_.reset(new LazyBackgroundTaskQueue(this));
extension_event_router_.reset(new ExtensionEventRouter(this));
- extension_message_service_.reset(new ExtensionMessageService(this));
+ extension_message_service_.reset(new ExtensionMessageService(
+ lazy_background_task_queue_.get()));
extension_navigation_observer_.reset(new ExtensionNavigationObserver(this));
ExtensionErrorReporter::Init(true); // allow noisy errors.
@@ -697,6 +700,10 @@ ExtensionSpecialStoragePolicy*
return extension_special_storage_policy_.get();
}
+LazyBackgroundTaskQueue* ProfileImpl::GetLazyBackgroundTaskQueue() {
+ return lazy_background_task_queue_.get();
+}
+
void ProfileImpl::OnPrefsLoaded(bool success) {
if (!success) {
if (delegate_)
diff --git a/chrome/browser/profiles/profile_impl.h b/chrome/browser/profiles/profile_impl.h
index 181cea7..18ff356 100644
--- a/chrome/browser/profiles/profile_impl.h
+++ b/chrome/browser/profiles/profile_impl.h
@@ -83,6 +83,7 @@ class ProfileImpl : public Profile,
virtual ExtensionEventRouter* GetExtensionEventRouter() OVERRIDE;
virtual ExtensionSpecialStoragePolicy*
GetExtensionSpecialStoragePolicy() OVERRIDE;
+ virtual LazyBackgroundTaskQueue* GetLazyBackgroundTaskQueue() OVERRIDE;
virtual FaviconService* GetFaviconService(ServiceAccessType sat) OVERRIDE;
virtual GAIAInfoUpdateService* GetGAIAInfoUpdateService() OVERRIDE;
virtual HistoryService* GetHistoryService(ServiceAccessType sat) OVERRIDE;
@@ -213,6 +214,8 @@ class ProfileImpl : public Profile,
// resource requests from extension processes and those require access
// to the ResourceContext owned by |io_data_|.
scoped_ptr<ExtensionProcessManager> extension_process_manager_;
+ // This is a dependency of ExtensionMessageService and ExtensionEventRouter.
+ scoped_ptr<LazyBackgroundTaskQueue> lazy_background_task_queue_;
scoped_ptr<ExtensionMessageService> extension_message_service_;
scoped_ptr<ExtensionEventRouter> extension_event_router_;
scoped_ptr<ExtensionNavigationObserver> extension_navigation_observer_;
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index e24de23..32828f2 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -378,6 +378,8 @@
'browser/extensions/installed_loader.h',
'browser/extensions/key_identifier_conversion_views.cc',
'browser/extensions/key_identifier_conversion_views.h',
+ 'browser/extensions/lazy_background_task_queue.cc',
+ 'browser/extensions/lazy_background_task_queue.h',
'browser/extensions/pack_extension_job.cc',
'browser/extensions/pack_extension_job.h',
'browser/extensions/pending_extension_info.cc',
diff --git a/chrome/test/base/testing_profile.cc b/chrome/test/base/testing_profile.cc
index 1615b71..722d4e6 100644
--- a/chrome/test/base/testing_profile.cc
+++ b/chrome/test/base/testing_profile.cc
@@ -502,6 +502,10 @@ TestingProfile::GetExtensionSpecialStoragePolicy() {
return extension_special_storage_policy_.get();
}
+LazyBackgroundTaskQueue* TestingProfile::GetLazyBackgroundTaskQueue() {
+ return NULL;
+}
+
FaviconService* TestingProfile::GetFaviconService(ServiceAccessType access) {
return favicon_service_.get();
}
diff --git a/chrome/test/base/testing_profile.h b/chrome/test/base/testing_profile.h
index 7600b97..cc31f9c 100644
--- a/chrome/test/base/testing_profile.h
+++ b/chrome/test/base/testing_profile.h
@@ -190,6 +190,7 @@ class TestingProfile : public Profile {
ExtensionSpecialStoragePolicy* extension_special_storage_policy);
virtual ExtensionSpecialStoragePolicy*
GetExtensionSpecialStoragePolicy() OVERRIDE;
+ virtual LazyBackgroundTaskQueue* GetLazyBackgroundTaskQueue() OVERRIDE;
virtual FaviconService* GetFaviconService(ServiceAccessType access) OVERRIDE;
virtual HistoryService* GetHistoryService(ServiceAccessType access) OVERRIDE;
virtual HistoryService* GetHistoryServiceWithoutCreating() OVERRIDE;
diff --git a/chrome/test/data/extensions/api_test/lazy_background_page/messaging/background.js b/chrome/test/data/extensions/api_test/lazy_background_page/messaging/background.js
new file mode 100644
index 0000000..06f9338
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/lazy_background_page/messaging/background.js
@@ -0,0 +1,9 @@
+// Copyright (c) 2012 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.
+
+chrome.extension.onConnect.addListener(function(port) {
+ port.onMessage.addListener(function(msg) {
+ chrome.test.notifyPass();
+ });
+});
diff --git a/chrome/test/data/extensions/api_test/lazy_background_page/messaging/content.js b/chrome/test/data/extensions/api_test/lazy_background_page/messaging/content.js
new file mode 100644
index 0000000..52953cc
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/lazy_background_page/messaging/content.js
@@ -0,0 +1,6 @@
+// Copyright (c) 2012 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.
+
+var port = chrome.extension.connect();
+port.postMessage({kittenMittens: true});
diff --git a/chrome/test/data/extensions/api_test/lazy_background_page/messaging/manifest.json b/chrome/test/data/extensions/api_test/lazy_background_page/messaging/manifest.json
new file mode 100644
index 0000000..8ccab3b
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/lazy_background_page/messaging/manifest.json
@@ -0,0 +1,17 @@
+{
+ "name": "Lazy BG messaging test",
+ "description": "Test that message passing starts the background page",
+ "version": "1",
+ "manifest_version": 2,
+ "permissions": ["experimental"],
+ "background": {
+ "scripts": ["background.js"],
+ "persistent": false
+ },
+ "content_scripts": [
+ {
+ "matches": ["http://*/*"],
+ "js": ["content.js"]
+ }
+ ]
+}