summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'extensions')
-rw-r--r--extensions/browser/DEPS1
-rw-r--r--extensions/browser/event_listener_map.cc2
-rw-r--r--extensions/browser/event_listener_map_unittest.cc2
-rw-r--r--extensions/browser/event_router.cc783
-rw-r--r--extensions/browser/event_router.h387
-rw-r--r--extensions/browser/event_router_unittest.cc122
-rw-r--r--extensions/extensions.gyp2
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',