diff options
Diffstat (limited to 'extensions')
-rw-r--r-- | extensions/browser/DEPS | 1 | ||||
-rw-r--r-- | extensions/browser/event_listener_map.cc | 2 | ||||
-rw-r--r-- | extensions/browser/event_listener_map_unittest.cc | 2 | ||||
-rw-r--r-- | extensions/browser/event_router.cc | 783 | ||||
-rw-r--r-- | extensions/browser/event_router.h | 387 | ||||
-rw-r--r-- | extensions/browser/event_router_unittest.cc | 122 | ||||
-rw-r--r-- | extensions/extensions.gyp | 2 |
7 files changed, 1296 insertions, 3 deletions
diff --git a/extensions/browser/DEPS b/extensions/browser/DEPS index cf696c7..34ce5b9 100644 --- a/extensions/browser/DEPS +++ b/extensions/browser/DEPS @@ -10,7 +10,6 @@ include_rules = [ # TODO(jamescook): Remove these. http://crbug.com/162530 "+chrome/browser/chrome_notification_types.h", "+chrome/browser/extensions/api/runtime/runtime_api.h", - "+chrome/browser/extensions/event_router.h", "+chrome/browser/extensions/extension_function_dispatcher.h", "+chrome/browser/extensions/extension_function_histogram_value.h", "+chrome/browser/extensions/extension_host.h", diff --git a/extensions/browser/event_listener_map.cc b/extensions/browser/event_listener_map.cc index ebb0f74..1c9fba4 100644 --- a/extensions/browser/event_listener_map.cc +++ b/extensions/browser/event_listener_map.cc @@ -5,7 +5,7 @@ #include "extensions/browser/event_listener_map.h" #include "base/values.h" -#include "chrome/browser/extensions/event_router.h" +#include "extensions/browser/event_router.h" #include "ipc/ipc_message.h" using base::DictionaryValue; diff --git a/extensions/browser/event_listener_map_unittest.cc b/extensions/browser/event_listener_map_unittest.cc index 926a3d1..9f4c5c3 100644 --- a/extensions/browser/event_listener_map_unittest.cc +++ b/extensions/browser/event_listener_map_unittest.cc @@ -4,9 +4,9 @@ #include "extensions/browser/event_listener_map.h" -#include "chrome/browser/extensions/event_router.h" #include "content/public/test/mock_render_process_host.h" #include "content/public/test/test_browser_context.h" +#include "extensions/browser/event_router.h" #include "testing/gtest/include/gtest/gtest.h" using base::DictionaryValue; diff --git a/extensions/browser/event_router.cc b/extensions/browser/event_router.cc new file mode 100644 index 0000000..6105572 --- /dev/null +++ b/extensions/browser/event_router.cc @@ -0,0 +1,783 @@ +// 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 "extensions/browser/event_router.h" + +#include <utility> + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "base/values.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/extensions/extension_host.h" +#include "chrome/browser/extensions/extension_prefs.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/extensions/extension_util.h" +#include "chrome/common/extensions/extension_messages.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/render_process_host.h" +#include "extensions/browser/extensions_browser_client.h" +#include "extensions/browser/lazy_background_task_queue.h" +#include "extensions/browser/process_manager.h" +#include "extensions/browser/process_map.h" +#include "extensions/common/extension.h" +#include "extensions/common/extension_api.h" +#include "extensions/common/extension_urls.h" +#include "extensions/common/manifest_handlers/background_info.h" +#include "extensions/common/manifest_handlers/incognito_info.h" + +using base::DictionaryValue; +using base::ListValue; +using content::BrowserContext; +using content::BrowserThread; + +namespace extensions { + +namespace { + +void DoNothing(ExtensionHost* host) {} + +// A dictionary of event names to lists of filters that this extension has +// registered from its lazy background page. +const char kFilteredEvents[] = "filtered_events"; + +} // namespace + +const char EventRouter::kRegisteredEvents[] = "events"; + +struct EventRouter::ListenerProcess { + content::RenderProcessHost* process; + std::string extension_id; + + ListenerProcess(content::RenderProcessHost* process, + const std::string& extension_id) + : process(process), extension_id(extension_id) {} + + bool operator<(const ListenerProcess& that) const { + if (process < that.process) + return true; + if (process == that.process && extension_id < that.extension_id) + return true; + return false; + } +}; + +// static +void EventRouter::NotifyExtensionDispatchObserverOnUIThread( + void* browser_context_id, + scoped_ptr<EventDispatchInfo> details) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&NotifyExtensionDispatchObserverOnUIThread, + browser_context_id, base::Passed(&details))); + } else { + BrowserContext* context = + reinterpret_cast<BrowserContext*>(browser_context_id); + if (!ExtensionsBrowserClient::Get()->IsValidContext(context)) + return; + ExtensionSystem* extension_system = + ExtensionSystem::GetForBrowserContext(context); + EventRouter* event_router = extension_system->event_router(); + if (!event_router) + return; + if (event_router->event_dispatch_observer_) { + event_router->event_dispatch_observer_->OnWillDispatchEvent( + details.Pass()); + } + } +} + +// static +void EventRouter::DispatchExtensionMessage(IPC::Sender* ipc_sender, + void* browser_context_id, + const std::string& extension_id, + const std::string& event_name, + ListValue* event_args, + UserGestureState user_gesture, + const EventFilteringInfo& info) { + NotifyExtensionDispatchObserverOnUIThread( + browser_context_id, + make_scoped_ptr(new EventDispatchInfo( + extension_id, + event_name, + make_scoped_ptr(event_args->DeepCopy())))); + + ListValue args; + args.Set(0, new base::StringValue(event_name)); + args.Set(1, event_args); + args.Set(2, info.AsValue().release()); + ipc_sender->Send(new ExtensionMsg_MessageInvoke( + MSG_ROUTING_CONTROL, + extension_id, + kEventBindings, + "dispatchEvent", + args, + user_gesture == USER_GESTURE_ENABLED)); + + // DispatchExtensionMessage does _not_ take ownership of event_args, so we + // must ensure that the destruction of args does not attempt to free it. + scoped_ptr<Value> removed_event_args; + args.Remove(1, &removed_event_args); + ignore_result(removed_event_args.release()); +} + +// static +std::string EventRouter::GetBaseEventName(const std::string& full_event_name) { + size_t slash_sep = full_event_name.find('/'); + return full_event_name.substr(0, slash_sep); +} + +// static +void EventRouter::DispatchEvent(IPC::Sender* ipc_sender, + void* browser_context_id, + const std::string& extension_id, + const std::string& event_name, + scoped_ptr<ListValue> event_args, + UserGestureState user_gesture, + const EventFilteringInfo& info) { + DispatchExtensionMessage(ipc_sender, + browser_context_id, + extension_id, + event_name, + event_args.get(), + user_gesture, + info); + + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&EventRouter::IncrementInFlightEventsOnUI, + browser_context_id, + extension_id)); +} + +EventRouter::EventRouter(BrowserContext* browser_context, + ExtensionPrefs* extension_prefs) + : browser_context_(browser_context), + extension_prefs_(extension_prefs), + listeners_(this), + event_dispatch_observer_(NULL) { + registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, + content::NotificationService::AllSources()); + registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED, + content::NotificationService::AllSources()); + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_ENABLED, + content::Source<BrowserContext>(browser_context_)); + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, + content::Source<BrowserContext>(browser_context_)); + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, + content::Source<BrowserContext>(browser_context_)); +} + +EventRouter::~EventRouter() {} + +void EventRouter::AddEventListener(const std::string& event_name, + content::RenderProcessHost* process, + const std::string& extension_id) { + listeners_.AddListener(scoped_ptr<EventListener>(new EventListener( + event_name, extension_id, process, scoped_ptr<DictionaryValue>()))); +} + +void EventRouter::RemoveEventListener(const std::string& event_name, + content::RenderProcessHost* process, + const std::string& extension_id) { + EventListener listener(event_name, extension_id, process, + scoped_ptr<DictionaryValue>()); + listeners_.RemoveListener(&listener); +} + +void EventRouter::RegisterObserver(Observer* observer, + const std::string& event_name) { + // Observing sub-event names like "foo.onBar/123" is not allowed. + DCHECK(event_name.find('/') == std::string::npos); + observers_[event_name] = observer; +} + +void EventRouter::UnregisterObserver(Observer* observer) { + std::vector<ObserverMap::iterator> iters_to_remove; + for (ObserverMap::iterator iter = observers_.begin(); + iter != observers_.end(); ++iter) { + if (iter->second == observer) + iters_to_remove.push_back(iter); + } + for (size_t i = 0; i < iters_to_remove.size(); ++i) + observers_.erase(iters_to_remove[i]); +} + +void EventRouter::SetEventDispatchObserver(EventDispatchObserver* observer) { + CHECK(!event_dispatch_observer_); + event_dispatch_observer_ = observer; +} + +void EventRouter::OnListenerAdded(const EventListener* listener) { + const EventListenerInfo details( + listener->event_name, + listener->extension_id, + listener->process ? listener->process->GetBrowserContext() : NULL); + std::string base_event_name = GetBaseEventName(listener->event_name); + ObserverMap::iterator observer = observers_.find(base_event_name); + if (observer != observers_.end()) + observer->second->OnListenerAdded(details); +} + +void EventRouter::OnListenerRemoved(const EventListener* listener) { + const EventListenerInfo details( + listener->event_name, + listener->extension_id, + listener->process ? listener->process->GetBrowserContext() : NULL); + std::string base_event_name = GetBaseEventName(listener->event_name); + ObserverMap::iterator observer = observers_.find(base_event_name); + if (observer != observers_.end()) + observer->second->OnListenerRemoved(details); +} + +void EventRouter::AddLazyEventListener(const std::string& event_name, + const std::string& extension_id) { + scoped_ptr<EventListener> listener(new EventListener( + event_name, extension_id, NULL, scoped_ptr<DictionaryValue>())); + bool is_new = listeners_.AddListener(listener.Pass()); + + if (is_new) { + std::set<std::string> events = GetRegisteredEvents(extension_id); + bool prefs_is_new = events.insert(event_name).second; + if (prefs_is_new) + SetRegisteredEvents(extension_id, events); + } +} + +void EventRouter::RemoveLazyEventListener(const std::string& event_name, + const std::string& extension_id) { + EventListener listener(event_name, extension_id, NULL, + scoped_ptr<DictionaryValue>()); + bool did_exist = listeners_.RemoveListener(&listener); + + if (did_exist) { + std::set<std::string> events = GetRegisteredEvents(extension_id); + bool prefs_did_exist = events.erase(event_name) > 0; + DCHECK(prefs_did_exist); + SetRegisteredEvents(extension_id, events); + } +} + +void EventRouter::AddFilteredEventListener(const std::string& event_name, + content::RenderProcessHost* process, + const std::string& extension_id, + const base::DictionaryValue& filter, + bool add_lazy_listener) { + listeners_.AddListener(scoped_ptr<EventListener>(new EventListener( + event_name, extension_id, process, + scoped_ptr<DictionaryValue>(filter.DeepCopy())))); + + if (add_lazy_listener) { + bool added = listeners_.AddListener(scoped_ptr<EventListener>( + new EventListener(event_name, extension_id, NULL, + scoped_ptr<DictionaryValue>(filter.DeepCopy())))); + + if (added) + AddFilterToEvent(event_name, extension_id, &filter); + } +} + +void EventRouter::RemoveFilteredEventListener( + const std::string& event_name, + content::RenderProcessHost* process, + const std::string& extension_id, + const base::DictionaryValue& filter, + bool remove_lazy_listener) { + EventListener listener(event_name, extension_id, process, + scoped_ptr<DictionaryValue>(filter.DeepCopy())); + + listeners_.RemoveListener(&listener); + + if (remove_lazy_listener) { + listener.process = NULL; + bool removed = listeners_.RemoveListener(&listener); + + if (removed) + RemoveFilterFromEvent(event_name, extension_id, &filter); + } +} + +bool EventRouter::HasEventListener(const std::string& event_name) { + return listeners_.HasListenerForEvent(event_name); +} + +bool EventRouter::ExtensionHasEventListener(const std::string& extension_id, + const std::string& event_name) { + return listeners_.HasListenerForExtension(extension_id, event_name); +} + +bool EventRouter::HasEventListenerImpl(const ListenerMap& listener_map, + const std::string& extension_id, + const std::string& event_name) { + ListenerMap::const_iterator it = listener_map.find(event_name); + if (it == listener_map.end()) + return false; + + const std::set<ListenerProcess>& listeners = it->second; + if (extension_id.empty()) + return !listeners.empty(); + + for (std::set<ListenerProcess>::const_iterator listener = listeners.begin(); + listener != listeners.end(); ++listener) { + if (listener->extension_id == extension_id) + return true; + } + return false; +} + +std::set<std::string> EventRouter::GetRegisteredEvents( + const std::string& extension_id) { + std::set<std::string> events; + const ListValue* events_value = NULL; + + if (!extension_prefs_ || + !extension_prefs_->ReadPrefAsList( + extension_id, kRegisteredEvents, &events_value)) { + return events; + } + + for (size_t i = 0; i < events_value->GetSize(); ++i) { + std::string event; + if (events_value->GetString(i, &event)) + events.insert(event); + } + return events; +} + +void EventRouter::SetRegisteredEvents(const std::string& extension_id, + const std::set<std::string>& events) { + ListValue* events_value = new ListValue; + for (std::set<std::string>::const_iterator iter = events.begin(); + iter != events.end(); ++iter) { + events_value->Append(new StringValue(*iter)); + } + extension_prefs_->UpdateExtensionPref( + extension_id, kRegisteredEvents, events_value); +} + +void EventRouter::AddFilterToEvent(const std::string& event_name, + const std::string& extension_id, + const DictionaryValue* filter) { + ExtensionPrefs::ScopedDictionaryUpdate update( + extension_prefs_, extension_id, kFilteredEvents); + DictionaryValue* filtered_events = update.Get(); + if (!filtered_events) + filtered_events = update.Create(); + + ListValue* filter_list = NULL; + if (!filtered_events->GetList(event_name, &filter_list)) { + filter_list = new ListValue; + filtered_events->SetWithoutPathExpansion(event_name, filter_list); + } + + filter_list->Append(filter->DeepCopy()); +} + +void EventRouter::RemoveFilterFromEvent(const std::string& event_name, + const std::string& extension_id, + const DictionaryValue* filter) { + ExtensionPrefs::ScopedDictionaryUpdate update( + extension_prefs_, extension_id, kFilteredEvents); + DictionaryValue* filtered_events = update.Get(); + ListValue* filter_list = NULL; + if (!filtered_events || + !filtered_events->GetListWithoutPathExpansion(event_name, &filter_list)) { + return; + } + + for (size_t i = 0; i < filter_list->GetSize(); i++) { + DictionaryValue* filter = NULL; + CHECK(filter_list->GetDictionary(i, &filter)); + if (filter->Equals(filter)) { + filter_list->Remove(i, NULL); + break; + } + } +} + +const DictionaryValue* EventRouter::GetFilteredEvents( + const std::string& extension_id) { + const DictionaryValue* events = NULL; + extension_prefs_->ReadPrefAsDictionary( + extension_id, kFilteredEvents, &events); + return events; +} + +void EventRouter::BroadcastEvent(scoped_ptr<Event> event) { + DispatchEventImpl(std::string(), linked_ptr<Event>(event.release())); +} + +void EventRouter::DispatchEventToExtension(const std::string& extension_id, + scoped_ptr<Event> event) { + DCHECK(!extension_id.empty()); + DispatchEventImpl(extension_id, linked_ptr<Event>(event.release())); +} + +void EventRouter::DispatchEventWithLazyListener(const std::string& extension_id, + scoped_ptr<Event> event) { + DCHECK(!extension_id.empty()); + std::string event_name = event->event_name; + bool has_listener = ExtensionHasEventListener(extension_id, event_name); + if (!has_listener) + AddLazyEventListener(event_name, extension_id); + DispatchEventToExtension(extension_id, event.Pass()); + if (!has_listener) + RemoveLazyEventListener(event_name, extension_id); +} + +void EventRouter::DispatchEventImpl(const std::string& restrict_to_extension_id, + const linked_ptr<Event>& event) { + // We don't expect to get events from a completely different browser context. + DCHECK(!event->restrict_to_browser_context || + ExtensionsBrowserClient::Get()->IsSameContext( + browser_context_, event->restrict_to_browser_context)); + + std::set<const EventListener*> listeners( + listeners_.GetEventListeners(*event)); + + std::set<EventDispatchIdentifier> already_dispatched; + + // We dispatch events for lazy background pages first because attempting to do + // so will cause those that are being suspended to cancel that suspension. + // As canceling a suspension entails sending an event to the affected + // background page, and as that event needs to be delivered before we dispatch + // the event we are dispatching here, we dispatch to the lazy listeners here + // first. + for (std::set<const EventListener*>::iterator it = listeners.begin(); + it != listeners.end(); it++) { + const EventListener* listener = *it; + if (restrict_to_extension_id.empty() || + restrict_to_extension_id == listener->extension_id) { + if (!listener->process) { + DispatchLazyEvent(listener->extension_id, event, &already_dispatched); + } + } + } + + for (std::set<const EventListener*>::iterator it = listeners.begin(); + it != listeners.end(); it++) { + const EventListener* listener = *it; + if (restrict_to_extension_id.empty() || + restrict_to_extension_id == listener->extension_id) { + if (listener->process) { + EventDispatchIdentifier dispatch_id( + listener->process->GetBrowserContext(), listener->extension_id); + if (!ContainsKey(already_dispatched, dispatch_id)) { + DispatchEventToProcess(listener->extension_id, listener->process, + event); + } + } + } + } +} + +void EventRouter::DispatchLazyEvent( + const std::string& extension_id, + const linked_ptr<Event>& event, + std::set<EventDispatchIdentifier>* already_dispatched) { + ExtensionService* service = ExtensionSystem::GetForBrowserContext( + browser_context_)->extension_service(); + // Check both the original and the incognito browser context to see if we + // should load a lazy bg page to handle the event. The latter case + // occurs in the case of split-mode extensions. + const Extension* extension = service->extensions()->GetByID(extension_id); + if (!extension) + return; + + if (MaybeLoadLazyBackgroundPageToDispatchEvent( + browser_context_, extension, event)) { + already_dispatched->insert(std::make_pair(browser_context_, extension_id)); + } + + ExtensionsBrowserClient* browser_client = ExtensionsBrowserClient::Get(); + if (browser_client->HasOffTheRecordContext(browser_context_) && + IncognitoInfo::IsSplitMode(extension)) { + BrowserContext* incognito_context = + browser_client->GetOffTheRecordContext(browser_context_); + if (MaybeLoadLazyBackgroundPageToDispatchEvent( + incognito_context, extension, event)) { + already_dispatched->insert( + std::make_pair(incognito_context, extension_id)); + } + } +} + +void EventRouter::DispatchEventToProcess(const std::string& extension_id, + content::RenderProcessHost* process, + const linked_ptr<Event>& event) { + ExtensionService* service = ExtensionSystem::GetForBrowserContext( + browser_context_)->extension_service(); + const Extension* extension = service->extensions()->GetByID(extension_id); + + // The extension could have been removed, but we do not unregister it until + // the extension process is unloaded. + if (!extension) + return; + + BrowserContext* listener_context = process->GetBrowserContext(); + ProcessMap* process_map = + ExtensionSystem::GetForBrowserContext(listener_context) + ->extension_service() + ->process_map(); + // If the event is privileged, only send to extension processes. Otherwise, + // it's OK to send to normal renderers (e.g., for content scripts). + if (ExtensionAPI::GetSharedInstance()->IsPrivileged(event->event_name) && + !process_map->Contains(extension->id(), process->GetID())) { + return; + } + + // If the event is restricted to a URL, only dispatch if the extension has + // permission for it (or if the event originated from itself). + if (!event->event_url.is_empty() && + event->event_url.host() != extension->id() && + !extension->GetActivePermissions()->HasEffectiveAccessToURL( + event->event_url)) { + return; + } + + if (!CanDispatchEventToBrowserContext(listener_context, extension, event)) + return; + + if (!event->will_dispatch_callback.is_null()) { + event->will_dispatch_callback.Run(listener_context, extension, + event->event_args.get()); + } + + DispatchExtensionMessage(process, listener_context, extension->id(), + event->event_name, event->event_args.get(), + event->user_gesture, event->filter_info); + IncrementInFlightEvents(listener_context, extension); +} + +bool EventRouter::CanDispatchEventToBrowserContext( + BrowserContext* context, + const Extension* extension, + const linked_ptr<Event>& event) { + // Is this event from a different browser context than the renderer (ie, an + // incognito tab event sent to a normal process, or vice versa). + bool cross_incognito = event->restrict_to_browser_context && + context != event->restrict_to_browser_context; + if (!cross_incognito) + return true; + ExtensionService* service = + ExtensionSystem::GetForBrowserContext(context)->extension_service(); + return extension_util::CanCrossIncognito(extension, service); +} + +bool EventRouter::MaybeLoadLazyBackgroundPageToDispatchEvent( + BrowserContext* context, + const Extension* extension, + const linked_ptr<Event>& event) { + if (!CanDispatchEventToBrowserContext(context, extension, event)) + return false; + + LazyBackgroundTaskQueue* queue = ExtensionSystem::GetForBrowserContext( + context)->lazy_background_task_queue(); + if (queue->ShouldEnqueueTask(context, extension)) { + linked_ptr<Event> dispatched_event(event); + + // If there's a dispatch callback, call it now (rather than dispatch time) + // to avoid lifetime issues. Use a separate copy of the event args, so they + // last until the event is dispatched. + if (!event->will_dispatch_callback.is_null()) { + dispatched_event.reset(event->DeepCopy()); + dispatched_event->will_dispatch_callback.Run( + context, extension, dispatched_event->event_args.get()); + // Ensure we don't call it again at dispatch time. + dispatched_event->will_dispatch_callback.Reset(); + } + + queue->AddPendingTask(context, extension->id(), + base::Bind(&EventRouter::DispatchPendingEvent, + base::Unretained(this), dispatched_event)); + return true; + } + + return false; +} + +// static +void EventRouter::IncrementInFlightEventsOnUI( + void* browser_context_id, + const std::string& extension_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + BrowserContext* browser_context = + reinterpret_cast<BrowserContext*>(browser_context_id); + if (!ExtensionsBrowserClient::Get()->IsValidContext(browser_context)) + return; + ExtensionSystem* extension_system = + ExtensionSystem::GetForBrowserContext(browser_context); + EventRouter* event_router = extension_system->event_router(); + if (!event_router) + return; + ExtensionService* extension_service = extension_system->extension_service(); + const Extension* extension = + extension_service->extensions()->GetByID(extension_id); + if (!extension) + return; + event_router->IncrementInFlightEvents(browser_context, extension); +} + +void EventRouter::IncrementInFlightEvents(BrowserContext* context, + const Extension* extension) { + // Only increment in-flight events if the lazy background page is active, + // because that's the only time we'll get an ACK. + if (BackgroundInfo::HasLazyBackgroundPage(extension)) { + ProcessManager* pm = + ExtensionSystem::GetForBrowserContext(context)->process_manager(); + ExtensionHost* host = pm->GetBackgroundHostForExtension(extension->id()); + if (host) + pm->IncrementLazyKeepaliveCount(extension); + } +} + +void EventRouter::OnEventAck(BrowserContext* context, + const std::string& extension_id) { + ProcessManager* pm = + ExtensionSystem::GetForBrowserContext(context)->process_manager(); + ExtensionHost* host = pm->GetBackgroundHostForExtension(extension_id); + // The event ACK is routed to the background host, so this should never be + // NULL. + CHECK(host); + // TODO(mpcomplete): We should never get this message unless + // HasLazyBackgroundPage is true. Find out why we're getting it anyway. + if (host->extension() && + BackgroundInfo::HasLazyBackgroundPage(host->extension())) + pm->DecrementLazyKeepaliveCount(host->extension()); +} + +void EventRouter::DispatchPendingEvent(const linked_ptr<Event>& event, + ExtensionHost* host) { + if (!host) + return; + + if (listeners_.HasProcessListener(host->render_process_host(), + host->extension()->id())) { + DispatchEventToProcess(host->extension()->id(), + host->render_process_host(), event); + } +} + +void EventRouter::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: + case content::NOTIFICATION_RENDERER_PROCESS_CLOSED: { + content::RenderProcessHost* renderer = + content::Source<content::RenderProcessHost>(source).ptr(); + // Remove all event listeners associated with this renderer. + listeners_.RemoveListenersForProcess(renderer); + break; + } + case chrome::NOTIFICATION_EXTENSION_ENABLED: { + // If the extension has a lazy background page, make sure it gets loaded + // to register the events the extension is interested in. + const Extension* extension = + content::Details<const Extension>(details).ptr(); + if (BackgroundInfo::HasLazyBackgroundPage(extension)) { + LazyBackgroundTaskQueue* queue = ExtensionSystem::GetForBrowserContext( + browser_context_)->lazy_background_task_queue(); + queue->AddPendingTask(browser_context_, extension->id(), + base::Bind(&DoNothing)); + } + break; + } + case chrome::NOTIFICATION_EXTENSION_LOADED: { + // Add all registered lazy listeners to our cache. + const Extension* extension = + content::Details<const Extension>(details).ptr(); + std::set<std::string> registered_events = + GetRegisteredEvents(extension->id()); + listeners_.LoadUnfilteredLazyListeners(extension->id(), + registered_events); + const DictionaryValue* filtered_events = + GetFilteredEvents(extension->id()); + if (filtered_events) + listeners_.LoadFilteredLazyListeners(extension->id(), *filtered_events); + break; + } + case chrome::NOTIFICATION_EXTENSION_UNLOADED: { + // Remove all registered lazy listeners from our cache. + UnloadedExtensionInfo* unloaded = + content::Details<UnloadedExtensionInfo>(details).ptr(); + listeners_.RemoveLazyListenersForExtension(unloaded->extension->id()); + break; + } + default: + NOTREACHED(); + return; + } +} + +Event::Event(const std::string& event_name, + scoped_ptr<base::ListValue> event_args) + : event_name(event_name), + event_args(event_args.Pass()), + restrict_to_browser_context(NULL), + user_gesture(EventRouter::USER_GESTURE_UNKNOWN) { + DCHECK(this->event_args.get()); +} + +Event::Event(const std::string& event_name, + scoped_ptr<base::ListValue> event_args, + BrowserContext* restrict_to_browser_context) + : event_name(event_name), + event_args(event_args.Pass()), + restrict_to_browser_context(restrict_to_browser_context), + user_gesture(EventRouter::USER_GESTURE_UNKNOWN) { + DCHECK(this->event_args.get()); +} + +Event::Event(const std::string& event_name, + scoped_ptr<ListValue> event_args, + BrowserContext* restrict_to_browser_context, + const GURL& event_url, + EventRouter::UserGestureState user_gesture, + const EventFilteringInfo& filter_info) + : event_name(event_name), + event_args(event_args.Pass()), + restrict_to_browser_context(restrict_to_browser_context), + event_url(event_url), + user_gesture(user_gesture), + filter_info(filter_info) { + DCHECK(this->event_args.get()); +} + +Event::~Event() {} + +Event* Event::DeepCopy() { + Event* copy = new Event(event_name, + scoped_ptr<base::ListValue>(event_args->DeepCopy()), + restrict_to_browser_context, + event_url, + user_gesture, + filter_info); + copy->will_dispatch_callback = will_dispatch_callback; + return copy; +} + +EventListenerInfo::EventListenerInfo(const std::string& event_name, + const std::string& extension_id, + content::BrowserContext* browser_context) + : event_name(event_name), + extension_id(extension_id), + browser_context(browser_context) {} + +EventDispatchInfo::EventDispatchInfo(const std::string& extension_id, + const std::string& event_name, + scoped_ptr<ListValue> event_args) + : extension_id(extension_id), + event_name(event_name), + event_args(event_args.Pass()) {} + +EventDispatchInfo::~EventDispatchInfo() {} + +} // namespace extensions diff --git a/extensions/browser/event_router.h b/extensions/browser/event_router.h new file mode 100644 index 0000000..d65adb0 --- /dev/null +++ b/extensions/browser/event_router.h @@ -0,0 +1,387 @@ +// 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 EXTENSIONS_BROWSER_EVENT_ROUTER_H_ +#define EXTENSIONS_BROWSER_EVENT_ROUTER_H_ + +#include <map> +#include <set> +#include <string> +#include <utility> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/containers/hash_tables.h" +#include "base/gtest_prod_util.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/ref_counted.h" +#include "base/values.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "extensions/browser/event_listener_map.h" +#include "extensions/common/event_filtering_info.h" +#include "ipc/ipc_sender.h" + +class GURL; +class PrefService; + +namespace content { +class BrowserContext; +class RenderProcessHost; +} + +namespace extensions { +class ActivityLog; +class Extension; +class ExtensionHost; +class ExtensionPrefs; + +struct Event; +struct EventDispatchInfo; +struct EventListenerInfo; + +class EventRouter : public content::NotificationObserver, + public EventListenerMap::Delegate { + public: + // These constants convey the state of our knowledge of whether we're in + // a user-caused gesture as part of DispatchEvent. + enum UserGestureState { + USER_GESTURE_UNKNOWN = 0, + USER_GESTURE_ENABLED = 1, + USER_GESTURE_NOT_ENABLED = 2, + }; + + // The pref key for the list of event names for which an extension has + // registered from its lazy background page. + static const char kRegisteredEvents[]; + + // Observers register interest in events with a particular name and are + // notified when a listener is added or removed. Observers are matched by + // the base name of the event (e.g. adding an event listener for event name + // "foo.onBar/123" will trigger observers registered for "foo.onBar"). + class Observer { + public: + // Called when a listener is added. + virtual void OnListenerAdded(const EventListenerInfo& details) {} + // Called when a listener is removed. + virtual void OnListenerRemoved(const EventListenerInfo& details) {} + }; + + // The EventDispatchObserver is notified on the UI thread whenever + // an event is dispatched. There can be only one EventDispatchObserver. + class EventDispatchObserver { + public: + virtual void OnWillDispatchEvent(scoped_ptr<EventDispatchInfo> details) = 0; + }; + + // Converts event names like "foo.onBar/123" into "foo.onBar". Event names + // without a "/" are returned unchanged. + static std::string GetBaseEventName(const std::string& full_event_name); + + // Sends an event via ipc_sender to the given extension. Can be called on any + // thread. + static void DispatchEvent(IPC::Sender* ipc_sender, + void* browser_context_id, + const std::string& extension_id, + const std::string& event_name, + scoped_ptr<base::ListValue> event_args, + UserGestureState user_gesture, + const EventFilteringInfo& info); + + // An EventRouter is shared between |browser_context| and its associated + // incognito context. |extension_prefs| may be NULL in tests. + EventRouter(content::BrowserContext* browser_context, + ExtensionPrefs* extension_prefs); + virtual ~EventRouter(); + + // Add or remove the process/extension pair as a listener for |event_name|. + // Note that multiple extensions can share a process due to process + // collapsing. Also, a single extension can have 2 processes if it is a split + // mode extension. + void AddEventListener(const std::string& event_name, + content::RenderProcessHost* process, + const std::string& extension_id); + void RemoveEventListener(const std::string& event_name, + content::RenderProcessHost* process, + const std::string& extension_id); + + EventListenerMap& listeners() { return listeners_; } + + // Registers an observer to be notified when an event listener for + // |event_name| is added or removed. There can currently be only one observer + // for each distinct |event_name|. + void RegisterObserver(Observer* observer, + const std::string& event_name); + + // Unregisters an observer from all events. + void UnregisterObserver(Observer* observer); + + // Sets the observer to be notified whenever an event is dispatched to an + // extension. + void SetEventDispatchObserver(EventDispatchObserver* observer); + + // Add or remove the extension as having a lazy background page that listens + // to the event. The difference from the above methods is that these will be + // remembered even after the process goes away. We use this list to decide + // which extension pages to load when dispatching an event. + void AddLazyEventListener(const std::string& event_name, + const std::string& extension_id); + void RemoveLazyEventListener(const std::string& event_name, + const std::string& extension_id); + + // If |add_lazy_listener| is true also add the lazy version of this listener. + void AddFilteredEventListener(const std::string& event_name, + content::RenderProcessHost* process, + const std::string& extension_id, + const base::DictionaryValue& filter, + bool add_lazy_listener); + + // If |remove_lazy_listener| is true also remove the lazy version of this + // listener. + void RemoveFilteredEventListener(const std::string& event_name, + content::RenderProcessHost* process, + const std::string& extension_id, + const base::DictionaryValue& filter, + bool remove_lazy_listener); + + // Returns true if there is at least one listener for the given event. + bool HasEventListener(const std::string& event_name); + + // Returns true if the extension is listening to the given event. + bool ExtensionHasEventListener(const std::string& extension_id, + const std::string& event_name); + + // Return or set the list of events for which the given extension has + // registered. + std::set<std::string> GetRegisteredEvents(const std::string& extension_id); + void SetRegisteredEvents(const std::string& extension_id, + const std::set<std::string>& events); + + // Broadcasts an event to every listener registered for that event. + virtual void BroadcastEvent(scoped_ptr<Event> event); + + // Dispatches an event to the given extension. + virtual void DispatchEventToExtension(const std::string& extension_id, + scoped_ptr<Event> event); + + // Dispatches |event| to the given extension as if the extension has a lazy + // listener for it. NOTE: This should be used rarely, for dispatching events + // to extensions that haven't had a chance to add their own listeners yet, eg: + // newly installed extensions. + void DispatchEventWithLazyListener(const std::string& extension_id, + scoped_ptr<Event> event); + + // Record the Event Ack from the renderer. (One less event in-flight.) + void OnEventAck(content::BrowserContext* context, + const std::string& extension_id); + + private: + FRIEND_TEST_ALL_PREFIXES(EventRouterTest, EventRouterObserver); + + // The extension and process that contains the event listener for a given + // event. + struct ListenerProcess; + + // A map between an event name and a set of extensions that are listening + // to that event. + typedef std::map<std::string, std::set<ListenerProcess> > ListenerMap; + + // An identifier for an event dispatch that is used to prevent double dispatch + // due to race conditions between the direct and lazy dispatch paths. + typedef std::pair<const content::BrowserContext*, std::string> + EventDispatchIdentifier; + + // Sends a notification about an event to the event dispatch observer on the + // UI thread. Can be called from any thread. + static void NotifyExtensionDispatchObserverOnUIThread( + void* browser_context_id, + scoped_ptr<EventDispatchInfo> details); + + // TODO(gdk): Document this. + static void DispatchExtensionMessage( + IPC::Sender* ipc_sender, + void* browser_context_id, + const std::string& extension_id, + const std::string& event_name, + base::ListValue* event_args, + UserGestureState user_gesture, + const extensions::EventFilteringInfo& info); + + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // Returns true if the given listener map contains a event listeners for + // the given event. If |extension_id| is non-empty, we also check that that + // extension is one of the listeners. + bool HasEventListenerImpl(const ListenerMap& listeners, + const std::string& extension_id, + const std::string& event_name); + + // Shared by DispatchEvent*. If |restrict_to_extension_id| is empty, the + // event is broadcast. + // An event that just came off the pending list may not be delayed again. + void DispatchEventImpl(const std::string& restrict_to_extension_id, + const linked_ptr<Event>& event); + + // Ensures that all lazy background pages that are interested in the given + // event are loaded, and queues the event if the page is not ready yet. + // Inserts an EventDispatchIdentifier into |already_dispatched| for each lazy + // event dispatch that is queued. + void DispatchLazyEvent(const std::string& extension_id, + const linked_ptr<Event>& event, + std::set<EventDispatchIdentifier>* already_dispatched); + + // Dispatches the event to the specified extension running in |process|. + void DispatchEventToProcess(const std::string& extension_id, + content::RenderProcessHost* process, + const linked_ptr<Event>& event); + + // Returns false when the event is scoped to a context and the listening + // extension does not have access to events from that context. Also fills + // |event_args| with the proper arguments to send, which may differ if + // the event crosses the incognito boundary. + bool CanDispatchEventToBrowserContext(content::BrowserContext* context, + const Extension* extension, + const linked_ptr<Event>& event); + + // Possibly loads given extension's background page in preparation to + // dispatch an event. Returns true if the event was queued for subsequent + // dispatch, false otherwise. + bool MaybeLoadLazyBackgroundPageToDispatchEvent( + content::BrowserContext* context, + const Extension* extension, + const linked_ptr<Event>& event); + + // Adds a filter to an event. + void AddFilterToEvent(const std::string& event_name, + const std::string& extension_id, + const base::DictionaryValue* filter); + + // Removes a filter from an event. + void RemoveFilterFromEvent(const std::string& event_name, + const std::string& extension_id, + const base::DictionaryValue* filter); + + // Returns the dictionary of event filters that the given extension has + // registered. + const base::DictionaryValue* GetFilteredEvents( + const std::string& extension_id); + + // Track of the number of dispatched events that have not yet sent an + // ACK from the renderer. + void IncrementInFlightEvents(content::BrowserContext* context, + const Extension* extension); + + // static + static void IncrementInFlightEventsOnUI( + void* browser_context_id, + const std::string& extension_id); + + void DispatchPendingEvent(const linked_ptr<Event>& event, + ExtensionHost* host); + + // Implementation of EventListenerMap::Delegate. + virtual void OnListenerAdded(const EventListener* listener) OVERRIDE; + virtual void OnListenerRemoved(const EventListener* listener) OVERRIDE; + + content::BrowserContext* browser_context_; + + // The ExtensionPrefs associated with |browser_context_|. May be NULL in + // tests. + ExtensionPrefs* extension_prefs_; + + content::NotificationRegistrar registrar_; + + EventListenerMap listeners_; + + // Map from base event name to observer. + typedef base::hash_map<std::string, Observer*> ObserverMap; + ObserverMap observers_; + + EventDispatchObserver* event_dispatch_observer_; + + DISALLOW_COPY_AND_ASSIGN(EventRouter); +}; + +struct Event { + typedef base::Callback<void(content::BrowserContext*, + const Extension*, + base::ListValue*)> WillDispatchCallback; + + // The event to dispatch. + std::string event_name; + + // Arguments to send to the event listener. + scoped_ptr<base::ListValue> event_args; + + // If non-NULL, then the event will not be sent to other BrowserContexts + // unless the extension has permission (e.g. incognito tab update -> normal + // tab only works if extension is allowed incognito access). + content::BrowserContext* restrict_to_browser_context; + + // If not empty, the event is only sent to extensions with host permissions + // for this url. + GURL event_url; + + // Whether a user gesture triggered the event. + EventRouter::UserGestureState user_gesture; + + // Extra information used to filter which events are sent to the listener. + EventFilteringInfo filter_info; + + // If specified, this is called before dispatching an event to each + // extension. The third argument is a mutable reference to event_args, + // allowing the caller to provide different arguments depending on the + // extension and profile. This is guaranteed to be called synchronously with + // DispatchEvent, so callers don't need to worry about lifetime. + WillDispatchCallback will_dispatch_callback; + + Event(const std::string& event_name, + scoped_ptr<base::ListValue> event_args); + + Event(const std::string& event_name, + scoped_ptr<base::ListValue> event_args, + content::BrowserContext* restrict_to_browser_context); + + Event(const std::string& event_name, + scoped_ptr<base::ListValue> event_args, + content::BrowserContext* restrict_to_browser_context, + const GURL& event_url, + EventRouter::UserGestureState user_gesture, + const EventFilteringInfo& info); + + ~Event(); + + // Makes a deep copy of this instance. Ownership is transferred to the + // caller. + Event* DeepCopy(); +}; + +struct EventListenerInfo { + EventListenerInfo(const std::string& event_name, + const std::string& extension_id, + content::BrowserContext* browser_context); + // The event name including any sub-event, e.g. "runtime.onStartup" or + // "webRequest.onCompleted/123". + const std::string event_name; + + const std::string extension_id; + content::BrowserContext* browser_context; +}; + +struct EventDispatchInfo { + EventDispatchInfo(const std::string& extension_id, + const std::string& event_name, + scoped_ptr<ListValue> event_args); + ~EventDispatchInfo(); + + const std::string extension_id; + const std::string event_name; + scoped_ptr<ListValue> event_args; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_EVENT_ROUTER_H_ diff --git a/extensions/browser/event_router_unittest.cc b/extensions/browser/event_router_unittest.cc new file mode 100644 index 0000000..ba5fed3 --- /dev/null +++ b/extensions/browser/event_router_unittest.cc @@ -0,0 +1,122 @@ +// Copyright 2013 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 "extensions/browser/event_router.h" + +#include <string> + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "extensions/browser/event_listener_map.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { + +namespace { + +// A simple mock to keep track of listener additions and removals. +class MockEventRouterObserver : public EventRouter::Observer { + public: + MockEventRouterObserver() + : listener_added_count_(0), + listener_removed_count_(0) {} + virtual ~MockEventRouterObserver() {} + + int listener_added_count() const { return listener_added_count_; } + int listener_removed_count() const { return listener_removed_count_; } + const std::string& last_event_name() const { return last_event_name_; } + + void Reset() { + listener_added_count_ = 0; + listener_removed_count_ = 0; + last_event_name_.clear(); + } + + // EventRouter::Observer overrides: + virtual void OnListenerAdded(const EventListenerInfo& details) OVERRIDE { + listener_added_count_++; + last_event_name_ = details.event_name; + } + + virtual void OnListenerRemoved(const EventListenerInfo& details) OVERRIDE { + listener_removed_count_++; + last_event_name_ = details.event_name; + } + + private: + int listener_added_count_; + int listener_removed_count_; + std::string last_event_name_; + + DISALLOW_COPY_AND_ASSIGN(MockEventRouterObserver); +}; + +} // namespace + +typedef testing::Test EventRouterTest; + +TEST_F(EventRouterTest, GetBaseEventName) { + // Normal event names are passed through unchanged. + EXPECT_EQ("foo.onBar", EventRouter::GetBaseEventName("foo.onBar")); + + // Sub-events are converted to the part before the slash. + EXPECT_EQ("foo.onBar", EventRouter::GetBaseEventName("foo.onBar/123")); +} + +// Tests adding and removing observers from EventRouter. +TEST_F(EventRouterTest, EventRouterObserver) { + EventRouter router(NULL, NULL); + EventListener listener( + "event_name", "extension_id", NULL, scoped_ptr<DictionaryValue>()); + + // Add/remove works without any observers. + router.OnListenerAdded(&listener); + router.OnListenerRemoved(&listener); + + // Register observers that both match and don't match the event above. + MockEventRouterObserver matching_observer; + router.RegisterObserver(&matching_observer, "event_name"); + MockEventRouterObserver non_matching_observer; + router.RegisterObserver(&non_matching_observer, "other"); + + // Adding a listener notifies the appropriate observers. + router.OnListenerAdded(&listener); + EXPECT_EQ(1, matching_observer.listener_added_count()); + EXPECT_EQ(0, non_matching_observer.listener_added_count()); + + // Removing a listener notifies the appropriate observers. + router.OnListenerRemoved(&listener); + EXPECT_EQ(1, matching_observer.listener_removed_count()); + EXPECT_EQ(0, non_matching_observer.listener_removed_count()); + + // Adding the listener again notifies again. + router.OnListenerAdded(&listener); + EXPECT_EQ(2, matching_observer.listener_added_count()); + EXPECT_EQ(0, non_matching_observer.listener_added_count()); + + // Removing the listener again notifies again. + router.OnListenerRemoved(&listener); + EXPECT_EQ(2, matching_observer.listener_removed_count()); + EXPECT_EQ(0, non_matching_observer.listener_removed_count()); + + // Adding a listener with a sub-event notifies the main observer with + // proper details. + matching_observer.Reset(); + EventListener sub_event_listener( + "event_name/1", "extension_id", NULL, scoped_ptr<DictionaryValue>()); + router.OnListenerAdded(&sub_event_listener); + EXPECT_EQ(1, matching_observer.listener_added_count()); + EXPECT_EQ(0, matching_observer.listener_removed_count()); + EXPECT_EQ("event_name/1", matching_observer.last_event_name()); + + // Ditto for removing the listener. + matching_observer.Reset(); + router.OnListenerRemoved(&sub_event_listener); + EXPECT_EQ(0, matching_observer.listener_added_count()); + EXPECT_EQ(1, matching_observer.listener_removed_count()); + EXPECT_EQ("event_name/1", matching_observer.last_event_name()); +} + +} // namespace extensions diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp index af642d4..16d290a 100644 --- a/extensions/extensions.gyp +++ b/extensions/extensions.gyp @@ -159,6 +159,8 @@ 'browser/app_sorting.h', 'browser/event_listener_map.cc', 'browser/event_listener_map.h', + 'browser/event_router.cc', + 'browser/event_router.h', 'browser/extension_prefs_scope.h', 'browser/extension_error.cc', 'browser/extension_error.h', |