diff options
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"] + } + ] +} |