summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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"]
+ }
+ ]
+}