summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkoz@chromium.org <koz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-25 03:40:08 +0000
committerkoz@chromium.org <koz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-25 03:40:08 +0000
commit64804d573437ccefc95c88306523850009fcc516 (patch)
tree6f82cee566aaa7075cb2726512bdc6b3cba7295d
parentab95315836cae6073fac88174f50653c6b2b0025 (diff)
downloadchromium_src-64804d573437ccefc95c88306523850009fcc516.zip
chromium_src-64804d573437ccefc95c88306523850009fcc516.tar.gz
chromium_src-64804d573437ccefc95c88306523850009fcc516.tar.bz2
Filtered events.
Makes web_navigation events support filters, eg: chrome.webNavigation.onBeforeCommitted.addListener(callback, {url: [{hostSuffix: 'google.com'}]}); Now callback will only be called when the event has a URL with a host suffix of google.com. BUG=121479 Review URL: https://chromiumcodereview.appspot.com/10514013 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@143872 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/accessibility/accessibility_extension_api.cc2
-rw-r--r--chrome/browser/bookmarks/bookmark_extension_api.cc2
-rw-r--r--chrome/browser/bookmarks/bookmark_manager_extension_api.cc2
-rw-r--r--chrome/browser/extensions/api/cookies/cookies_api.cc2
-rw-r--r--chrome/browser/extensions/api/downloads/downloads_api.cc3
-rw-r--r--chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc3
-rw-r--r--chrome/browser/extensions/api/web_navigation/web_navigation_api.cc45
-rw-r--r--chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc12
-rw-r--r--chrome/browser/extensions/api/web_request/web_request_api.cc3
-rw-r--r--chrome/browser/extensions/event_listener_map.cc240
-rw-r--r--chrome/browser/extensions/event_listener_map.h158
-rw-r--r--chrome/browser/extensions/event_listener_map_unittest.cc300
-rw-r--r--chrome/browser/extensions/extension_browser_event_router.cc2
-rw-r--r--chrome/browser/extensions/extension_devtools_bridge.cc6
-rw-r--r--chrome/browser/extensions/extension_event_router.cc423
-rw-r--r--chrome/browser/extensions/extension_event_router.h122
-rw-r--r--chrome/browser/extensions/extension_event_router_forwarder.cc3
-rw-r--r--chrome/browser/extensions/extension_idle_api.cc2
-rw-r--r--chrome/browser/extensions/extension_managed_mode_api.cc5
-rw-r--r--chrome/browser/extensions/extension_management_api.cc2
-rw-r--r--chrome/browser/extensions/extension_messages_apitest.cc12
-rw-r--r--chrome/browser/extensions/extension_prefs.cc57
-rw-r--r--chrome/browser/extensions/extension_prefs.h15
-rw-r--r--chrome/browser/extensions/extension_processes_api.cc2
-rw-r--r--chrome/browser/extensions/lazy_background_page_apitest.cc16
-rw-r--r--chrome/browser/extensions/system/system_api.cc2
-rw-r--r--chrome/browser/history/history_extension_api.cc3
-rw-r--r--chrome/browser/renderer_host/chrome_render_message_filter.cc34
-rw-r--r--chrome/browser/renderer_host/chrome_render_message_filter.h8
-rw-r--r--chrome/chrome_browser_extensions.gypi2
-rw-r--r--chrome/chrome_common.gypi2
-rw-r--r--chrome/chrome_tests.gypi2
-rw-r--r--chrome/common/extensions/api/web_navigation.json35
-rw-r--r--chrome/common/extensions/event_filtering_info.cc14
-rw-r--r--chrome/common/extensions/event_filtering_info.h8
-rw-r--r--chrome/common/extensions/extension_messages.h16
-rw-r--r--chrome/common/extensions/value_counter.cc67
-rw-r--r--chrome/common/extensions/value_counter.h72
-rw-r--r--chrome/common/extensions/value_counter_unittest.cc42
-rw-r--r--chrome/renderer/extensions/event_bindings.cc225
-rw-r--r--chrome/renderer/extensions/event_bindings.h7
-rw-r--r--chrome/renderer/extensions/event_unittest.cc154
-rw-r--r--chrome/renderer/extensions/extension_dispatcher.cc5
-rw-r--r--chrome/renderer/extensions/extension_dispatcher.h9
-rw-r--r--chrome/renderer/resources/extensions/event.js220
-rw-r--r--chrome/test/data/extensions/api_test/filtered_events/manifest.json11
-rw-r--r--chrome/test/data/extensions/api_test/lazy_background_page/filters/background.js9
-rw-r--r--chrome/test/data/extensions/api_test/lazy_background_page/filters/manifest.json11
-rw-r--r--chrome/test/data/extensions/api_test/webnavigation/filtered/a.html1
-rw-r--r--chrome/test/data/extensions/api_test/webnavigation/filtered/b.html1
-rw-r--r--chrome/test/data/extensions/api_test/webnavigation/test_filtered.html5
-rw-r--r--chrome/test/data/extensions/api_test/webnavigation/test_filtered.js28
52 files changed, 2094 insertions, 338 deletions
diff --git a/chrome/browser/accessibility/accessibility_extension_api.cc b/chrome/browser/accessibility/accessibility_extension_api.cc
index 981bf0b..e2c9fa9 100644
--- a/chrome/browser/accessibility/accessibility_extension_api.cc
+++ b/chrome/browser/accessibility/accessibility_extension_api.cc
@@ -164,7 +164,7 @@ void ExtensionAccessibilityEventRouter::DispatchEvent(
const std::string& json_args) {
if (enabled_ && profile && profile->GetExtensionEventRouter()) {
profile->GetExtensionEventRouter()->DispatchEventToRenderers(
- event_name, json_args, NULL, GURL());
+ event_name, json_args, NULL, GURL(), extensions::EventFilteringInfo());
}
}
diff --git a/chrome/browser/bookmarks/bookmark_extension_api.cc b/chrome/browser/bookmarks/bookmark_extension_api.cc
index 2543977..a5eedb1 100644
--- a/chrome/browser/bookmarks/bookmark_extension_api.cc
+++ b/chrome/browser/bookmarks/bookmark_extension_api.cc
@@ -147,7 +147,7 @@ void BookmarkExtensionEventRouter::DispatchEvent(Profile *profile,
const std::string& json_args) {
if (profile->GetExtensionEventRouter()) {
profile->GetExtensionEventRouter()->DispatchEventToRenderers(
- event_name, json_args, NULL, GURL());
+ event_name, json_args, NULL, GURL(), extensions::EventFilteringInfo());
}
}
diff --git a/chrome/browser/bookmarks/bookmark_manager_extension_api.cc b/chrome/browser/bookmarks/bookmark_manager_extension_api.cc
index 159d7f8..4679096 100644
--- a/chrome/browser/bookmarks/bookmark_manager_extension_api.cc
+++ b/chrome/browser/bookmarks/bookmark_manager_extension_api.cc
@@ -173,7 +173,7 @@ void BookmarkManagerExtensionEventRouter::DispatchEvent(const char* event_name,
std::string json_args;
base::JSONWriter::Write(args, &json_args);
profile_->GetExtensionEventRouter()->DispatchEventToRenderers(
- event_name, json_args, NULL, GURL());
+ event_name, json_args, NULL, GURL(), extensions::EventFilteringInfo());
}
void BookmarkManagerExtensionEventRouter::DispatchDragEvent(
diff --git a/chrome/browser/extensions/api/cookies/cookies_api.cc b/chrome/browser/extensions/api/cookies/cookies_api.cc
index e2cb2b0..cb17f30 100644
--- a/chrome/browser/extensions/api/cookies/cookies_api.cc
+++ b/chrome/browser/extensions/api/cookies/cookies_api.cc
@@ -115,7 +115,7 @@ void ExtensionCookiesEventRouter::DispatchEvent(Profile* profile,
GURL& cookie_domain) {
if (profile && profile->GetExtensionEventRouter()) {
profile->GetExtensionEventRouter()->DispatchEventToRenderers(
- event_name, json_args, profile, cookie_domain);
+ event_name, json_args, profile, cookie_domain, EventFilteringInfo());
}
}
diff --git a/chrome/browser/extensions/api/downloads/downloads_api.cc b/chrome/browser/extensions/api/downloads/downloads_api.cc
index ea28deb..4233402 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api.cc
+++ b/chrome/browser/extensions/api/downloads/downloads_api.cc
@@ -1101,5 +1101,6 @@ void ExtensionDownloadsEventRouter::DispatchEvent(
event_name,
json_args,
profile_,
- GURL());
+ GURL(),
+ extensions::EventFilteringInfo());
}
diff --git a/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc
index 71b9fd9..d35a43c 100644
--- a/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc
+++ b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc
@@ -276,7 +276,8 @@ void OffscreenTab::Observe(int type,
// event.
Profile* profile = parent_tab_->tab_contents()->profile();
profile->GetExtensionEventRouter()->DispatchEventToRenderers(
- events::kOnOffscreenTabUpdated, json_args, profile, GURL());
+ events::kOnOffscreenTabUpdated, json_args, profile, GURL(),
+ extensions::EventFilteringInfo());
}
ParentTab::ParentTab() : tab_contents_(NULL) {}
diff --git a/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc b/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc
index 906294a..82fe920 100644
--- a/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc
+++ b/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc
@@ -20,6 +20,7 @@
#include "chrome/browser/view_type_utils.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/extensions/api/web_navigation.h"
+#include "chrome/common/extensions/event_filtering_info.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/resource_request_details.h"
#include "content/public/browser/navigation_details.h"
@@ -74,11 +75,18 @@ double MilliSecondsFromTime(const base::Time& time) {
// Dispatches events to the extension message service.
void DispatchEvent(BrowserContext* browser_context,
const char* event_name,
- const std::string& json_args) {
+ const ListValue& args,
+ const GURL& url) {
+ std::string json_args;
+ base::JSONWriter::Write(&args, &json_args);
+
+ extensions::EventFilteringInfo info;
+ info.SetURL(url);
+
Profile* profile = Profile::FromBrowserContext(browser_context);
if (profile && profile->GetExtensionEventRouter()) {
profile->GetExtensionEventRouter()->DispatchEventToRenderers(
- event_name, json_args, profile, GURL());
+ event_name, json_args, profile, GURL(), info);
}
}
@@ -95,11 +103,10 @@ void DispatchOnBeforeNavigate(WebContents* web_contents,
dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
args.Append(dict);
- std::string json_args;
- base::JSONWriter::Write(&args, &json_args);
DispatchEvent(web_contents->GetBrowserContext(),
keys::kOnBeforeNavigate,
- json_args);
+ args,
+ validated_url);
}
// Constructs and dispatches an onCommitted or onReferenceFragmentUpdated
@@ -131,9 +138,7 @@ void DispatchOnCommitted(const char* event_name,
dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
args.Append(dict);
- std::string json_args;
- base::JSONWriter::Write(&args, &json_args);
- DispatchEvent(web_contents->GetBrowserContext(), event_name, json_args);
+ DispatchEvent(web_contents->GetBrowserContext(), event_name, args, url);
}
// Constructs and dispatches an onDOMContentLoaded event.
@@ -150,11 +155,10 @@ void DispatchOnDOMContentLoaded(WebContents* web_contents,
dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
args.Append(dict);
- std::string json_args;
- base::JSONWriter::Write(&args, &json_args);
DispatchEvent(web_contents->GetBrowserContext(),
keys::kOnDOMContentLoaded,
- json_args);
+ args,
+ url);
}
// Constructs and dispatches an onCompleted event.
@@ -171,10 +175,8 @@ void DispatchOnCompleted(WebContents* web_contents,
dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
args.Append(dict);
- std::string json_args;
- base::JSONWriter::Write(&args, &json_args);
- DispatchEvent(web_contents->GetBrowserContext(),
- keys::kOnCompleted, json_args);
+ DispatchEvent(web_contents->GetBrowserContext(), keys::kOnCompleted, args,
+ url);
}
// Constructs and dispatches an onCreatedNavigationTarget event.
@@ -204,10 +206,8 @@ void DispatchOnCreatedNavigationTarget(
dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
args.Append(dict);
- std::string json_args;
- base::JSONWriter::Write(&args, &json_args);
- DispatchEvent(
- browser_context, keys::kOnCreatedNavigationTarget, json_args);
+ DispatchEvent(browser_context, keys::kOnCreatedNavigationTarget, args,
+ target_url);
}
// Constructs and dispatches an onErrorOccurred event.
@@ -225,11 +225,8 @@ void DispatchOnErrorOccurred(WebContents* web_contents,
dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
args.Append(dict);
- std::string json_args;
- base::JSONWriter::Write(&args, &json_args);
- DispatchEvent(web_contents->GetBrowserContext(),
- keys::kOnErrorOccurred,
- json_args);
+ DispatchEvent(web_contents->GetBrowserContext(), keys::kOnErrorOccurred,
+ args, url);
}
} // namespace
diff --git a/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc b/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
index 1ebf8bb..f1d95f4 100644
--- a/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
+++ b/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
@@ -32,6 +32,7 @@ using content::WebContents;
#define MAYBE_WebNavigationReferenceFragment \
DISABLED_WebNavigationReferenceFragment
#define MAYBE_WebNavigationOpenTab DISABLED_WebNavigationOpenTab
+#define MAYBE_WebNavigationFilteredTest DISABLED_WebNavigationFilteredTest
#else
#define MAYBE_WebNavigationIFrame WebNavigationIFrame
#define MAYBE_WebNavigationFailures WebNavigationFailures
@@ -41,6 +42,7 @@ using content::WebContents;
#define MAYBE_WebNavigationSimpleLoad WebNavigationSimpleLoad
#define MAYBE_WebNavigationReferenceFragment WebNavigationReferenceFragment
#define MAYBE_WebNavigationOpenTab WebNavigationOpenTab
+#define MAYBE_WebNavigationFilteredTest WebNavigationFilteredTest
#endif
namespace extensions {
@@ -173,6 +175,16 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MAYBE_WebNavigationFailures) {
RunExtensionSubtest("webnavigation", "test_failures.html")) << message_;
}
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MAYBE_WebNavigationFilteredTest) {
+ FrameNavigationState::set_allow_extension_scheme(true);
+
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kAllowLegacyExtensionManifests);
+
+ ASSERT_TRUE(
+ RunExtensionSubtest("webnavigation", "test_filtered.html")) << message_;
+}
+
IN_PROC_BROWSER_TEST_F(ExtensionApiTest, WebNavigationUserAction) {
FrameNavigationState::set_allow_extension_scheme(true);
diff --git a/chrome/browser/extensions/api/web_request/web_request_api.cc b/chrome/browser/extensions/api/web_request/web_request_api.cc
index bff5aea..ea0ae29 100644
--- a/chrome/browser/extensions/api/web_request/web_request_api.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_api.cc
@@ -937,7 +937,8 @@ bool ExtensionWebRequestEventRouter::DispatchEvent(
ExtensionEventRouter::DispatchEvent(
(*it)->ipc_sender.get(), (*it)->extension_id, (*it)->sub_event_name,
- json_args, GURL(), ExtensionEventRouter::USER_GESTURE_UNKNOWN);
+ json_args, GURL(), ExtensionEventRouter::USER_GESTURE_UNKNOWN,
+ EventFilteringInfo());
if ((*it)->extra_info_spec &
(ExtraInfoSpec::BLOCKING | ExtraInfoSpec::ASYNC_BLOCKING)) {
(*it)->blocked_requests.insert(request->identifier());
diff --git a/chrome/browser/extensions/event_listener_map.cc b/chrome/browser/extensions/event_listener_map.cc
new file mode 100644
index 0000000..2ed22f0
--- /dev/null
+++ b/chrome/browser/extensions/event_listener_map.cc
@@ -0,0 +1,240 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/event_listener_map.h"
+
+#include "base/values.h"
+
+#include "chrome/browser/extensions/extension_event_router.h"
+
+namespace extensions {
+
+typedef EventFilter::MatcherID MatcherID;
+
+EventListener::EventListener(const std::string& event_name,
+ const std::string& extension_id,
+ content::RenderProcessHost* process,
+ scoped_ptr<DictionaryValue> filter)
+ : event_name(event_name),
+ extension_id(extension_id),
+ process(process),
+ filter(filter.Pass()),
+ matcher_id(-1) {}
+
+EventListener::~EventListener() {}
+
+bool EventListener::Equals(const EventListener* other) const {
+ // We don't check matcher_id equality because we want a listener with a
+ // filter that hasn't been added to EventFilter to match one that is
+ // equivalent but has.
+ return event_name == other->event_name &&
+ extension_id == other->extension_id &&
+ process == other->process &&
+ ((!!filter.get()) == (!!other->filter.get())) &&
+ (!filter.get() || filter->Equals(other->filter.get()));
+}
+
+scoped_ptr<EventListener> EventListener::Copy() const {
+ scoped_ptr<DictionaryValue> filter_copy;
+ if (filter.get())
+ filter_copy.reset(filter->DeepCopy());
+ return scoped_ptr<EventListener>(new EventListener(event_name, extension_id,
+ process,
+ filter_copy.Pass()));
+}
+
+EventListenerMap::EventListenerMap(Delegate* delegate)
+ : delegate_(delegate) {
+}
+
+EventListenerMap::~EventListenerMap() {}
+
+bool EventListenerMap::AddListener(scoped_ptr<EventListener> listener) {
+ if (HasListener(listener.get()))
+ return false;
+ if (listener->filter.get()) {
+ scoped_ptr<EventMatcher> matcher(ParseEventMatcher(listener->filter.get()));
+ MatcherID id = event_filter_.AddEventMatcher(listener->event_name,
+ matcher.Pass());
+ listener->matcher_id = id;
+ listeners_by_matcher_id_[id] = listener.get();
+ filtered_events_.insert(listener->event_name);
+ }
+ linked_ptr<EventListener> listener_ptr(listener.release());
+ listeners_[listener_ptr->event_name].push_back(listener_ptr);
+
+ delegate_->OnListenerAdded(listener_ptr.get());
+
+ return true;
+}
+
+scoped_ptr<EventMatcher> EventListenerMap::ParseEventMatcher(
+ DictionaryValue* filter_dict) {
+ return scoped_ptr<EventMatcher>(new EventMatcher(
+ scoped_ptr<DictionaryValue>(filter_dict->DeepCopy())));
+}
+
+bool EventListenerMap::RemoveListener(const EventListener* listener) {
+ ListenerList& listeners = listeners_[listener->event_name];
+ for (ListenerList::iterator it = listeners.begin(); it != listeners.end();
+ it++) {
+ if ((*it)->Equals(listener)) {
+ delegate_->OnListenerRemoved(it->get());
+ CleanupListener(it->get());
+ // Popping from the back should be cheaper than erase(it).
+ std::swap(*it, listeners.back());
+ listeners.pop_back();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool EventListenerMap::HasListenerForEvent(const std::string& event_name) {
+ ListenerMap::iterator it = listeners_.find(event_name);
+ return it != listeners_.end() && !it->second.empty();
+}
+
+bool EventListenerMap::HasListenerForExtension(
+ const std::string& extension_id,
+ const std::string& event_name) {
+ ListenerMap::iterator it = listeners_.find(event_name);
+ if (it == listeners_.end())
+ return false;
+
+ for (ListenerList::iterator it2 = it->second.begin();
+ it2 != it->second.end(); it2++) {
+ if ((*it2)->extension_id == extension_id)
+ return true;
+ }
+ return false;
+}
+
+bool EventListenerMap::HasListener(const EventListener* listener) {
+ ListenerMap::iterator it = listeners_.find(listener->event_name);
+ if (it == listeners_.end())
+ return false;
+ for (ListenerList::iterator it2 = it->second.begin();
+ it2 != it->second.end(); it2++) {
+ if ((*it2)->Equals(listener)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool EventListenerMap::HasProcessListener(content::RenderProcessHost* process,
+ const std::string& extension_id) {
+ for (ListenerMap::iterator it = listeners_.begin(); it != listeners_.end();
+ it++) {
+ for (ListenerList::iterator it2 = it->second.begin();
+ it2 != it->second.end(); it2++) {
+ if ((*it2)->process == process && (*it2)->extension_id == extension_id)
+ return true;
+ }
+ }
+ return false;
+}
+
+void EventListenerMap::RemoveLazyListenersForExtension(
+ const std::string& extension_id) {
+ for (ListenerMap::iterator it = listeners_.begin(); it != listeners_.end();
+ it++) {
+ for (ListenerList::iterator it2 = it->second.begin();
+ it2 != it->second.end();) {
+ if (!(*it2)->process && (*it2)->extension_id == extension_id) {
+ CleanupListener(it2->get());
+ it2 = it->second.erase(it2);
+ } else {
+ it2++;
+ }
+ }
+ }
+}
+
+void EventListenerMap::AddLazyListenersFromPreferences(
+ const std::string& extension_id,
+ const std::set<std::string>& unfiltered,
+ const DictionaryValue& filtered) {
+ for (std::set<std::string>::const_iterator it = unfiltered.begin();
+ it != unfiltered.end(); ++it) {
+ scoped_ptr<EventListener> listener(new EventListener(
+ *it, extension_id, NULL,
+ scoped_ptr<DictionaryValue>()));
+ AddListener(listener.Pass());
+ }
+
+ for (DictionaryValue::key_iterator it = filtered.begin_keys();
+ it != filtered.end_keys(); ++it) {
+ // We skip entries if they are malformed.
+ ListValue* filter_list = NULL;
+ if (!filtered.GetList(*it, &filter_list))
+ continue;
+ for (size_t i = 0; i < filter_list->GetSize(); i++) {
+ DictionaryValue* filter = NULL;
+ if (!filter_list->GetDictionary(i, &filter))
+ continue;
+ scoped_ptr<EventListener> listener(new EventListener(
+ *it, extension_id, NULL,
+ scoped_ptr<DictionaryValue>(filter->DeepCopy())));
+ AddListener(listener.Pass());
+ }
+ }
+}
+
+std::set<const EventListener*> EventListenerMap::GetEventListeners(
+ const ExtensionEvent& event) {
+ std::set<const EventListener*> interested_listeners;
+ if (IsFilteredEvent(event)) {
+ // Look up the interested listeners via the EventFilter.
+ std::set<MatcherID> ids =
+ event_filter_.MatchEvent(event.event_name, event.info);
+ for (std::set<MatcherID>::iterator id = ids.begin(); id != ids.end();
+ id++) {
+ EventListener* listener = listeners_by_matcher_id_[*id];
+ CHECK(listener);
+ interested_listeners.insert(listener);
+ }
+ } else {
+ ListenerList& listeners = listeners_[event.event_name];
+ for (ListenerList::const_iterator it = listeners.begin();
+ it != listeners.end(); it++) {
+ interested_listeners.insert(it->get());
+ }
+ }
+
+ return interested_listeners;
+}
+
+void EventListenerMap::RemoveListenersForProcess(
+ const content::RenderProcessHost* process) {
+ CHECK(process);
+ for (ListenerMap::iterator it = listeners_.begin(); it != listeners_.end();
+ it++) {
+ for (ListenerList::iterator it2 = it->second.begin();
+ it2 != it->second.end();) {
+ if ((*it2)->process == process) {
+ delegate_->OnListenerRemoved(it2->get());
+ CleanupListener(it2->get());
+ it2 = it->second.erase(it2);
+ } else {
+ it2++;
+ }
+ }
+ }
+}
+
+void EventListenerMap::CleanupListener(EventListener* listener) {
+ // If the listener doesn't have a filter then we have nothing to clean up.
+ if (listener->matcher_id == -1)
+ return;
+ event_filter_.RemoveEventMatcher(listener->matcher_id);
+ CHECK_EQ(1u, listeners_by_matcher_id_.erase(listener->matcher_id));
+}
+
+bool EventListenerMap::IsFilteredEvent(const ExtensionEvent& event) const {
+ return filtered_events_.count(event.event_name) > 0u;
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/event_listener_map.h b/chrome/browser/extensions/event_listener_map.h
new file mode 100644
index 0000000..bcce17e
--- /dev/null
+++ b/chrome/browser/extensions/event_listener_map.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_EVENT_LISTENER_MAP_H_
+#define CHROME_BROWSER_EXTENSIONS_EVENT_LISTENER_MAP_H_
+#pragma once
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/extensions/event_filter.h"
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace content {
+class RenderProcessHost;
+}
+
+struct ExtensionEvent;
+class ListenerRemovalListener;
+
+using base::DictionaryValue;
+
+namespace extensions {
+
+// A listener for an extension event. A listener is essentially an endpoint
+// that an event can be dispatched to. This is a lazy listener if |process| is
+// NULL and a filtered listener if |filter| is defined.
+//
+// A lazy listener is added to an event to indicate that a lazy background page
+// is listening to the event. It is associated with no process, so to dispatch
+// an event to a lazy listener one must start a process running the associated
+// extension and dispatch the event to that.
+//
+struct EventListener {
+ // |filter| represents a generic filter structure that EventFilter knows how
+ // to filter events with. A typical filter instance will look like
+ //
+ // {
+ // url: [{hostSuffix: 'google.com'}],
+ // tabId: 5
+ // }
+ EventListener(const std::string& event_name,
+ const std::string& extension_id,
+ content::RenderProcessHost* process,
+ scoped_ptr<DictionaryValue> filter);
+ ~EventListener();
+
+ bool Equals(const EventListener* other) const;
+
+ scoped_ptr<EventListener> Copy() const;
+
+ const std::string event_name;
+ const std::string extension_id;
+ content::RenderProcessHost* process;
+ scoped_ptr<DictionaryValue> filter;
+ EventFilter::MatcherID matcher_id;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EventListener);
+};
+
+// Holds listeners for extension events and can answer questions about which
+// listeners are interested in what events.
+class EventListenerMap {
+ public:
+ typedef std::vector<linked_ptr<EventListener> > ListenerList;
+
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+ virtual void OnListenerAdded(const EventListener* listener) = 0;
+ virtual void OnListenerRemoved(const EventListener* listener) = 0;
+ };
+
+ explicit EventListenerMap(Delegate* delegate);
+ ~EventListenerMap();
+
+ // Add a listener for a particular event. GetEventListeners() will include a
+ // weak pointer to |listener| in its results if passed a relevant
+ // ExtensionEvent.
+ // Returns true if the listener was added (in the case that it has never been
+ // seen before).
+ bool AddListener(scoped_ptr<EventListener> listener);
+
+ // Remove a listener that .Equals() |listener|.
+ // Returns true if the listener was removed .
+ bool RemoveListener(const EventListener* listener);
+
+ // Returns the set of listeners that want to be notified of |event|.
+ std::set<const EventListener*> GetEventListeners(
+ const ExtensionEvent& event);
+
+ // Removes all listeners with process equal to |process|.
+ void RemoveListenersForProcess(const content::RenderProcessHost* process);
+
+ // Returns true if there are any listeners on the event named |event_name|.
+ bool HasListenerForEvent(const std::string& event_name);
+
+ // Returns true if there are any listeners on |event_name| from
+ // |extension_id|.
+ bool HasListenerForExtension(const std::string& extension_id,
+ const std::string& event_name);
+
+ // Returns true if this map contains an EventListener that .Equals()
+ // |listener|.
+ bool HasListener(const EventListener* listener);
+
+ // Returns true if there is a listener for |extension_id| in |process|.
+ bool HasProcessListener(content::RenderProcessHost* process,
+ const std::string& extension_id);
+
+ // Removes any lazy listeners that |extension_id| has added.
+ void RemoveLazyListenersForExtension(const std::string& extension_id);
+
+ // Adds lazy listeners as described by |unfiltered| and |filtered|, which
+ // contain serialised descriptions of listeners.
+ // |unfiltered| contains a list of event names, which each defines an
+ // unfiltered lazy listener for that event.
+ // |filtered| contains a map from event names to filters, each pairing
+ // defining a lazy filtered listener.
+ // Note that we can only load lazy listeners in this fashion, because there
+ // is no way to serialise a RenderProcessHost*.
+ void AddLazyListenersFromPreferences(
+ const std::string& extension_id,
+ const std::set<std::string>& unfiltered,
+ const DictionaryValue& filtered);
+
+ private:
+ // The key here is an event name.
+ typedef std::map<std::string, ListenerList> ListenerMap;
+
+ void CleanupListener(EventListener* listener);
+ bool IsFilteredEvent(const ExtensionEvent& event) const;
+ scoped_ptr<EventMatcher> ParseEventMatcher(DictionaryValue* filter_dict);
+
+ // Listens for removals from this map.
+ Delegate* delegate_;
+
+ std::set<std::string> filtered_events_;
+ ListenerMap listeners_;
+
+ std::map<EventFilter::MatcherID, EventListener*> listeners_by_matcher_id_;
+
+ EventFilter event_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventListenerMap);
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_EVENT_LISTENER_MAP_H_
diff --git a/chrome/browser/extensions/event_listener_map_unittest.cc b/chrome/browser/extensions/event_listener_map_unittest.cc
new file mode 100644
index 0000000..efec0cd
--- /dev/null
+++ b/chrome/browser/extensions/event_listener_map_unittest.cc
@@ -0,0 +1,300 @@
+// 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 "testing/gtest/include/gtest/gtest.h"
+
+#include "chrome/browser/extensions/event_listener_map.h"
+#include "chrome/browser/extensions/extension_event_router.h"
+#include "content/public/test/mock_render_process_host.h"
+
+namespace {
+
+const char kExt1Id[] = "extension_1";
+const char kExt2Id[] = "extension_2";
+const char kEvent1Name[] = "event1";
+const char kEvent2Name[] = "event2";
+
+} // namespace
+
+class Delegate : public EventListenerMap::Delegate {
+ virtual void OnListenerAdded(const EventListener* listener) OVERRIDE {};
+ virtual void OnListenerRemoved(const EventListener* listener) OVERRIDE {};
+};
+
+class EventListenerMapUnittest : public testing::Test {
+ public:
+ EventListenerMapUnittest()
+ : delegate_(new Delegate),
+ listeners_(new EventListenerMap(delegate_.get())),
+ process_(new content::MockRenderProcessHost(NULL)) {
+ }
+
+ scoped_ptr<DictionaryValue> CreateHostSuffixFilter(
+ const std::string& suffix) {
+ scoped_ptr<DictionaryValue> filter(new DictionaryValue);
+ scoped_ptr<ListValue> filter_list(new ListValue);
+ scoped_ptr<DictionaryValue> filter_dict(new DictionaryValue);
+
+ filter_dict->Set("hostSuffix", new StringValue(suffix));
+
+ filter_list->Append(filter_dict.release());
+ filter->Set("url", filter_list.release());
+ return filter.Pass();
+ }
+
+ scoped_ptr<ExtensionEvent> CreateNamedEvent(const std::string& event_name) {
+ return CreateEvent(event_name, GURL());
+ }
+
+ scoped_ptr<ExtensionEvent> CreateEvent(const std::string& event_name,
+ const GURL& url) {
+ EventFilteringInfo info;
+ info.SetURL(url);
+ scoped_ptr<ExtensionEvent> result(new ExtensionEvent(event_name, "", GURL(),
+ NULL, "", ExtensionEventRouter::USER_GESTURE_UNKNOWN, info));
+ return result.Pass();
+ }
+
+ protected:
+ scoped_ptr<Delegate> delegate_;
+ scoped_ptr<EventListenerMap> listeners_;
+ scoped_ptr<content::MockRenderProcessHost> process_;
+};
+
+TEST_F(EventListenerMapUnittest, UnfilteredEventsGoToAllListeners) {
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, NULL, scoped_ptr<DictionaryValue>())));
+
+ scoped_ptr<ExtensionEvent> event(CreateNamedEvent(kEvent1Name));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(1u, targets.size());
+}
+
+TEST_F(EventListenerMapUnittest, FilteredEventsGoToAllMatchingListeners) {
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com"))));
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, NULL, scoped_ptr<DictionaryValue>(
+ new DictionaryValue))));
+
+ scoped_ptr<ExtensionEvent> event(CreateNamedEvent(kEvent1Name));
+ event->info.SetURL(GURL("http://www.google.com"));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(2u, targets.size());
+}
+
+TEST_F(EventListenerMapUnittest, FilteredEventsOnlyGoToMatchingListeners) {
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com"))));
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("yahoo.com"))));
+
+ scoped_ptr<ExtensionEvent> event(CreateNamedEvent(kEvent1Name));
+ event->info.SetURL(GURL("http://www.google.com"));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(1u, targets.size());
+}
+
+TEST_F(EventListenerMapUnittest, LazyAndUnlazyListenersGetReturned) {
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com"))));
+
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, process_.get(),
+ CreateHostSuffixFilter("google.com"))));
+
+ scoped_ptr<ExtensionEvent> event(CreateNamedEvent(kEvent1Name));
+ event->info.SetURL(GURL("http://www.google.com"));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(2u, targets.size());
+}
+
+TEST_F(EventListenerMapUnittest, TestRemovingByProcess) {
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com"))));
+
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, process_.get(),
+ CreateHostSuffixFilter("google.com"))));
+
+ listeners_->RemoveListenersForProcess(process_.get());
+
+ scoped_ptr<ExtensionEvent> event(CreateNamedEvent(kEvent1Name));
+ event->info.SetURL(GURL("http://www.google.com"));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(1u, targets.size());
+}
+
+TEST_F(EventListenerMapUnittest, TestRemovingByListener) {
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com"))));
+
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, process_.get(),
+ CreateHostSuffixFilter("google.com"))));
+
+ scoped_ptr<EventListener> listener(new EventListener(kEvent1Name, kExt1Id,
+ process_.get(), CreateHostSuffixFilter("google.com")));
+ listeners_->RemoveListener(listener.get());
+
+ scoped_ptr<ExtensionEvent> event(CreateNamedEvent(kEvent1Name));
+ event->info.SetURL(GURL("http://www.google.com"));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(1u, targets.size());
+}
+
+TEST_F(EventListenerMapUnittest, TestLazyDoubleAddIsUndoneByRemove) {
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com"))));
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com"))));
+
+ scoped_ptr<EventListener> listener(new EventListener(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+ listeners_->RemoveListener(listener.get());
+
+ scoped_ptr<ExtensionEvent> event(CreateNamedEvent(kEvent1Name));
+ event->info.SetURL(GURL("http://www.google.com"));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(0u, targets.size());
+}
+
+TEST_F(EventListenerMapUnittest, HostSuffixFilterEquality) {
+ scoped_ptr<DictionaryValue> filter1(CreateHostSuffixFilter("google.com"));
+ scoped_ptr<DictionaryValue> filter2(CreateHostSuffixFilter("google.com"));
+ ASSERT_TRUE(filter1->Equals(filter2.get()));
+}
+
+TEST_F(EventListenerMapUnittest, RemoveLazyListenersForExtension) {
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com"))));
+ listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
+ kEvent2Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com"))));
+
+ listeners_->RemoveLazyListenersForExtension(kExt1Id);
+
+ scoped_ptr<ExtensionEvent> event(CreateNamedEvent(kEvent1Name));
+ event->info.SetURL(GURL("http://www.google.com"));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(0u, targets.size());
+
+ event->event_name = kEvent2Name;
+ targets = listeners_->GetEventListeners(*event);
+ ASSERT_EQ(0u, targets.size());
+}
+
+TEST_F(EventListenerMapUnittest, AddExistingFilteredListener) {
+ bool first_new = listeners_->AddListener(scoped_ptr<EventListener>(
+ new EventListener(kEvent1Name, kExt1Id, NULL,
+ CreateHostSuffixFilter("google.com"))));
+ bool second_new = listeners_->AddListener(scoped_ptr<EventListener>(
+ new EventListener(kEvent1Name, kExt1Id, NULL,
+ CreateHostSuffixFilter("google.com"))));
+
+ ASSERT_TRUE(first_new);
+ ASSERT_FALSE(second_new);
+}
+
+TEST_F(EventListenerMapUnittest, AddExistingUnfilteredListener) {
+ bool first_add = listeners_->AddListener(scoped_ptr<EventListener>(
+ new EventListener(kEvent1Name, kExt1Id, NULL,
+ scoped_ptr<DictionaryValue>())));
+ bool second_add = listeners_->AddListener(scoped_ptr<EventListener>(
+ new EventListener(kEvent1Name, kExt1Id, NULL,
+ scoped_ptr<DictionaryValue>())));
+
+ scoped_ptr<EventListener> listener(
+ new EventListener(kEvent1Name, kExt1Id, NULL,
+ scoped_ptr<DictionaryValue>()));
+ bool first_remove = listeners_->RemoveListener(listener.get());
+ bool second_remove = listeners_->RemoveListener(listener.get());
+
+ ASSERT_TRUE(first_add);
+ ASSERT_FALSE(second_add);
+ ASSERT_TRUE(first_remove);
+ ASSERT_FALSE(second_remove);
+}
+
+TEST_F(EventListenerMapUnittest, RemovingRouters) {
+ listeners_->AddListener(scoped_ptr<EventListener>(
+ new EventListener(kEvent1Name, kExt1Id, process_.get(),
+ scoped_ptr<DictionaryValue>())));
+ listeners_->RemoveListenersForProcess(process_.get());
+ ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent1Name));
+}
+
+TEST_F(EventListenerMapUnittest, HasListenerForEvent) {
+ ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent1Name));
+
+ listeners_->AddListener(scoped_ptr<EventListener>(
+ new EventListener(kEvent1Name, kExt1Id, process_.get(),
+ scoped_ptr<DictionaryValue>())));
+
+ ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent2Name));
+ ASSERT_TRUE(listeners_->HasListenerForEvent(kEvent1Name));
+ listeners_->RemoveListenersForProcess(process_.get());
+ ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent1Name));
+}
+
+TEST_F(EventListenerMapUnittest, HasListenerForExtension) {
+ ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
+
+ // Non-lazy listener.
+ listeners_->AddListener(scoped_ptr<EventListener>(
+ new EventListener(kEvent1Name, kExt1Id, process_.get(),
+ scoped_ptr<DictionaryValue>())));
+ // Lazy listener.
+ listeners_->AddListener(scoped_ptr<EventListener>(
+ new EventListener(kEvent1Name, kExt1Id, NULL,
+ scoped_ptr<DictionaryValue>())));
+
+ ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent2Name));
+ ASSERT_TRUE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
+ ASSERT_FALSE(listeners_->HasListenerForExtension(kExt2Id, kEvent1Name));
+ listeners_->RemoveListenersForProcess(process_.get());
+ ASSERT_TRUE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
+ listeners_->RemoveLazyListenersForExtension(kExt1Id);
+ ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
+}
+
+TEST_F(EventListenerMapUnittest, AddLazyListenersFromPreferences) {
+ scoped_ptr<DictionaryValue> filter1(CreateHostSuffixFilter("google.com"));
+ scoped_ptr<DictionaryValue> filter2(CreateHostSuffixFilter("yahoo.com"));
+
+ DictionaryValue filtered_listeners;
+ ListValue* filter_list = new ListValue();
+ filtered_listeners.Set(kEvent1Name, filter_list);
+
+ filter_list->Append(filter1.release());
+ filter_list->Append(filter2.release());
+
+ std::set<std::string> unfiltered_listeners;
+ listeners_->AddLazyListenersFromPreferences(kExt1Id, unfiltered_listeners,
+ filtered_listeners);
+
+ scoped_ptr<ExtensionEvent> event(CreateEvent(kEvent1Name,
+ GURL("http://www.google.com")));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(1u, targets.size());
+ scoped_ptr<EventListener> listener(new EventListener(kEvent1Name, kExt1Id,
+ NULL, CreateHostSuffixFilter("google.com")));
+ ASSERT_TRUE((*targets.begin())->Equals(listener.get()));
+}
+
+TEST_F(EventListenerMapUnittest, CorruptedExtensionPrefsShouldntCrash) {
+ scoped_ptr<DictionaryValue> filter1(CreateHostSuffixFilter("google.com"));
+
+ DictionaryValue filtered_listeners;
+ // kEvent1Name should be associated with a list, not a dictionary.
+ filtered_listeners.Set(kEvent1Name, filter1.release());
+
+ std::set<std::string> unfiltered_listeners;
+ listeners_->AddLazyListenersFromPreferences(kExt1Id, unfiltered_listeners,
+ filtered_listeners);
+
+ scoped_ptr<ExtensionEvent> event(CreateEvent(kEvent1Name,
+ GURL("http://www.google.com")));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(0u, targets.size());
+}
diff --git a/chrome/browser/extensions/extension_browser_event_router.cc b/chrome/browser/extensions/extension_browser_event_router.cc
index 745d4e5..ea1ab0b 100644
--- a/chrome/browser/extensions/extension_browser_event_router.cc
+++ b/chrome/browser/extensions/extension_browser_event_router.cc
@@ -451,7 +451,7 @@ void ExtensionBrowserEventRouter::DispatchEvent(Profile* profile,
return;
profile->GetExtensionEventRouter()->DispatchEventToRenderers(
- event_name, json_args, profile, GURL());
+ event_name, json_args, profile, GURL(), extensions::EventFilteringInfo());
}
void ExtensionBrowserEventRouter::DispatchEventToExtension(
diff --git a/chrome/browser/extensions/extension_devtools_bridge.cc b/chrome/browser/extensions/extension_devtools_bridge.cc
index bdd4f91..ff331b7 100644
--- a/chrome/browser/extensions/extension_devtools_bridge.cc
+++ b/chrome/browser/extensions/extension_devtools_bridge.cc
@@ -107,7 +107,8 @@ void ExtensionDevToolsBridge::InspectedContentsClosing() {
// event in extensions.
std::string json("[{}]");
profile_->GetExtensionEventRouter()->DispatchEventToRenderers(
- on_tab_close_event_name_, json, profile_, GURL());
+ on_tab_close_event_name_, json, profile_, GURL(),
+ extensions::EventFilteringInfo());
// This may result in this object being destroyed.
extension_devtools_manager_->BridgeClosingForTab(tab_id_);
@@ -119,7 +120,8 @@ void ExtensionDevToolsBridge::DispatchOnInspectorFrontend(
std::string json = base::StringPrintf("[%s]", data.c_str());
profile_->GetExtensionEventRouter()->DispatchEventToRenderers(
- on_page_event_name_, json, profile_, GURL());
+ on_page_event_name_, json, profile_, GURL(),
+ extensions::EventFilteringInfo());
}
void ExtensionDevToolsBridge::ContentsReplaced(WebContents* new_contents) {
diff --git a/chrome/browser/extensions/extension_event_router.cc b/chrome/browser/extensions/extension_event_router.cc
index 9dab54c..c3b680d 100644
--- a/chrome/browser/extensions/extension_event_router.cc
+++ b/chrome/browser/extensions/extension_event_router.cc
@@ -65,88 +65,46 @@ struct ExtensionEventRouter::ListenerProcess {
}
};
-struct ExtensionEventRouter::ExtensionEvent {
- std::string event_name;
- scoped_ptr<Value> event_args;
- GURL event_url;
- Profile* restrict_to_profile;
- scoped_ptr<Value> cross_incognito_args;
- UserGestureState user_gesture;
-
- ExtensionEvent(const std::string& event_name,
- const Value& event_args,
- const GURL& event_url,
- Profile* restrict_to_profile,
- const Value& cross_incognito_args,
- UserGestureState user_gesture)
- : event_name(event_name),
- event_args(event_args.DeepCopy()),
- event_url(event_url),
- restrict_to_profile(restrict_to_profile),
- cross_incognito_args(cross_incognito_args.DeepCopy()),
- user_gesture(user_gesture) {}
-
- ExtensionEvent(const std::string& event_name,
- const Value& event_args,
- const GURL& event_url,
- Profile* restrict_to_profile,
- UserGestureState user_gesture)
- : event_name(event_name),
- event_args(event_args.DeepCopy()),
- event_url(event_url),
- restrict_to_profile(restrict_to_profile),
- cross_incognito_args(NULL),
- user_gesture(user_gesture) {}
-
- // TODO(gdk): This variant should be retired once the callers are switched to
- // providing Values instead of just strings.
- ExtensionEvent(const std::string& event_name,
- const std::string& event_args,
- const GURL& event_url,
- Profile* restrict_to_profile,
- const std::string& cross_incognito_args,
- UserGestureState user_gesture)
- : event_name(event_name),
- event_args(Value::CreateStringValue(event_args)),
- event_url(event_url),
- restrict_to_profile(restrict_to_profile),
- cross_incognito_args(Value::CreateStringValue(cross_incognito_args)),
- user_gesture(user_gesture) {}
-};
-
// static
-void ExtensionEventRouter::DispatchEvent(IPC::Sender* ipc_sender,
- const std::string& extension_id,
- const std::string& event_name,
- const Value& event_args,
- const GURL& event_url,
- UserGestureState user_gesture) {
+void ExtensionEventRouter::DispatchEvent(
+ IPC::Message::Sender* ipc_sender,
+ const std::string& extension_id,
+ const std::string& event_name,
+ const Value& event_args,
+ const GURL& event_url,
+ UserGestureState user_gesture,
+ const extensions::EventFilteringInfo& info) {
// TODO(gdk): Reduce number of DeepCopy() calls throughout the event dispatch
// chain, starting by replacing the event_args with a Value*.
ListValue args;
args.Set(0, Value::CreateStringValue(event_name));
args.Set(1, event_args.DeepCopy());
+ args.Set(2, info.AsValue().release());
+
ipc_sender->Send(new ExtensionMsg_MessageInvoke(MSG_ROUTING_CONTROL,
extension_id, kDispatchEvent, args, event_url,
user_gesture == USER_GESTURE_ENABLED));
}
// static
-void ExtensionEventRouter::DispatchEvent(IPC::Sender* ipc_sender,
- const std::string& extension_id,
- const std::string& event_name,
- const std::string& event_args,
- const GURL& event_url,
- UserGestureState user_gesture) {
+void ExtensionEventRouter::DispatchEvent(
+ IPC::Sender* ipc_sender,
+ const std::string& extension_id,
+ const std::string& event_name,
+ const std::string& event_args,
+ const GURL& event_url,
+ UserGestureState user_gesture,
+ const extensions::EventFilteringInfo& info) {
scoped_ptr<Value> event_args_value(Value::CreateStringValue(event_args));
DispatchEvent(ipc_sender, extension_id, event_name, *event_args_value.get(),
- event_url, user_gesture);
+ event_url, user_gesture, info);
}
ExtensionEventRouter::ExtensionEventRouter(Profile* profile)
: profile_(profile),
extension_devtools_manager_(
- ExtensionSystem::Get(profile)->devtools_manager()) {
+ ExtensionSystem::Get(profile)->devtools_manager()),
+ listeners_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
content::NotificationService::AllSources());
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
@@ -165,37 +123,46 @@ void ExtensionEventRouter::AddEventListener(
const std::string& event_name,
content::RenderProcessHost* process,
const std::string& extension_id) {
- ListenerProcess listener(process, extension_id);
- DCHECK_EQ(listeners_[event_name].count(listener), 0u) << event_name;
- listeners_[event_name].insert(listener);
+ listeners_.AddListener(scoped_ptr<EventListener>(new EventListener(
+ event_name, extension_id, process, scoped_ptr<DictionaryValue>())));
+}
+
+void ExtensionEventRouter::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 ExtensionEventRouter::OnListenerAdded(const EventListener* listener) {
+ // We don't care about lazy events being added.
+ if (!listener->process)
+ return;
if (extension_devtools_manager_.get())
- extension_devtools_manager_->AddEventListener(event_name,
- process->GetID());
+ extension_devtools_manager_->AddEventListener(listener->event_name,
+ listener->process->GetID());
// We lazily tell the TaskManager to start updating when listeners to the
// processes.onUpdated or processes.onUpdatedWithMemory events arrive.
+ const std::string& event_name = listener->event_name;
if (event_name.compare(extension_processes_api_constants::kOnUpdated) == 0 ||
event_name.compare(
extension_processes_api_constants::kOnUpdatedWithMemory) == 0)
ExtensionProcessesEventRouter::GetInstance()->ListenerAdded();
}
-void ExtensionEventRouter::RemoveEventListener(
- const std::string& event_name,
- content::RenderProcessHost* process,
- const std::string& extension_id) {
- ListenerProcess listener(process, extension_id);
- DCHECK_EQ(listeners_[event_name].count(listener), 1u) <<
- " PID=" << process->GetID() << " extension=" << extension_id <<
- " event=" << event_name;
- listeners_[event_name].erase(listener);
- // Note: extension_id may point to data in the now-deleted listeners_ object.
- // Do not use.
+void ExtensionEventRouter::OnListenerRemoved(const EventListener* listener) {
+ // We don't care about lazy events being removed.
+ if (!listener->process)
+ return;
+ const std::string& event_name = listener->event_name;
if (extension_devtools_manager_.get())
- extension_devtools_manager_->RemoveEventListener(event_name,
- process->GetID());
+ extension_devtools_manager_->RemoveEventListener(
+ event_name, listener->process->GetID());
// If a processes.onUpdated or processes.onUpdatedWithMemory event listener
// is removed (or a process with one exits), then we let the extension API
@@ -209,14 +176,16 @@ void ExtensionEventRouter::RemoveEventListener(
BrowserThread::IO, FROM_HERE,
base::Bind(
&NotifyEventListenerRemovedOnIOThread,
- profile_, listener.extension_id, event_name));
+ profile_, listener->extension_id, listener->event_name));
}
void ExtensionEventRouter::AddLazyEventListener(
const std::string& event_name,
const std::string& extension_id) {
- ListenerProcess lazy_listener(NULL, extension_id);
- bool is_new = lazy_listeners_[event_name].insert(lazy_listener).second;
+ scoped_ptr<EventListener> listener(new EventListener(
+ event_name, extension_id, NULL, scoped_ptr<DictionaryValue>()));
+ bool is_new = listeners_.AddListener(listener.Pass());
+
if (is_new) {
ExtensionPrefs* prefs = profile_->GetExtensionService()->extension_prefs();
std::set<std::string> events = prefs->GetRegisteredEvents(extension_id);
@@ -229,8 +198,10 @@ void ExtensionEventRouter::AddLazyEventListener(
void ExtensionEventRouter::RemoveLazyEventListener(
const std::string& event_name,
const std::string& extension_id) {
- ListenerProcess lazy_listener(NULL, extension_id);
- bool did_exist = lazy_listeners_[event_name].erase(lazy_listener) > 0;
+ EventListener listener(event_name, extension_id, NULL,
+ scoped_ptr<DictionaryValue>());
+ bool did_exist = listeners_.RemoveListener(&listener);
+
if (did_exist) {
ExtensionPrefs* prefs = profile_->GetExtensionService()->extension_prefs();
std::set<std::string> events = prefs->GetRegisteredEvents(extension_id);
@@ -240,15 +211,59 @@ void ExtensionEventRouter::RemoveLazyEventListener(
}
}
+void ExtensionEventRouter::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) {
+ ExtensionPrefs* prefs =
+ profile_->GetExtensionService()->extension_prefs();
+ prefs->AddFilterToEvent(event_name, extension_id, &filter);
+ }
+ }
+}
+
+void ExtensionEventRouter::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) {
+ ExtensionPrefs* prefs =
+ profile_->GetExtensionService()->extension_prefs();
+ prefs->RemoveFilterFromEvent(event_name, extension_id, &filter);
+ }
+ }
+}
+
bool ExtensionEventRouter::HasEventListener(const std::string& event_name) {
- return (HasEventListenerImpl(listeners_, "", event_name) ||
- HasEventListenerImpl(lazy_listeners_, "", event_name));
+ return listeners_.HasListenerForEvent(event_name);
}
bool ExtensionEventRouter::ExtensionHasEventListener(
const std::string& extension_id, const std::string& event_name) {
- return (HasEventListenerImpl(listeners_, extension_id, event_name) ||
- HasEventListenerImpl(lazy_listeners_, extension_id, event_name));
+ return listeners_.HasListenerForExtension(extension_id, event_name);
}
bool ExtensionEventRouter::HasEventListenerImpl(
@@ -275,13 +290,25 @@ void ExtensionEventRouter::DispatchEventToRenderers(
const std::string& event_name,
const std::string& event_args,
Profile* restrict_to_profile,
- const GURL& event_url) {
+ const GURL& event_url,
+ extensions::EventFilteringInfo info) {
+ DCHECK(!event_args.empty());
+ StringValue event_args_value(event_args);
linked_ptr<ExtensionEvent> event(
- new ExtensionEvent(event_name, event_args, event_url,
- restrict_to_profile, "", USER_GESTURE_UNKNOWN));
+ new ExtensionEvent(event_name, event_args_value, event_url,
+ restrict_to_profile, USER_GESTURE_UNKNOWN, info));
DispatchEventImpl("", event);
}
+void ExtensionEventRouter::DispatchEventToRenderers(
+ const std::string& event_name,
+ const std::string& event_args,
+ Profile* restrict_to_profile,
+ const GURL& event_url) {
+ DispatchEventToRenderers(event_name, event_args, restrict_to_profile,
+ event_url, extensions::EventFilteringInfo());
+}
+
void ExtensionEventRouter::DispatchEventToExtension(
const std::string& extension_id,
const std::string& event_name,
@@ -291,7 +318,8 @@ void ExtensionEventRouter::DispatchEventToExtension(
DCHECK(!extension_id.empty());
linked_ptr<ExtensionEvent> event(
new ExtensionEvent(event_name, event_args, event_url,
- restrict_to_profile, USER_GESTURE_UNKNOWN));
+ restrict_to_profile, USER_GESTURE_UNKNOWN,
+ EventFilteringInfo()));
DispatchEventImpl(extension_id, event);
}
@@ -301,8 +329,8 @@ void ExtensionEventRouter::DispatchEventToExtension(
const std::string& event_args,
Profile* restrict_to_profile,
const GURL& event_url) {
- scoped_ptr<Value> event_args_value(Value::CreateStringValue(event_args));
- DispatchEventToExtension(extension_id, event_name, *event_args_value.get(),
+ StringValue event_args_value(event_args);
+ DispatchEventToExtension(extension_id, event_name, event_args_value,
restrict_to_profile, event_url);
}
@@ -314,9 +342,11 @@ void ExtensionEventRouter::DispatchEventToExtension(
const GURL& event_url,
UserGestureState user_gesture) {
DCHECK(!extension_id.empty());
+ StringValue event_args_value(event_args);
linked_ptr<ExtensionEvent> event(
- new ExtensionEvent(event_name, event_args, event_url,
- restrict_to_profile, "", user_gesture));
+ new ExtensionEvent(event_name, event_args_value, event_url,
+ restrict_to_profile, user_gesture,
+ EventFilteringInfo()));
DispatchEventImpl(extension_id, event);
}
@@ -329,39 +359,57 @@ void ExtensionEventRouter::DispatchEventsToRenderersAcrossIncognito(
linked_ptr<ExtensionEvent> event(
new ExtensionEvent(event_name, event_args, event_url,
restrict_to_profile, cross_incognito_args,
- USER_GESTURE_UNKNOWN));
+ USER_GESTURE_UNKNOWN, EventFilteringInfo()));
DispatchEventImpl("", event);
}
void ExtensionEventRouter::DispatchEventImpl(
- const std::string& extension_id,
+ const std::string& restrict_to_extension_id,
const linked_ptr<ExtensionEvent>& event) {
// We don't expect to get events from a completely different profile.
DCHECK(!event->restrict_to_profile ||
profile_->IsSameProfile(event->restrict_to_profile));
- LoadLazyBackgroundPagesForEvent(extension_id, event);
-
- ListenerMap::iterator it = listeners_.find(event->event_name);
- if (it == listeners_.end())
- return;
-
- std::set<ListenerProcess>& listeners = it->second;
- for (std::set<ListenerProcess>::iterator listener = listeners.begin();
- listener != listeners.end(); ++listener) {
- if (!extension_id.empty() && extension_id != listener->extension_id)
- continue;
+ std::set<const EventListener*> listeners(
+ listeners_.GetEventListeners(*event));
+ for (std::set<const EventListener*>::iterator it = listeners.begin();
+ it != listeners.end(); it++) {
+ const EventListener* listener = *it;
+ if (listener->process) {
+ if (restrict_to_extension_id.empty() ||
+ restrict_to_extension_id == listener->extension_id)
+ DispatchEventToProcess(listener->extension_id, listener->process,
+ event);
+ } else {
+ DispatchLazyEvent(listener->extension_id, event);
+ }
+ }
+}
- DispatchEventToListener(*listener, event);
+void ExtensionEventRouter::DispatchLazyEvent(
+ const std::string& extension_id,
+ const linked_ptr<ExtensionEvent>& event) {
+ ExtensionService* service = profile_->GetExtensionService();
+ // Check both the original and the incognito profile 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) {
+ MaybeLoadLazyBackgroundPageToDispatchEvent(profile_, extension, event);
+ if (profile_->HasOffTheRecordProfile() &&
+ extension->incognito_split_mode()) {
+ MaybeLoadLazyBackgroundPageToDispatchEvent(
+ profile_->GetOffTheRecordProfile(), extension, event);
+ }
}
}
-void ExtensionEventRouter::DispatchEventToListener(
- const ListenerProcess& listener,
+void ExtensionEventRouter::DispatchEventToProcess(
+ const std::string& extension_id,
+ content::RenderProcessHost* process,
const linked_ptr<ExtensionEvent>& event) {
ExtensionService* service = profile_->GetExtensionService();
- const Extension* extension = service->extensions()->GetByID(
- listener.extension_id);
+ 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.
@@ -369,13 +417,13 @@ void ExtensionEventRouter::DispatchEventToListener(
return;
Profile* listener_profile = Profile::FromBrowserContext(
- listener.process->GetBrowserContext());
+ process->GetBrowserContext());
extensions::ProcessMap* process_map =
listener_profile->GetExtensionService()->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(), listener.process->GetID())) {
+ !process_map->Contains(extension->id(), process->GetID())) {
return;
}
@@ -384,9 +432,10 @@ void ExtensionEventRouter::DispatchEventToListener(
event, &event_args))
return;
- DispatchEvent(listener.process, listener.extension_id,
+ DispatchEvent(process, extension_id,
event->event_name, *event_args,
- event->event_url, event->user_gesture);
+ event->event_url, event->user_gesture,
+ event->info);
IncrementInFlightEvents(listener_profile, extension);
}
@@ -413,38 +462,7 @@ bool ExtensionEventRouter::CanDispatchEventToProfile(
return true;
}
-void ExtensionEventRouter::LoadLazyBackgroundPagesForEvent(
- const std::string& extension_id,
- const linked_ptr<ExtensionEvent>& event) {
- ExtensionService* service = profile_->GetExtensionService();
-
- ListenerMap::iterator it = lazy_listeners_.find(event->event_name);
- if (it == lazy_listeners_.end())
- return;
-
- std::set<ListenerProcess>& listeners = it->second;
- for (std::set<ListenerProcess>::iterator listener = listeners.begin();
- listener != listeners.end(); ++listener) {
- if (!extension_id.empty() && extension_id != listener->extension_id)
- continue;
-
- // Check both the original and the incognito profile 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(
- listener->extension_id);
- if (extension) {
- MaybeLoadLazyBackgroundPage(profile_, extension, event);
- if (profile_->HasOffTheRecordProfile() &&
- extension->incognito_split_mode()) {
- MaybeLoadLazyBackgroundPage(
- profile_->GetOffTheRecordProfile(), extension, event);
- }
- }
- }
-}
-
-void ExtensionEventRouter::MaybeLoadLazyBackgroundPage(
+void ExtensionEventRouter::MaybeLoadLazyBackgroundPageToDispatchEvent(
Profile* profile,
const Extension* extension,
const linked_ptr<ExtensionEvent>& event) {
@@ -490,14 +508,15 @@ void ExtensionEventRouter::OnEventAck(
}
void ExtensionEventRouter::DispatchPendingEvent(
- const linked_ptr<ExtensionEvent>& event, ExtensionHost* host) {
+ const linked_ptr<ExtensionEvent>& event,
+ ExtensionHost* host) {
if (!host)
return;
- ListenerProcess listener(host->render_process_host(),
- host->extension()->id());
- if (listeners_[event->event_name].count(listener) > 0u)
- DispatchEventToListener(listener, event);
+ if (listeners_.HasProcessListener(host->render_process_host(),
+ host->extension()->id()))
+ DispatchEventToProcess(host->extension()->id(),
+ host->render_process_host(), event);
}
void ExtensionEventRouter::Observe(
@@ -510,45 +529,30 @@ void ExtensionEventRouter::Observe(
content::RenderProcessHost* renderer =
content::Source<content::RenderProcessHost>(source).ptr();
// Remove all event listeners associated with this renderer.
- for (ListenerMap::iterator it = listeners_.begin();
- it != listeners_.end(); ) {
- ListenerMap::iterator current_it = it++;
- for (std::set<ListenerProcess>::iterator jt =
- current_it->second.begin();
- jt != current_it->second.end(); ) {
- std::set<ListenerProcess>::iterator current_jt = jt++;
- if (current_jt->process == renderer) {
- RemoveEventListener(current_it->first,
- current_jt->process,
- current_jt->extension_id);
- }
- }
- }
+ listeners_.RemoveListenersForProcess(renderer);
break;
}
case chrome::NOTIFICATION_EXTENSION_LOADED: {
// Add all registered lazy listeners to our cache.
const Extension* extension =
content::Details<const Extension>(details).ptr();
+ ExtensionPrefs* prefs =
+ profile_->GetExtensionService()->extension_prefs();
std::set<std::string> registered_events =
- profile_->GetExtensionService()->extension_prefs()->
- GetRegisteredEvents(extension->id());
- ListenerProcess lazy_listener(NULL, extension->id());
- for (std::set<std::string>::iterator it = registered_events.begin();
- it != registered_events.end(); ++it) {
- lazy_listeners_[*it].insert(lazy_listener);
- }
+ prefs->GetRegisteredEvents(extension->id());
+ const DictionaryValue* filtered_events =
+ prefs->GetFilteredEvents(extension->id());
+ if (filtered_events)
+ listeners_.AddLazyListenersFromPreferences(extension->id(),
+ registered_events,
+ *filtered_events);
break;
}
case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
// Remove all registered lazy listeners from our cache.
extensions::UnloadedExtensionInfo* unloaded =
content::Details<extensions::UnloadedExtensionInfo>(details).ptr();
- ListenerProcess lazy_listener(NULL, unloaded->extension->id());
- for (ListenerMap::iterator it = lazy_listeners_.begin();
- it != lazy_listeners_.end(); ++it) {
- it->second.erase(lazy_listener);
- }
+ listeners_.RemoveLazyListenersForExtension(unloaded->extension->id());
break;
}
case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
@@ -565,3 +569,56 @@ void ExtensionEventRouter::Observe(
return;
}
}
+
+ExtensionEvent::ExtensionEvent(
+ const std::string& event_name,
+ const Value& event_args,
+ const GURL& event_url,
+ Profile* restrict_to_profile,
+ const Value& cross_incognito_args,
+ ExtensionEventRouter::UserGestureState user_gesture,
+ const extensions::EventFilteringInfo& info)
+ : event_name(event_name),
+ event_args(event_args.DeepCopy()),
+ event_url(event_url),
+ restrict_to_profile(restrict_to_profile),
+ cross_incognito_args(cross_incognito_args.DeepCopy()),
+ user_gesture(user_gesture),
+ info(info) {
+}
+
+ExtensionEvent::ExtensionEvent(
+ const std::string& event_name,
+ const std::string& event_args,
+ const GURL& event_url,
+ Profile* restrict_to_profile,
+ const std::string& cross_incognito_args,
+ ExtensionEventRouter::UserGestureState user_gesture,
+ const extensions::EventFilteringInfo& info)
+ : event_name(event_name),
+ event_args(Value::CreateStringValue(event_args)),
+ event_url(event_url),
+ restrict_to_profile(restrict_to_profile),
+ cross_incognito_args(Value::CreateStringValue(cross_incognito_args)),
+ user_gesture(user_gesture),
+ info(info) {
+}
+
+ExtensionEvent::ExtensionEvent(
+ const std::string& event_name,
+ const Value& event_args,
+ const GURL& event_url,
+ Profile* restrict_to_profile,
+ ExtensionEventRouter::UserGestureState user_gesture,
+ const extensions::EventFilteringInfo& info)
+ : event_name(event_name),
+ event_args(event_args.DeepCopy()),
+ event_url(event_url),
+ restrict_to_profile(restrict_to_profile),
+ cross_incognito_args(NULL),
+ user_gesture(user_gesture),
+ info(info) {
+}
+
+ExtensionEvent::~ExtensionEvent() {
+}
diff --git a/chrome/browser/extensions/extension_event_router.h b/chrome/browser/extensions/extension_event_router.h
index 4bcfc0e..4afc4a2 100644
--- a/chrome/browser/extensions/extension_event_router.h
+++ b/chrome/browser/extensions/extension_event_router.h
@@ -14,6 +14,8 @@
#include "base/memory/linked_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/values.h"
+#include "chrome/browser/extensions/event_listener_map.h"
+#include "chrome/common/extensions/event_filtering_info.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "ipc/ipc_sender.h"
@@ -31,7 +33,14 @@ namespace extensions {
class Extension;
}
-class ExtensionEventRouter : public content::NotificationObserver {
+struct ExtensionEvent;
+
+using extensions::EventFilteringInfo;
+using extensions::EventListener;
+using extensions::EventListenerMap;
+
+class ExtensionEventRouter : public content::NotificationObserver,
+ public extensions::EventListenerMap::Delegate {
public:
// These constants convey the state of our knowledge of whether we're in
// a user-caused gesture as part of DispatchEvent.
@@ -48,7 +57,8 @@ class ExtensionEventRouter : public content::NotificationObserver {
const std::string& event_name,
const base::Value& event_args,
const GURL& event_url,
- UserGestureState user_gesture);
+ UserGestureState user_gesture,
+ const extensions::EventFilteringInfo& info);
// This invocation is deprecated. All future consumers of this API should be
// sending Values as event arguments, using the above version.
@@ -57,7 +67,8 @@ class ExtensionEventRouter : public content::NotificationObserver {
const std::string& event_name,
const std::string& event_args,
const GURL& event_url,
- UserGestureState user_gesture);
+ UserGestureState user_gesture,
+ const extensions::EventFilteringInfo& info);
explicit ExtensionEventRouter(Profile* profile);
virtual ~ExtensionEventRouter();
@@ -82,6 +93,21 @@ class ExtensionEventRouter : public content::NotificationObserver {
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);
@@ -99,6 +125,14 @@ class ExtensionEventRouter : public content::NotificationObserver {
const std::string& event_name,
const std::string& event_args,
Profile* restrict_to_profile,
+ const GURL& event_url,
+ extensions::EventFilteringInfo info);
+
+ // As above, but defaults |info| to EventFilteringInfo().
+ void DispatchEventToRenderers(
+ const std::string& event_name,
+ const std::string& event_args,
+ Profile* restrict_to_profile,
const GURL& event_url);
// Same as above, except only send the event to the given extension.
@@ -146,9 +180,6 @@ class ExtensionEventRouter : public content::NotificationObserver {
void OnEventAck(Profile* profile, const std::string& extension_id);
private:
- // The details of an event to be dispatched.
- struct ExtensionEvent;
-
// The extension and process that contains the event listener for a given
// event.
struct ListenerProcess;
@@ -168,16 +199,21 @@ class ExtensionEventRouter : public content::NotificationObserver {
const std::string& extension_id,
const std::string& event_name);
- // Shared by DispatchEvent*. If |extension_id| is empty, the event is
- // broadcast. If |process| is non-NULL, the event is only dispatched to that
- // particular process.
+ // 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& extension_id,
+ void DispatchEventImpl(const std::string& restrict_to_extension_id,
+ const linked_ptr<ExtensionEvent>& 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.
+ void DispatchLazyEvent(const std::string& extension_id,
const linked_ptr<ExtensionEvent>& event);
- // Dispatches the event to a single listener process.
- void DispatchEventToListener(const ListenerProcess& listener,
- const linked_ptr<ExtensionEvent>& event);
+ // Dispatches the event to the specified extension running in |process|.
+ void DispatchEventToProcess(const std::string& extension_id,
+ content::RenderProcessHost* process,
+ const linked_ptr<ExtensionEvent>& event);
// Returns false when the event is scoped to a profile and the listening
// extension does not have access to events from that profile. Also fills
@@ -189,17 +225,9 @@ class ExtensionEventRouter : public content::NotificationObserver {
const linked_ptr<ExtensionEvent>& event,
const base::Value** event_args);
- // 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.
- // If |extension_id| is non-empty, we load only that extension's page
- // (assuming it is interested in the event).
- void LoadLazyBackgroundPagesForEvent(
- const std::string& extension_id,
- const linked_ptr<ExtensionEvent>& event);
-
// Possibly loads given extension's background page in preparation to
// dispatch an event.
- void MaybeLoadLazyBackgroundPage(
+ void MaybeLoadLazyBackgroundPageToDispatchEvent(
Profile* profile,
const extensions::Extension* extension,
const linked_ptr<ExtensionEvent>& event);
@@ -212,21 +240,57 @@ class ExtensionEventRouter : public content::NotificationObserver {
void DispatchPendingEvent(const linked_ptr<ExtensionEvent>& event,
ExtensionHost* host);
+ // Implementation of extensions::EventListenerMap::Delegate.
+ virtual void OnListenerAdded(const EventListener* listener) OVERRIDE;
+ virtual void OnListenerRemoved(const EventListener* listener) OVERRIDE;
+
Profile* profile_;
content::NotificationRegistrar registrar_;
scoped_refptr<ExtensionDevToolsManager> extension_devtools_manager_;
- // The list of active extension processes that are listening to events.
- ListenerMap listeners_;
-
- // The list of all the lazy (non-persistent) background pages that are
- // listening to events. This is just a cache of the real list, which is
- // stored on disk in the extension prefs.
- ListenerMap lazy_listeners_;
+ EventListenerMap listeners_;
DISALLOW_COPY_AND_ASSIGN(ExtensionEventRouter);
};
+struct ExtensionEvent {
+ std::string event_name;
+ scoped_ptr<Value> event_args;
+ GURL event_url;
+ Profile* restrict_to_profile;
+ scoped_ptr<Value> cross_incognito_args;
+ ExtensionEventRouter::UserGestureState user_gesture;
+ extensions::EventFilteringInfo info;
+
+ ExtensionEvent(const std::string& event_name,
+ const Value& event_args,
+ const GURL& event_url,
+ Profile* restrict_to_profile,
+ const Value& cross_incognito_args,
+ ExtensionEventRouter::UserGestureState user_gesture,
+ const extensions::EventFilteringInfo& info);
+
+ // TODO(gdk): This variant should be retired once the callers are switched to
+ // providing Values instead of just strings.
+ ExtensionEvent(const std::string& event_name,
+ const std::string& event_args,
+ const GURL& event_url,
+ Profile* restrict_to_profile,
+ const std::string& cross_incognito_args,
+ ExtensionEventRouter::UserGestureState user_gesture,
+ const extensions::EventFilteringInfo& info);
+
+ ExtensionEvent(const std::string& event_name,
+ const Value& event_args,
+ const GURL& event_url,
+ Profile* restrict_to_profile,
+ ExtensionEventRouter::UserGestureState user_gesture,
+ const extensions::EventFilteringInfo& info);
+
+ ~ExtensionEvent();
+};
+
+
#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_EVENT_ROUTER_H_
diff --git a/chrome/browser/extensions/extension_event_router_forwarder.cc b/chrome/browser/extensions/extension_event_router_forwarder.cc
index f654d12..93b8c66 100644
--- a/chrome/browser/extensions/extension_event_router_forwarder.cc
+++ b/chrome/browser/extensions/extension_event_router_forwarder.cc
@@ -114,7 +114,8 @@ void ExtensionEventRouterForwarder::CallExtensionEventRouter(
if (extension_id.empty()) {
profile->GetExtensionEventRouter()->
DispatchEventToRenderers(
- event_name, event_args, restrict_to_profile, event_url);
+ event_name, event_args, restrict_to_profile, event_url,
+ extensions::EventFilteringInfo());
} else {
profile->GetExtensionEventRouter()->
DispatchEventToExtension(
diff --git a/chrome/browser/extensions/extension_idle_api.cc b/chrome/browser/extensions/extension_idle_api.cc
index 8df6ac8..87ce764 100644
--- a/chrome/browser/extensions/extension_idle_api.cc
+++ b/chrome/browser/extensions/extension_idle_api.cc
@@ -145,7 +145,7 @@ void ExtensionIdleEventRouter::OnIdleStateChange(Profile* profile,
base::JSONWriter::Write(&args, &json_args);
profile->GetExtensionEventRouter()->DispatchEventToRenderers(
- keys::kOnStateChanged, json_args, profile, GURL());
+ keys::kOnStateChanged, json_args, profile, GURL(), EventFilteringInfo());
}
bool ExtensionIdleQueryStateFunction::RunImpl() {
diff --git a/chrome/browser/extensions/extension_managed_mode_api.cc b/chrome/browser/extensions/extension_managed_mode_api.cc
index efa05f8..11e9f6f 100644
--- a/chrome/browser/extensions/extension_managed_mode_api.cc
+++ b/chrome/browser/extensions/extension_managed_mode_api.cc
@@ -66,8 +66,9 @@ void ExtensionManagedModeEventRouter::Observe(
std::string json_args;
base::JSONWriter::Write(&args, &json_args);
ExtensionEventRouter* event_router = profile_->GetExtensionEventRouter();
- event_router->DispatchEventToRenderers(kChangeEventName, json_args,
- NULL, GURL());
+ event_router->DispatchEventToRenderers(kChangeEventName, json_args, NULL,
+ GURL(),
+ extensions::EventFilteringInfo());
}
GetManagedModeFunction::~GetManagedModeFunction() { }
diff --git a/chrome/browser/extensions/extension_management_api.cc b/chrome/browser/extensions/extension_management_api.cc
index dcc0274..35008a2 100644
--- a/chrome/browser/extensions/extension_management_api.cc
+++ b/chrome/browser/extensions/extension_management_api.cc
@@ -588,5 +588,5 @@ void ExtensionManagementEventRouter::Observe(
base::JSONWriter::Write(&args, &args_json);
profile->GetExtensionEventRouter()->DispatchEventToRenderers(
- event_name, args_json, NULL, GURL());
+ event_name, args_json, NULL, GURL(), extensions::EventFilteringInfo());
}
diff --git a/chrome/browser/extensions/extension_messages_apitest.cc b/chrome/browser/extensions/extension_messages_apitest.cc
index afb94ca..a61882d 100644
--- a/chrome/browser/extensions/extension_messages_apitest.cc
+++ b/chrome/browser/extensions/extension_messages_apitest.cc
@@ -31,19 +31,23 @@ class MessageSender : public content::NotificationObserver {
event_router->DispatchEventToRenderers("test.onMessage",
"[{\"lastMessage\":false,\"data\":\"no restriction\"}]",
content::Source<Profile>(source).ptr(),
- GURL());
+ GURL(),
+ EventFilteringInfo());
event_router->DispatchEventToRenderers("test.onMessage",
"[{\"lastMessage\":false,\"data\":\"http://a.com/\"}]",
content::Source<Profile>(source).ptr(),
- GURL("http://a.com/"));
+ GURL("http://a.com/"),
+ EventFilteringInfo());
event_router->DispatchEventToRenderers("test.onMessage",
"[{\"lastMessage\":false,\"data\":\"http://b.com/\"}]",
content::Source<Profile>(source).ptr(),
- GURL("http://b.com/"));
+ GURL("http://b.com/"),
+ EventFilteringInfo());
event_router->DispatchEventToRenderers("test.onMessage",
"[{\"lastMessage\":true,\"data\":\"last message\"}]",
content::Source<Profile>(source).ptr(),
- GURL());
+ GURL(),
+ EventFilteringInfo());
}
content::NotificationRegistrar registrar_;
diff --git a/chrome/browser/extensions/extension_prefs.cc b/chrome/browser/extensions/extension_prefs.cc
index 9337830..fc523325 100644
--- a/chrome/browser/extensions/extension_prefs.cc
+++ b/chrome/browser/extensions/extension_prefs.cc
@@ -173,6 +173,10 @@ const char kPrefIncognitoContentSettings[] = "incognito_content_settings";
// background page.
const char kRegisteredEvents[] = "events";
+// A dictionary of event names to lists of filters that this extension has
+// registered from its lazy background page.
+const char kFilteredEvents[] = "filtered_events";
+
// Persisted value for omnibox.setDefaultSuggestion.
const char kOmniboxDefaultSuggestion[] = "omnibox_default_suggestion";
@@ -957,6 +961,59 @@ std::set<std::string> ExtensionPrefs::GetRegisteredEvents(
return events;
}
+void ExtensionPrefs::AddFilterToEvent(const std::string& event_name,
+ const std::string& extension_id,
+ const DictionaryValue* filter) {
+ ScopedExtensionPrefUpdate update(prefs_, extension_id);
+ DictionaryValue* extension_dict = update.Get();
+ DictionaryValue* filtered_events = NULL;
+ if (!extension_dict->GetDictionary(kFilteredEvents, &filtered_events)) {
+ filtered_events = new DictionaryValue;
+ extension_dict->Set(kFilteredEvents, filtered_events);
+ }
+ ListValue* filter_list = NULL;
+ if (!filtered_events->GetList(event_name, &filter_list)) {
+ filter_list = new ListValue;
+ filtered_events->Set(event_name, filter_list);
+ }
+
+ filter_list->Append(filter->DeepCopy());
+}
+
+void ExtensionPrefs::RemoveFilterFromEvent(const std::string& event_name,
+ const std::string& extension_id,
+ const DictionaryValue* filter) {
+ ScopedExtensionPrefUpdate update(prefs_, extension_id);
+ DictionaryValue* extension_dict = update.Get();
+ DictionaryValue* filtered_events = NULL;
+
+ if (!extension_dict->GetDictionary(kFilteredEvents, &filtered_events))
+ return;
+ ListValue* filter_list = NULL;
+ if (!filtered_events->GetList(event_name, &filter_list))
+ return;
+
+ for (size_t i = 0; i < filter_list->GetSize(); i++) {
+ DictionaryValue* filter;
+ CHECK(filter_list->GetDictionary(i, &filter));
+ if (filter->Equals(filter)) {
+ filter_list->Remove(i, NULL);
+ break;
+ }
+ }
+}
+
+const DictionaryValue* ExtensionPrefs::GetFilteredEvents(
+ const std::string& extension_id) const {
+ const DictionaryValue* extension = GetExtensionPref(extension_id);
+ if (!extension)
+ return NULL;
+ DictionaryValue* result = NULL;
+ if (!extension->GetDictionary(kFilteredEvents, &result))
+ return NULL;
+ return result;
+}
+
void ExtensionPrefs::SetRegisteredEvents(
const std::string& extension_id, const std::set<std::string>& events) {
ListValue* value = new ListValue();
diff --git a/chrome/browser/extensions/extension_prefs.h b/chrome/browser/extensions/extension_prefs.h
index ae05fd9..0efe0b4 100644
--- a/chrome/browser/extensions/extension_prefs.h
+++ b/chrome/browser/extensions/extension_prefs.h
@@ -275,6 +275,21 @@ class ExtensionPrefs : public extensions::ContentSettingsStore::Observer,
void SetRegisteredEvents(const std::string& extension_id,
const std::set<std::string>& events);
+ // Adds a filter to an event.
+ void AddFilterToEvent(const std::string& event_name,
+ const std::string& extension_id,
+ const DictionaryValue* filter);
+
+ // Removes a filter from an event.
+ void RemoveFilterFromEvent(const std::string& event_name,
+ const std::string& extension_id,
+ const DictionaryValue* filter);
+
+ // Returns the dictionary of event filters that the given extension has
+ // registered.
+ const DictionaryValue* GetFilteredEvents(
+ const std::string& extension_id) const;
+
// Controls the omnibox default suggestion as set by the extension.
extensions::ExtensionOmniboxSuggestion GetOmniboxDefaultSuggestion(
const std::string& extension_id);
diff --git a/chrome/browser/extensions/extension_processes_api.cc b/chrome/browser/extensions/extension_processes_api.cc
index 4607c50..c853495 100644
--- a/chrome/browser/extensions/extension_processes_api.cc
+++ b/chrome/browser/extensions/extension_processes_api.cc
@@ -489,7 +489,7 @@ void ExtensionProcessesEventRouter::DispatchEvent(
const std::string& json_args) {
if (profile && profile->GetExtensionEventRouter()) {
profile->GetExtensionEventRouter()->DispatchEventToRenderers(
- event_name, json_args, NULL, GURL());
+ event_name, json_args, NULL, GURL(), EventFilteringInfo());
}
}
diff --git a/chrome/browser/extensions/lazy_background_page_apitest.cc b/chrome/browser/extensions/lazy_background_page_apitest.cc
index 5441af6..2c589d0 100644
--- a/chrome/browser/extensions/lazy_background_page_apitest.cc
+++ b/chrome/browser/extensions/lazy_background_page_apitest.cc
@@ -166,6 +166,22 @@ IN_PROC_BROWSER_TEST_F(LazyBackgroundPageApiTest, BroadcastEvent) {
GetLocationBarForTesting()->PageActionVisibleCount());
}
+IN_PROC_BROWSER_TEST_F(LazyBackgroundPageApiTest, Filters) {
+ const Extension* extension = LoadExtensionAndWait("filters");
+ ASSERT_TRUE(extension);
+
+ // Lazy Background Page doesn't exist yet.
+ ExtensionProcessManager* pm =
+ browser()->profile()->GetExtensionProcessManager();
+ EXPECT_FALSE(pm->GetBackgroundHostForExtension(last_loaded_extension_id_));
+
+ // Open a tab to a URL that will fire a webNavigation event.
+ LazyBackgroundObserver page_complete;
+ ui_test_utils::NavigateToURL(
+ browser(), test_server()->GetURL("files/extensions/test_file.html"));
+ page_complete.Wait();
+}
+
// Tests that the lazy background page receives the onInstalled event and shuts
// down.
IN_PROC_BROWSER_TEST_F(LazyBackgroundPageApiTest, OnInstalled) {
diff --git a/chrome/browser/extensions/system/system_api.cc b/chrome/browser/extensions/system/system_api.cc
index b80538f..042eb35 100644
--- a/chrome/browser/extensions/system/system_api.cc
+++ b/chrome/browser/extensions/system/system_api.cc
@@ -60,7 +60,7 @@ void DispatchEvent(const std::string& event_name, const ListValue& args) {
std::string json_args;
base::JSONWriter::Write(&args, &json_args);
extension_event_router->DispatchEventToRenderers(
- event_name, json_args, NULL, GURL());
+ event_name, json_args, NULL, GURL(), extensions::EventFilteringInfo());
}
} // namespace
diff --git a/chrome/browser/history/history_extension_api.cc b/chrome/browser/history/history_extension_api.cc
index b020762..a55d095 100644
--- a/chrome/browser/history/history_extension_api.cc
+++ b/chrome/browser/history/history_extension_api.cc
@@ -163,7 +163,8 @@ void HistoryExtensionEventRouter::DispatchEvent(Profile* profile,
const std::string& json_args) {
if (profile && profile->GetExtensionEventRouter()) {
profile->GetExtensionEventRouter()->DispatchEventToRenderers(
- event_name, json_args, profile, GURL());
+ event_name, json_args, profile, GURL(),
+ extensions::EventFilteringInfo());
}
}
diff --git a/chrome/browser/renderer_host/chrome_render_message_filter.cc b/chrome/browser/renderer_host/chrome_render_message_filter.cc
index 722fb1b..6207a9c 100644
--- a/chrome/browser/renderer_host/chrome_render_message_filter.cc
+++ b/chrome/browser/renderer_host/chrome_render_message_filter.cc
@@ -93,6 +93,10 @@ bool ChromeRenderMessageFilter::OnMessageReceived(const IPC::Message& message,
OnExtensionAddLazyListener)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_RemoveLazyListener,
OnExtensionRemoveLazyListener)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_AddFilteredListener,
+ OnExtensionAddFilteredListener)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_RemoveFilteredListener,
+ OnExtensionRemoveFilteredListener)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_CloseChannel, OnExtensionCloseChannel)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_RequestForIOThread,
OnExtensionRequestForIOThread)
@@ -143,6 +147,8 @@ void ChromeRenderMessageFilter::OverrideThreadForMessage(
case ExtensionHostMsg_RemoveListener::ID:
case ExtensionHostMsg_AddLazyListener::ID:
case ExtensionHostMsg_RemoveLazyListener::ID:
+ case ExtensionHostMsg_AddFilteredListener::ID:
+ case ExtensionHostMsg_RemoveFilteredListener::ID:
case ExtensionHostMsg_CloseChannel::ID:
case ExtensionHostMsg_ShouldUnloadAck::ID:
case ExtensionHostMsg_UnloadAck::ID:
@@ -378,6 +384,34 @@ void ChromeRenderMessageFilter::OnExtensionRemoveLazyListener(
event_name, extension_id);
}
+void ChromeRenderMessageFilter::OnExtensionAddFilteredListener(
+ const std::string& extension_id,
+ const std::string& event_name,
+ const base::DictionaryValue& filter,
+ bool lazy) {
+ content::RenderProcessHost* process =
+ content::RenderProcessHost::FromID(render_process_id_);
+ if (!process || !profile_->GetExtensionEventRouter())
+ return;
+
+ profile_->GetExtensionEventRouter()->AddFilteredEventListener(
+ event_name, process, extension_id, filter, lazy);
+}
+
+void ChromeRenderMessageFilter::OnExtensionRemoveFilteredListener(
+ const std::string& extension_id,
+ const std::string& event_name,
+ const base::DictionaryValue& filter,
+ bool lazy) {
+ content::RenderProcessHost* process =
+ content::RenderProcessHost::FromID(render_process_id_);
+ if (!process || !profile_->GetExtensionEventRouter())
+ return;
+
+ profile_->GetExtensionEventRouter()->RemoveFilteredEventListener(
+ event_name, process, extension_id, filter, lazy);
+}
+
void ChromeRenderMessageFilter::OnExtensionCloseChannel(int port_id,
bool connection_error) {
if (!content::RenderProcessHost::FromID(render_process_id_))
diff --git a/chrome/browser/renderer_host/chrome_render_message_filter.h b/chrome/browser/renderer_host/chrome_render_message_filter.h
index af0b82b..2dd34ac 100644
--- a/chrome/browser/renderer_host/chrome_render_message_filter.h
+++ b/chrome/browser/renderer_host/chrome_render_message_filter.h
@@ -117,6 +117,14 @@ class ChromeRenderMessageFilter : public content::BrowserMessageFilter {
const std::string& event_name);
void OnExtensionRemoveLazyListener(const std::string& extension_id,
const std::string& event_name);
+ void OnExtensionAddFilteredListener(const std::string& extension_id,
+ const std::string& event_name,
+ const base::DictionaryValue& filter,
+ bool lazy);
+ void OnExtensionRemoveFilteredListener(const std::string& extension_id,
+ const std::string& event_name,
+ const base::DictionaryValue& filter,
+ bool lazy);
void OnExtensionCloseChannel(int port_id, bool connection_error);
void OnExtensionRequestForIOThread(
int routing_id,
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index 4810c34..c470c38 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -258,6 +258,8 @@
'browser/extensions/default_apps.h',
'browser/extensions/default_apps_trial.cc',
'browser/extensions/default_apps_trial.h',
+ 'browser/extensions/event_listener_map.cc',
+ 'browser/extensions/event_listener_map.h',
'browser/extensions/extension_activity_log.cc',
'browser/extensions/extension_activity_log.h',
'browser/extensions/extension_browser_event_router.cc',
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi
index 48021e0..5b608c3 100644
--- a/chrome/chrome_common.gypi
+++ b/chrome/chrome_common.gypi
@@ -185,6 +185,8 @@
'common/extensions/url_pattern_set.h',
'common/extensions/user_script.cc',
'common/extensions/user_script.h',
+ 'common/extensions/value_counter.cc',
+ 'common/extensions/value_counter.h',
'common/extensions/api/extension_api.cc',
'common/extensions/api/extension_api.h',
'common/external_ipc_fuzzer.h',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 3cd41c7..687a53c 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1216,6 +1216,7 @@
'browser/extensions/component_loader_unittest.cc',
'browser/extensions/convert_user_script_unittest.cc',
'browser/extensions/convert_web_app_unittest.cc',
+ 'browser/extensions/event_listener_map_unittest.cc',
'browser/extensions/extension_creator_filter_unittest.cc',
'browser/extensions/extension_event_router_forwarder_unittest.cc',
'browser/extensions/extension_function_test_utils.cc',
@@ -1916,6 +1917,7 @@
'common/extensions/url_pattern_set_unittest.cc',
'common/extensions/url_pattern_unittest.cc',
'common/extensions/user_script_unittest.cc',
+ 'common/extensions/value_counter_unittest.cc',
'common/extensions/api/extension_api_unittest.cc',
'common/important_file_writer_unittest.cc',
'common/json_pref_store_unittest.cc',
diff --git a/chrome/common/extensions/api/web_navigation.json b/chrome/common/extensions/api/web_navigation.json
index cbfc720..5534297 100644
--- a/chrome/common/extensions/api/web_navigation.json
+++ b/chrome/common/extensions/api/web_navigation.json
@@ -91,6 +91,11 @@
"name": "onBeforeNavigate",
"type": "function",
"description": "Fired when a navigation is about to occur.",
+ "options": {
+ "supportsFilters": true,
+ "supportsListeners": true,
+ "supportsRules": false
+ },
"parameters": [
{
"type": "object",
@@ -108,6 +113,11 @@
"name": "onCommitted",
"type": "function",
"description": "Fired when a navigation is committed. The document (and the resources it refers to, such as images and subframes) might still be downloading, but at least part of the document has been received from the server and the browser has decided to switch to the new document.",
+ "options": {
+ "supportsFilters": true,
+ "supportsListeners": true,
+ "supportsRules": false
+ },
"parameters": [
{
"type": "object",
@@ -127,6 +137,11 @@
"name": "onDOMContentLoaded",
"type": "function",
"description": "Fired when the page's DOM is fully constructed, but the referenced resources may not finish loading.",
+ "options": {
+ "supportsFilters": true,
+ "supportsListeners": true,
+ "supportsRules": false
+ },
"parameters": [
{
"type": "object",
@@ -144,6 +159,11 @@
"name": "onCompleted",
"type": "function",
"description": "Fired when a document, including the resources it refers to, is completely loaded and initialized.",
+ "options": {
+ "supportsFilters": true,
+ "supportsListeners": true,
+ "supportsRules": false
+ },
"parameters": [
{
"type": "object",
@@ -161,6 +181,11 @@
"name": "onErrorOccurred",
"type": "function",
"description": "Fired when an error occurs and the navigation is aborted. This can happen if either a network error occurred, or the user aborted the navigation.",
+ "options": {
+ "supportsFilters": true,
+ "supportsListeners": true,
+ "supportsRules": false
+ },
"parameters": [
{
"type": "object",
@@ -179,6 +204,11 @@
"name": "onCreatedNavigationTarget",
"type": "function",
"description": "Fired when a new window, or a new tab in an existing window, is created to host a navigation.",
+ "options": {
+ "supportsFilters": true,
+ "supportsListeners": true,
+ "supportsRules": false
+ },
"parameters": [
{
"type": "object",
@@ -197,6 +227,11 @@
"name": "onReferenceFragmentUpdated",
"type": "function",
"description": "Fired when the reference fragment of a frame was updated. All future events for that frame will use the updated URL.",
+ "options": {
+ "supportsFilters": true,
+ "supportsListeners": true,
+ "supportsRules": false
+ },
"parameters": [
{
"type": "object",
diff --git a/chrome/common/extensions/event_filtering_info.cc b/chrome/common/extensions/event_filtering_info.cc
index 1b61399..1e30283 100644
--- a/chrome/common/extensions/event_filtering_info.cc
+++ b/chrome/common/extensions/event_filtering_info.cc
@@ -31,4 +31,18 @@ std::string EventFilteringInfo::AsJSONString() const {
return result;
}
+scoped_ptr<base::Value> EventFilteringInfo::AsValue() const {
+ if (IsEmpty())
+ return scoped_ptr<base::Value>(base::Value::CreateNullValue());
+
+ scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue);
+ if (has_url_)
+ result->SetString("url", url_.spec());
+ return result.PassAs<base::Value>();
+}
+
+bool EventFilteringInfo::IsEmpty() const {
+ return !has_url_;
+}
+
} // namespace extensions
diff --git a/chrome/common/extensions/event_filtering_info.h b/chrome/common/extensions/event_filtering_info.h
index 89d4d32..b137c56 100644
--- a/chrome/common/extensions/event_filtering_info.h
+++ b/chrome/common/extensions/event_filtering_info.h
@@ -6,7 +6,13 @@
#define CHROME_COMMON_EXTENSIONS_EVENT_FILTERING_INFO_H_
#pragma once
+#include "base/memory/scoped_ptr.h"
#include "googleurl/src/gurl.h"
+#include "v8/include/v8.h"
+
+namespace base {
+class Value;
+}
namespace extensions {
@@ -28,6 +34,8 @@ class EventFilteringInfo {
const GURL& url() const { return url_; }
std::string AsJSONString() const;
+ scoped_ptr<base::Value> AsValue() const;
+ bool IsEmpty() const;
private:
bool has_url_;
diff --git a/chrome/common/extensions/extension_messages.h b/chrome/common/extensions/extension_messages.h
index ae2b7f9..3e1c113 100644
--- a/chrome/common/extensions/extension_messages.h
+++ b/chrome/common/extensions/extension_messages.h
@@ -367,6 +367,22 @@ IPC_MESSAGE_CONTROL2(ExtensionHostMsg_RemoveLazyListener,
std::string /* extension_id */,
std::string /* name */)
+// Notify the browser that the given extension added a listener to instances of
+// the named event that satisfy the filter.
+IPC_MESSAGE_CONTROL4(ExtensionHostMsg_AddFilteredListener,
+ std::string /* extension_id */,
+ std::string /* name */,
+ DictionaryValue /* filter */,
+ bool /* lazy */)
+
+// Notify the browser that the given extension is no longer interested in
+// instances of the named event that satisfy the filter.
+IPC_MESSAGE_CONTROL4(ExtensionHostMsg_RemoveFilteredListener,
+ std::string /* extension_id */,
+ std::string /* name */,
+ DictionaryValue /* filter */,
+ bool /* lazy */)
+
// Notify the browser that an event has finished being dispatched.
IPC_MESSAGE_ROUTED0(ExtensionHostMsg_EventAck)
diff --git a/chrome/common/extensions/value_counter.cc b/chrome/common/extensions/value_counter.cc
new file mode 100644
index 0000000..156e3ad
--- /dev/null
+++ b/chrome/common/extensions/value_counter.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/value_counter.h"
+
+#include "base/values.h"
+
+#include <algorithm>
+
+namespace extensions {
+
+ValueCounter::ValueCounter() {
+}
+
+ValueCounter::~ValueCounter() {
+}
+
+ValueCounter::Entry::Entry(const base::Value& value)
+ : value_(value.DeepCopy()),
+ count_(1) {
+}
+
+ValueCounter::Entry::~Entry() {
+}
+
+int ValueCounter::Entry::Increment() {
+ return ++count_;
+}
+
+int ValueCounter::Entry::Decrement() {
+ return --count_;
+}
+
+int ValueCounter::Add(const base::Value& value) {
+ return AddImpl(value, true);
+}
+
+int ValueCounter::Remove(const base::Value& value) {
+ for (EntryList::iterator it = entries_.begin(); it != entries_.end(); it++) {
+ (*it)->value()->GetType();
+ if ((*it)->value()->Equals(&value)) {
+ int remaining = (*it)->Decrement();
+ if (remaining == 0) {
+ std::swap(*it, entries_.back());
+ entries_.pop_back();
+ }
+ return remaining;
+ }
+ }
+ return 0;
+}
+
+int ValueCounter::AddIfMissing(const base::Value& value) {
+ return AddImpl(value, false);
+}
+
+int ValueCounter::AddImpl(const base::Value& value, bool increment) {
+ for (EntryList::iterator it = entries_.begin(); it != entries_.end(); it++) {
+ if ((*it)->value()->Equals(&value))
+ return increment ? (*it)->Increment() : (*it)->count();
+ }
+ entries_.push_back(linked_ptr<Entry>(new Entry(value)));
+ return 1;
+}
+
+} // namespace extensions
diff --git a/chrome/common/extensions/value_counter.h b/chrome/common/extensions/value_counter.h
new file mode 100644
index 0000000..52c719c
--- /dev/null
+++ b/chrome/common/extensions/value_counter.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EXTENSIONS_VALUE_COUNTER_H_
+#define CHROME_COMMON_EXTENSIONS_VALUE_COUNTER_H_
+#pragma once
+
+#include "base/memory/linked_ptr.h"
+
+#include <vector>
+
+namespace base {
+class Value;
+}
+
+namespace extensions {
+
+// Keeps a running count of Values, like map<Value, int>. Adding / removing
+// values increments / decrements the count associated with a given Value.
+//
+// Add() and Remove() are linear in the number of Values in the ValueCounter,
+// because there is no operator<() defined on Value, so we must iterate to find
+// whether a Value is equal to an existing one.
+class ValueCounter {
+ public:
+ ValueCounter();
+ ~ValueCounter();
+
+ // Adds |value| to the set and returns how many equal values are in the set
+ // after. Does not take ownership of |value|. In the case where a Value equal
+ // to |value| doesn't already exist in this map, this function makes a
+ // DeepCopy() of |value|.
+ int Add(const base::Value& value);
+
+ // Removes |value| from the set and returns how many equal values are in
+ // the set after.
+ int Remove(const base::Value& value);
+
+ // Same as Add() but only performs the add if the value isn't present.
+ int AddIfMissing(const base::Value& value);
+
+ private:
+ class Entry {
+ public:
+ explicit Entry(const base::Value& value);
+ ~Entry();
+
+ int Increment();
+ int Decrement();
+
+ const base::Value* value() const { return value_.get(); }
+ int count() const { return count_; }
+
+ private:
+ linked_ptr<base::Value> value_;
+ int count_;
+
+ DISALLOW_COPY_AND_ASSIGN(Entry);
+ };
+ typedef std::vector<linked_ptr<Entry> > EntryList;
+
+ int AddImpl(const base::Value& value, bool increment);
+
+ EntryList entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(ValueCounter);
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_VALUE_COUNTER_H_
diff --git a/chrome/common/extensions/value_counter_unittest.cc b/chrome/common/extensions/value_counter_unittest.cc
new file mode 100644
index 0000000..6ad7454
--- /dev/null
+++ b/chrome/common/extensions/value_counter_unittest.cc
@@ -0,0 +1,42 @@
+// 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 "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chrome/common/extensions/value_counter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class ValueCounterUnittest : public testing::Test {
+};
+
+TEST_F(ValueCounterUnittest, TestAddingSameValue) {
+ extensions::ValueCounter vc;
+ base::ListValue value;
+ ASSERT_EQ(1, vc.Add(value));
+ ASSERT_EQ(2, vc.Add(value));
+}
+
+TEST_F(ValueCounterUnittest, TestAddingDifferentValue) {
+ extensions::ValueCounter vc;
+ base::ListValue value1;
+ base::DictionaryValue value2;
+ ASSERT_EQ(1, vc.Add(value1));
+ ASSERT_EQ(1, vc.Add(value2));
+}
+
+TEST_F(ValueCounterUnittest, TestRemovingValue) {
+ extensions::ValueCounter vc;
+ base::ListValue value;
+ ASSERT_EQ(1, vc.Add(value));
+ ASSERT_EQ(2, vc.Add(value));
+ ASSERT_EQ(1, vc.Remove(value));
+ ASSERT_EQ(0, vc.Remove(value));
+}
+
+TEST_F(ValueCounterUnittest, TestAddIfMissing) {
+ extensions::ValueCounter vc;
+ base::ListValue value;
+ ASSERT_EQ(1, vc.AddIfMissing(value));
+ ASSERT_EQ(1, vc.AddIfMissing(value));
+}
diff --git a/chrome/renderer/extensions/event_bindings.cc b/chrome/renderer/extensions/event_bindings.cc
index 94a0203..06d9dbc 100644
--- a/chrome/renderer/extensions/event_bindings.cc
+++ b/chrome/renderer/extensions/event_bindings.cc
@@ -6,11 +6,15 @@
#include <vector>
+#include "base/bind.h"
#include "base/basictypes.h"
#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "chrome/common/extensions/extension_messages.h"
#include "chrome/common/extensions/extension_set.h"
+#include "chrome/common/extensions/event_filter.h"
+#include "chrome/common/extensions/value_counter.h"
#include "chrome/common/url_constants.h"
#include "chrome/common/view_type.h"
#include "chrome/renderer/extensions/chrome_v8_context.h"
@@ -21,6 +25,7 @@
#include "chrome/renderer/extensions/extension_helper.h"
#include "chrome/renderer/extensions/user_script_slave.h"
#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/v8_value_converter.h"
#include "googleurl/src/gurl.h"
#include "grit/renderer_resources.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
@@ -48,33 +53,57 @@ typedef std::map<std::string, int> EventListenerCounts;
base::LazyInstance<std::map<std::string, EventListenerCounts> >
g_listener_counts = LAZY_INSTANCE_INITIALIZER;
+// A map of event names to a (filter -> count) map. The map is used to keep
+// track of which filters are in effect for which events.
+// We notify the browser about filtered event listeners when we transition
+// between 0 and 1.
+typedef std::map<std::string, linked_ptr<extensions::ValueCounter> >
+ FilteredEventListenerCounts;
+
+// A map of extension IDs to filtered listener counts for that extension.
+base::LazyInstance<std::map<std::string, FilteredEventListenerCounts> >
+ g_filtered_listener_counts = LAZY_INSTANCE_INITIALIZER;
+
// TODO(koz): Merge this into EventBindings.
class ExtensionImpl : public ChromeV8Extension {
public:
- explicit ExtensionImpl(ExtensionDispatcher* dispatcher)
- : ChromeV8Extension(dispatcher) {
- RouteStaticFunction("AttachEvent", &AttachEvent);
- RouteStaticFunction("DetachEvent", &DetachEvent);
+ ExtensionImpl(ExtensionDispatcher* dispatcher,
+ extensions::EventFilter* event_filter)
+ : ChromeV8Extension(dispatcher),
+ event_filter_(event_filter) {
+ RouteFunction("AttachEvent",
+ base::Bind(&ExtensionImpl::AttachEvent,
+ base::Unretained(this)));
+ RouteFunction("DetachEvent",
+ base::Bind(&ExtensionImpl::DetachEvent,
+ base::Unretained(this)));
+ RouteFunction("AttachFilteredEvent",
+ base::Bind(&ExtensionImpl::AttachFilteredEvent,
+ base::Unretained(this)));
+ RouteFunction("DetachFilteredEvent",
+ base::Bind(&ExtensionImpl::DetachFilteredEvent,
+ base::Unretained(this)));
+ RouteFunction("MatchAgainstEventFilter",
+ base::Bind(&ExtensionImpl::MatchAgainstEventFilter,
+ base::Unretained(this)));
}
~ExtensionImpl() {}
// Attach an event name to an object.
- static v8::Handle<v8::Value> AttachEvent(const v8::Arguments& args) {
+ v8::Handle<v8::Value> AttachEvent(const v8::Arguments& args) {
DCHECK(args.Length() == 1);
// TODO(erikkay) should enforce that event name is a string in the bindings
DCHECK(args[0]->IsString() || args[0]->IsUndefined());
if (args[0]->IsString()) {
- ExtensionImpl* self = GetFromArguments<ExtensionImpl>(args);
+ std::string event_name = *v8::String::AsciiValue(args[0]->ToString());
const ChromeV8ContextSet& context_set =
- self->extension_dispatcher()->v8_context_set();
+ extension_dispatcher()->v8_context_set();
ChromeV8Context* context = context_set.GetCurrent();
CHECK(context);
- std::string event_name(*v8::String::AsciiValue(args[0]));
- ExtensionDispatcher* extension_dispatcher = self->extension_dispatcher();
- if (!extension_dispatcher->CheckCurrentContextAccessToExtensionAPI(
+ if (!extension_dispatcher()->CheckCurrentContextAccessToExtensionAPI(
event_name))
return v8::Undefined();
@@ -89,24 +118,25 @@ class ExtensionImpl : public ChromeV8Extension {
// This is called the first time the page has added a listener. Since
// the background page is the only lazy page, we know this is the first
// time this listener has been registered.
- if (self->IsLazyBackgroundPage(context->extension())) {
+ if (IsLazyBackgroundPage(context->extension())) {
content::RenderThread::Get()->Send(
new ExtensionHostMsg_AddLazyListener(extension_id, event_name));
}
}
-
return v8::Undefined();
}
- static v8::Handle<v8::Value> DetachEvent(const v8::Arguments& args) {
+ v8::Handle<v8::Value> DetachEvent(const v8::Arguments& args) {
DCHECK(args.Length() == 2);
// TODO(erikkay) should enforce that event name is a string in the bindings
DCHECK(args[0]->IsString() || args[0]->IsUndefined());
if (args[0]->IsString() && args[1]->IsBoolean()) {
- ExtensionImpl* self = GetFromArguments<ExtensionImpl>(args);
+ std::string event_name = *v8::String::AsciiValue(args[0]->ToString());
+ bool is_manual = args[1]->BooleanValue();
+
const ChromeV8ContextSet& context_set =
- self->extension_dispatcher()->v8_context_set();
+ extension_dispatcher()->v8_context_set();
ChromeV8Context* context = context_set.GetCurrent();
if (!context)
return v8::Undefined();
@@ -114,8 +144,6 @@ class ExtensionImpl : public ChromeV8Extension {
std::string extension_id = context->GetExtensionID();
EventListenerCounts& listener_counts =
g_listener_counts.Get()[extension_id];
- std::string event_name(*v8::String::AsciiValue(args[0]));
- bool is_manual = args[1]->BooleanValue();
if (--listener_counts[event_name] == 0) {
content::RenderThread::Get()->Send(
@@ -126,17 +154,164 @@ class ExtensionImpl : public ChromeV8Extension {
// removed. If the context is the background page, and it removes the
// last listener manually, then we assume that it is no longer interested
// in being awakened for this event.
- if (is_manual && self->IsLazyBackgroundPage(context->extension())) {
+ if (is_manual && IsLazyBackgroundPage(context->extension())) {
content::RenderThread::Get()->Send(
new ExtensionHostMsg_RemoveLazyListener(extension_id, event_name));
}
}
+ return v8::Undefined();
+ }
+
+ // MatcherID AttachFilteredEvent(string event_name, object filter)
+ // event_name - Name of the event to attach.
+ // filter - Which instances of the named event are we interested in.
+ // returns the id assigned to the listener, which will be returned from calls
+ // to MatchAgainstEventFilter where this listener matches.
+ v8::Handle<v8::Value> AttachFilteredEvent(const v8::Arguments& args) {
+ DCHECK_EQ(2, args.Length());
+ DCHECK(args[0]->IsString());
+ DCHECK(args[1]->IsObject());
+
+ const ChromeV8ContextSet& context_set =
+ extension_dispatcher()->v8_context_set();
+ ChromeV8Context* context = context_set.GetCurrent();
+ DCHECK(context);
+ if (!context)
+ return v8::Integer::New(-1);
+
+ std::string event_name = *v8::String::AsciiValue(args[0]);
+ // This method throws an exception if it returns false.
+ if (!extension_dispatcher()->CheckCurrentContextAccessToExtensionAPI(
+ event_name))
+ return v8::Undefined();
+
+ std::string extension_id = context->GetExtensionID();
+ if (extension_id.empty())
+ return v8::Integer::New(-1);
+
+ scoped_ptr<base::DictionaryValue> filter;
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+
+ base::DictionaryValue* filter_dict = NULL;
+ base::Value* filter_value = converter->FromV8Value(args[1]->ToObject(),
+ v8::Context::GetCurrent());
+ if (!filter_value->GetAsDictionary(&filter_dict)) {
+ delete filter_value;
+ return v8::Integer::New(-1);
+ }
+
+ filter.reset(filter_dict);
+ int id = event_filter_->AddEventMatcher(event_name, ParseEventMatcher(
+ filter.get()));
+
+ // Only send IPCs the first time a filter gets added.
+ if (AddFilter(event_name, extension_id, filter.get())) {
+ bool lazy = IsLazyBackgroundPage(context->extension());
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_AddFilteredListener(extension_id, event_name,
+ *filter, lazy));
+ }
+
+ return v8::Integer::New(id);
+ }
+
+ // Add a filter to |event_name| in |extension_id|, returning true if it
+ // was the first filter for that event in that extension.
+ bool AddFilter(const std::string& event_name,
+ const std::string& extension_id,
+ base::DictionaryValue* filter) {
+ FilteredEventListenerCounts& counts =
+ g_filtered_listener_counts.Get()[extension_id];
+ FilteredEventListenerCounts::iterator it = counts.find(event_name);
+ if (it == counts.end())
+ counts[event_name].reset(new extensions::ValueCounter);
+
+ int result = counts[event_name]->Add(*filter);
+ return 1 == result;
+ }
+
+ // Remove a filter from |event_name| in |extension_id|, returning true if it
+ // was the last filter for that event in that extension.
+ bool RemoveFilter(const std::string& event_name,
+ const std::string& extension_id,
+ base::DictionaryValue* filter) {
+ FilteredEventListenerCounts& counts =
+ g_filtered_listener_counts.Get()[extension_id];
+ FilteredEventListenerCounts::iterator it = counts.find(event_name);
+ if (it == counts.end())
+ return false;
+ return 0 == it->second->Remove(*filter);
+ }
+
+ // void DetachFilteredEvent(int id, bool manual)
+ // id - Id of the event to detach.
+ // manual - false if this is part of the extension unload process where all
+ // listeners are automatically detached.
+ v8::Handle<v8::Value> DetachFilteredEvent(const v8::Arguments& args) {
+ DCHECK_EQ(2, args.Length());
+ DCHECK(args[0]->IsInt32());
+ DCHECK(args[1]->IsBoolean());
+ bool is_manual = args[1]->BooleanValue();
+ const ChromeV8ContextSet& context_set =
+ extension_dispatcher()->v8_context_set();
+ ChromeV8Context* context = context_set.GetCurrent();
+ if (!context)
+ return v8::Undefined();
+
+ std::string extension_id = context->GetExtensionID();
+ if (extension_id.empty())
+ return v8::Undefined();
+
+ int matcher_id = args[0]->Int32Value();
+ extensions::EventMatcher* event_matcher =
+ event_filter_->GetEventMatcher(matcher_id);
+
+ const std::string& event_name = event_filter_->GetEventName(matcher_id);
+
+ // Only send IPCs the last time a filter gets removed.
+ if (RemoveFilter(event_name, extension_id, event_matcher->value())) {
+ bool lazy = is_manual && IsLazyBackgroundPage(context->extension());
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_RemoveFilteredListener(extension_id, event_name,
+ *event_matcher->value(),
+ lazy));
+ }
+
+ event_filter_->RemoveEventMatcher(matcher_id);
return v8::Undefined();
}
- private:
+ v8::Handle<v8::Value> MatchAgainstEventFilter(const v8::Arguments& args) {
+ typedef std::set<extensions::EventFilter::MatcherID> MatcherIDs;
+
+ std::string event_name = *v8::String::AsciiValue(args[0]->ToString());
+ extensions::EventFilteringInfo info = ParseFromObject(args[1]->ToObject());
+ MatcherIDs matched_event_filters = event_filter_->MatchEvent(
+ event_name, info);
+ v8::Handle<v8::Array> array(v8::Array::New(matched_event_filters.size()));
+ int i = 0;
+ for (MatcherIDs::iterator it = matched_event_filters.begin();
+ it != matched_event_filters.end(); ++it) {
+ array->Set(v8::Integer::New(i++), v8::Integer::New(*it));
+ }
+ return array;
+ }
+
+ extensions::EventFilteringInfo ParseFromObject(
+ v8::Handle<v8::Object> object) {
+ extensions::EventFilteringInfo info;
+ v8::Handle<v8::String> url(v8::String::New("url"));
+ if (object->Has(url)) {
+ v8::Handle<v8::Value> url_value(object->Get(url));
+ info.SetURL(GURL(*v8::String::AsciiValue(url_value)));
+ }
+ return info;
+ }
+ private:
+ extensions::EventFilter* event_filter_;
bool IsLazyBackgroundPage(const Extension* extension) {
content::RenderView* render_view = GetCurrentRenderView();
if (!render_view)
@@ -146,10 +321,18 @@ class ExtensionImpl : public ChromeV8Extension {
return (extension && extension->has_lazy_background_page() &&
helper->view_type() == chrome::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE);
}
+
+ scoped_ptr<extensions::EventMatcher> ParseEventMatcher(
+ base::DictionaryValue* filter_dict) {
+ return scoped_ptr<extensions::EventMatcher>(new extensions::EventMatcher(
+ scoped_ptr<base::DictionaryValue>(filter_dict->DeepCopy())));
+ }
};
} // namespace
-ChromeV8Extension* EventBindings::Get(ExtensionDispatcher* dispatcher) {
- return new ExtensionImpl(dispatcher);
+// static
+ChromeV8Extension* EventBindings::Get(ExtensionDispatcher* dispatcher,
+ extensions::EventFilter* event_filter) {
+ return new ExtensionImpl(dispatcher, event_filter);
}
diff --git a/chrome/renderer/extensions/event_bindings.h b/chrome/renderer/extensions/event_bindings.h
index baa2004..17b32d5 100644
--- a/chrome/renderer/extensions/event_bindings.h
+++ b/chrome/renderer/extensions/event_bindings.h
@@ -9,6 +9,10 @@
class ChromeV8Extension;
class ExtensionDispatcher;
+namespace extensions {
+class EventFilter;
+}
+
namespace v8 {
class Extension;
}
@@ -16,7 +20,8 @@ class Extension;
// This class deals with the javascript bindings related to Event objects.
class EventBindings {
public:
- static ChromeV8Extension* Get(ExtensionDispatcher* dispatcher);
+ static ChromeV8Extension* Get(ExtensionDispatcher* dispatcher,
+ extensions::EventFilter* event_filter);
};
#endif // CHROME_RENDERER_EXTENSIONS_EVENT_BINDINGS_H_
diff --git a/chrome/renderer/extensions/event_unittest.cc b/chrome/renderer/extensions/event_unittest.cc
index 947d3e8..4b0619f 100644
--- a/chrome/renderer/extensions/event_unittest.cc
+++ b/chrome/renderer/extensions/event_unittest.cc
@@ -22,13 +22,41 @@ class EventUnittest : public ModuleSystemTest {
OverrideNativeHandler("event_bindings",
"var assert = requireNative('assert');"
"var attachedListeners = exports.attachedListeners = {};"
+ "var attachedFilteredListeners = "
+ " exports.attachedFilteredListeners = {};"
+ "var nextId = 0;"
+ "var idToName = {};"
+
"exports.AttachEvent = function(eventName) {"
" assert.AssertFalse(!!attachedListeners[eventName]);"
" attachedListeners[eventName] = 1;"
"};"
+
"exports.DetachEvent = function(eventName) {"
" assert.AssertTrue(!!attachedListeners[eventName]);"
" delete attachedListeners[eventName];"
+ "};"
+
+ "exports.IsEventAttached = function(eventName) {"
+ " return !!attachedListeners[eventName];"
+ "};"
+
+ "exports.AttachFilteredEvent = function(name, filters) {"
+ " var id = nextId++;"
+ " idToName[id] = name;"
+ " attachedFilteredListeners[name] ="
+ " attachedFilteredListeners[name] || [];"
+ " attachedFilteredListeners[name][id] = filters;"
+ " return id;"
+ "};"
+
+ "exports.DetachFilteredEvent = function(id, manual) {"
+ " var i = attachedFilteredListeners[idToName[id]].indexOf(id);"
+ " attachedFilteredListeners[idToName[id]].splice(i, 1);"
+ "};"
+
+ "exports.HasFilteredListener = function(name) {"
+ " return attachedFilteredListeners[name].length;"
"};");
OverrideNativeHandler("chrome_hidden",
"var chromeHidden = {};"
@@ -62,6 +90,39 @@ TEST_F(EventUnittest, AddRemoveTwoListeners) {
module_system_->Require("test");
}
+TEST_F(EventUnittest, OnUnloadDetachesAllListeners) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system_.get());
+ RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "var event = require('event');"
+ "var eventBindings = requireNative('event_bindings');"
+ "var chromeHidden = requireNative('chrome_hidden').GetChromeHidden();"
+ "var myEvent = new event.Event('named-event');"
+ "var cb1 = function() {};"
+ "var cb2 = function() {};"
+ "myEvent.addListener(cb1);"
+ "myEvent.addListener(cb2);"
+ "chromeHidden.dispatchOnUnload();"
+ "assert.AssertFalse(!!eventBindings.attachedListeners['named-event']);");
+ module_system_->Require("test");
+}
+
+TEST_F(EventUnittest, OnUnloadDetachesAllListenersEvenDupes) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system_.get());
+ RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "var event = require('event');"
+ "var eventBindings = requireNative('event_bindings');"
+ "var chromeHidden = requireNative('chrome_hidden').GetChromeHidden();"
+ "var myEvent = new event.Event('named-event');"
+ "var cb1 = function() {};"
+ "myEvent.addListener(cb1);"
+ "myEvent.addListener(cb1);"
+ "chromeHidden.dispatchOnUnload();"
+ "assert.AssertFalse(!!eventBindings.attachedListeners['named-event']);");
+ module_system_->Require("test");
+}
+
TEST_F(EventUnittest, EventsThatSupportRulesMustHaveAName) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system_.get());
RegisterModule("test",
@@ -92,4 +153,97 @@ TEST_F(EventUnittest, NamedEventDispatch) {
module_system_->Require("test");
}
+TEST_F(EventUnittest, AddListenerWithFiltersThrowsErrorByDefault) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system_.get());
+ RegisterModule("test",
+ "var event = require('event');"
+ "var assert = requireNative('assert');"
+ "var e = new event.Event('myevent');"
+ "var filter = [{"
+ " url: {hostSuffix: 'google.com'},"
+ "}];"
+ "var caught = false;"
+ "try {"
+ " e.addListener(function() {}, filter);"
+ "} catch (e) {"
+ " caught = true;"
+ "}"
+ "assert.AssertTrue(caught);");
+ module_system_->Require("test");
+}
+
+TEST_F(EventUnittest, FilteredEventsAttachment) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system_.get());
+ RegisterModule("test",
+ "var event = require('event');"
+ "var assert = requireNative('assert');"
+ "var bindings = requireNative('event_bindings');"
+ "var eventOpts = {supportsListeners: true, supportsFilters: true};"
+ "var e = new event.Event('myevent', undefined, eventOpts);"
+ "var cb = function() {};"
+ "var filters = {url: [{hostSuffix: 'google.com'}]};"
+ "e.addListener(cb, filters);"
+ "assert.AssertTrue(bindings.HasFilteredListener('myevent'));"
+ "e.removeListener(cb);"
+ "assert.AssertFalse(bindings.HasFilteredListener('myevent'));");
+ module_system_->Require("test");
+}
+
+TEST_F(EventUnittest, DetachFilteredEvent) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system_.get());
+ RegisterModule("test",
+ "var event = require('event');"
+ "var assert = requireNative('assert');"
+ "var bindings = requireNative('event_bindings');"
+ "var eventOpts = {supportsListeners: true, supportsFilters: true};"
+ "var e = new event.Event('myevent', undefined, eventOpts);"
+ "var cb1 = function() {};"
+ "var cb2 = function() {};"
+ "var filters = {url: [{hostSuffix: 'google.com'}]};"
+ "e.addListener(cb1, filters);"
+ "e.addListener(cb2, filters);"
+ "e.detach_();"
+ "assert.AssertFalse(bindings.HasFilteredListener('myevent'));");
+ module_system_->Require("test");
+}
+
+TEST_F(EventUnittest, AttachAndRemoveSameFilteredEventListener) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system_.get());
+ RegisterModule("test",
+ "var event = require('event');"
+ "var assert = requireNative('assert');"
+ "var bindings = requireNative('event_bindings');"
+ "var eventOpts = {supportsListeners: true, supportsFilters: true};"
+ "var e = new event.Event('myevent', undefined, eventOpts);"
+ "var cb = function() {};"
+ "var filters = {url: [{hostSuffix: 'google.com'}]};"
+ "e.addListener(cb, filters);"
+ "e.addListener(cb, filters);"
+ "assert.AssertTrue(bindings.HasFilteredListener('myevent'));"
+ "e.removeListener(cb);"
+ "assert.AssertTrue(bindings.HasFilteredListener('myevent'));"
+ "e.removeListener(cb);"
+ "assert.AssertFalse(bindings.HasFilteredListener('myevent'));");
+ module_system_->Require("test");
+}
+
+TEST_F(EventUnittest, AddingFilterWithUrlFieldNotAListThrowsException) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system_.get());
+ RegisterModule("test",
+ "var event = require('event');"
+ "var assert = requireNative('assert');"
+ "var eventOpts = {supportsListeners: true, supportsFilters: true};"
+ "var e = new event.Event('myevent', undefined, eventOpts);"
+ "var cb = function() {};"
+ "var filters = {url: {hostSuffix: 'google.com'}};"
+ "var caught = false;"
+ "try {"
+ " e.addListener(cb, filters);"
+ "} catch (e) {"
+ " caught = true;"
+ "}"
+ "assert.AssertTrue(caught);");
+ module_system_->Require("test");
+}
+
} // namespace
diff --git a/chrome/renderer/extensions/extension_dispatcher.cc b/chrome/renderer/extensions/extension_dispatcher.cc
index 35fe9af..9a60e06 100644
--- a/chrome/renderer/extensions/extension_dispatcher.cc
+++ b/chrome/renderer/extensions/extension_dispatcher.cc
@@ -233,7 +233,8 @@ ExtensionDispatcher::ExtensionDispatcher()
webrequest_adblock_plus_(false),
webrequest_other_(false),
source_map_(&ResourceBundle::GetSharedInstance()),
- chrome_channel_(chrome::VersionInfo::CHANNEL_UNKNOWN) {
+ chrome_channel_(chrome::VersionInfo::CHANNEL_UNKNOWN),
+ event_filter_(new extensions::EventFilter) {
const CommandLine& command_line = *(CommandLine::ForCurrentProcess());
is_extension_process_ =
command_line.HasSwitch(switches::kExtensionProcess) ||
@@ -490,7 +491,7 @@ bool ExtensionDispatcher::AllowScriptExtension(
void ExtensionDispatcher::RegisterNativeHandlers(ModuleSystem* module_system,
ChromeV8Context* context) {
module_system->RegisterNativeHandler("event_bindings",
- scoped_ptr<NativeHandler>(EventBindings::Get(this)));
+ scoped_ptr<NativeHandler>(EventBindings::Get(this, event_filter_.get())));
module_system->RegisterNativeHandler("miscellaneous_bindings",
scoped_ptr<NativeHandler>(MiscellaneousBindings::Get(this)));
module_system->RegisterNativeHandler("apiDefinitions",
diff --git a/chrome/renderer/extensions/extension_dispatcher.h b/chrome/renderer/extensions/extension_dispatcher.h
index ea2f9c1..4947f48 100644
--- a/chrome/renderer/extensions/extension_dispatcher.h
+++ b/chrome/renderer/extensions/extension_dispatcher.h
@@ -13,6 +13,7 @@
#include "base/shared_memory.h"
#include "base/timer.h"
#include "content/public/renderer/render_process_observer.h"
+#include "chrome/common/extensions/event_filter.h"
#include "chrome/common/extensions/extension_set.h"
#include "chrome/common/extensions/features/feature.h"
#include "chrome/renderer/extensions/chrome_v8_context.h"
@@ -28,6 +29,10 @@ class URLPattern;
class UserScriptSlave;
struct ExtensionMsg_Loaded_Params;
+namespace extensions {
+class FilteredEventRouter;
+}
+
namespace WebKit {
class WebFrame;
}
@@ -258,6 +263,10 @@ class ExtensionDispatcher : public content::RenderProcessObserver {
// TODO(aa): Remove when we can restrict non-permission APIs to dev-only.
int chrome_channel_;
+ // Routes events to the appropriate listener taking into consideration event
+ // filters.
+ scoped_ptr<extensions::EventFilter> event_filter_;
+
DISALLOW_COPY_AND_ASSIGN(ExtensionDispatcher);
};
diff --git a/chrome/renderer/resources/extensions/event.js b/chrome/renderer/resources/extensions/event.js
index a6d6363..4b784ad 100644
--- a/chrome/renderer/resources/extensions/event.js
+++ b/chrome/renderer/resources/extensions/event.js
@@ -5,6 +5,9 @@
var eventBindingsNatives = requireNative('event_bindings');
var AttachEvent = eventBindingsNatives.AttachEvent;
var DetachEvent = eventBindingsNatives.DetachEvent;
+ var AttachFilteredEvent = eventBindingsNatives.AttachFilteredEvent;
+ var DetachFilteredEvent = eventBindingsNatives.DetachFilteredEvent;
+ var MatchAgainstEventFilter = eventBindingsNatives.MatchAgainstEventFilter;
var sendRequest = require('sendRequest').sendRequest;
var utils = require('utils');
var validate = require('schemaUtils').validate;
@@ -75,6 +78,85 @@
};
})();
+ // A map of event names to the event object that is registered to that name.
+ var attachedNamedEvents = {};
+
+ // An array of all attached event objects, used for detaching on unload.
+ var allAttachedEvents = [];
+
+ // A map of functions that massage event arguments before they are dispatched.
+ // Key is event name, value is function.
+ var eventArgumentMassagers = {};
+
+ // Handles adding/removing/dispatching listeners for unfiltered events.
+ var UnfilteredAttachmentStrategy = function(event) {
+ this.event_ = event;
+ };
+
+ UnfilteredAttachmentStrategy.prototype.onAddedListener =
+ function(listener) {
+ // Only attach / detach on the first / last listener removed.
+ if (this.event_.listeners_.length == 0)
+ AttachEvent(this.event_.eventName_);
+ };
+
+ UnfilteredAttachmentStrategy.prototype.onRemovedListener =
+ function(listener) {
+ if (this.event_.listeners_.length == 0)
+ this.detach(true);
+ };
+
+ UnfilteredAttachmentStrategy.prototype.detach = function(manual) {
+ DetachEvent(this.event_.eventName_, manual);
+ };
+
+ UnfilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
+ return this.event_.listeners_;
+ };
+
+ var FilteredAttachmentStrategy = function(event) {
+ this.event_ = event;
+ this.listenerMap_ = {};
+ };
+
+ FilteredAttachmentStrategy.idToEventMap = {};
+
+ FilteredAttachmentStrategy.prototype.onAddedListener = function(listener) {
+ var id = AttachFilteredEvent(this.event_.eventName_,
+ listener.filters || {});
+ if (id == -1)
+ throw new Error("Can't add listener");
+ listener.id = id;
+ this.listenerMap_[id] = listener;
+ FilteredAttachmentStrategy.idToEventMap[id] = this.event_;
+ };
+
+ FilteredAttachmentStrategy.prototype.onRemovedListener = function(listener) {
+ this.detachListener(listener, true);
+ };
+
+ FilteredAttachmentStrategy.prototype.detachListener =
+ function(listener, manual) {
+ if (listener.id == undefined)
+ throw new Error("listener.id undefined - '" + listener + "'");
+ var id = listener.id;
+ delete this.listenerMap_[id];
+ delete FilteredAttachmentStrategy.idToEventMap[id];
+ DetachFilteredEvent(id, manual);
+ };
+
+ FilteredAttachmentStrategy.prototype.detach = function(manual) {
+ for (var i in this.listenerMap_)
+ this.detachListener(this.listenerMap_[i], manual);
+ };
+
+ FilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
+ var result = [];
+ for (var i = 0; i < ids.length; i++)
+ result.push(this.listenerMap_[ids[i]]);
+ return result;
+ };
+
// Event object. If opt_eventName is provided, this object represents
// the unique instance of that named event, and dispatching an event
// with that name will route through this object's listeners. Note that
@@ -92,11 +174,20 @@
this.eventName_ = opt_eventName;
this.listeners_ = [];
this.eventOptions_ = opt_eventOptions ||
- {"supportsListeners": true, "supportsRules": false};
+ {supportsFilters: false,
+ supportsListeners: true,
+ supportsRules: false,
+ };
if (this.eventOptions_.supportsRules && !opt_eventName)
throw new Error("Events that support rules require an event name.");
+ if (this.eventOptions_.supportsFilters) {
+ this.attachmentStrategy_ = new FilteredAttachmentStrategy(this);
+ } else {
+ this.attachmentStrategy_ = new UnfilteredAttachmentStrategy(this);
+ }
+
// Validate event arguments (the data that is passed to the callbacks)
// if we are in debug.
if (opt_argSchemas &&
@@ -115,16 +206,6 @@
}
};
- // A map of event names to the event object that is registered to that name.
- var attachedNamedEvents = {};
-
- // An array of all attached event objects, used for detaching on unload.
- var allAttachedEvents = [];
-
- // A map of functions that massage event arguments before they are dispatched.
- // Key is event name, value is function.
- var eventArgumentMassagers = {};
-
chromeHidden.Event = {};
chromeHidden.Event.registerArgumentMassager = function(name, fn) {
@@ -136,7 +217,12 @@
// Dispatches a named event with the given JSON array, which is deserialized
// before dispatch. The JSON array is the list of arguments that will be
// sent with the event callback.
- chromeHidden.Event.dispatchJSON = function(name, args) {
+ chromeHidden.Event.dispatchJSON = function(name, args, filteringInfo) {
+ var listenerIDs = null;
+
+ if (filteringInfo) {
+ listenerIDs = MatchAgainstEventFilter(name, filteringInfo);
+ }
if (attachedNamedEvents[name]) {
if (args) {
// TODO(asargent): This is an antiquity. Until all callers of
@@ -148,8 +234,18 @@
if (eventArgumentMassagers[name])
eventArgumentMassagers[name](args);
}
- var result = attachedNamedEvents[name].dispatch.apply(
- attachedNamedEvents[name], args);
+
+ var event = attachedNamedEvents[name];
+ var result;
+ // TODO(koz): We have to do this differently for unfiltered events (which
+ // have listenerIDs = null) because some bindings write over
+ // event.dispatch (eg: experimental.app.custom_bindings.js) and so expect
+ // events to go through it. These places need to be fixed so that they
+ // expect a listenerIDs parameter.
+ if (listenerIDs)
+ result = event.dispatch_(args, listenerIDs);
+ else
+ result = event.dispatch.apply(event, args);
if (result && result.validationErrors)
return result.validationErrors;
}
@@ -170,13 +266,34 @@
};
// Registers a callback to be called when this event is dispatched.
- chrome.Event.prototype.addListener = function(cb) {
+ chrome.Event.prototype.addListener = function(cb, filters) {
if (!this.eventOptions_.supportsListeners)
throw new Error("This event does not support listeners.");
+ if (filters) {
+ if (!this.eventOptions_.supportsFilters)
+ throw new Error("This event does not support filters.");
+ if (filters.url && !(filters.url instanceof Array))
+ throw new Error("filters.url should be an array");
+ }
+ var listener = {callback: cb, filters: filters};
+ this.attach_(listener);
+ this.listeners_.push(listener);
+ };
+
+ chrome.Event.prototype.attach_ = function(listener) {
+ this.attachmentStrategy_.onAddedListener(listener);
if (this.listeners_.length == 0) {
- this.attach_();
+ allAttachedEvents[allAttachedEvents.length] = this;
+ if (!this.eventName_)
+ return;
+
+ if (attachedNamedEvents[this.eventName_]) {
+ throw new Error("chrome.Event '" + this.eventName_ +
+ "' is already attached.");
+ }
+
+ attachedNamedEvents[this.eventName_] = this;
}
- this.listeners_.push(cb);
};
// Unregisters a callback.
@@ -188,9 +305,22 @@
return;
}
- this.listeners_.splice(idx, 1);
+ var removedListener = this.listeners_.splice(idx, 1)[0];
+ this.attachmentStrategy_.onRemovedListener(removedListener);
+
if (this.listeners_.length == 0) {
- this.detach_(true);
+ var i = allAttachedEvents.indexOf(this);
+ if (i >= 0)
+ delete allAttachedEvents[i];
+ if (!this.eventName_)
+ return;
+
+ if (!attachedNamedEvents[this.eventName_]) {
+ throw new Error("chrome.Event '" + this.eventName_ +
+ "' is not attached.");
+ }
+
+ delete attachedNamedEvents[this.eventName_];
}
};
@@ -212,7 +342,7 @@
// found.
chrome.Event.prototype.findListener_ = function(cb) {
for (var i = 0; i < this.listeners_.length; i++) {
- if (this.listeners_[i] == cb) {
+ if (this.listeners_[i].callback == cb) {
return i;
}
}
@@ -220,21 +350,21 @@
return -1;
};
- // Dispatches this event object to all listeners, passing all supplied
- // arguments to this function each listener.
- chrome.Event.prototype.dispatch = function(varargs) {
+ chrome.Event.prototype.dispatch_ = function(args, listenerIDs) {
if (!this.eventOptions_.supportsListeners)
throw new Error("This event does not support listeners.");
- var args = Array.prototype.slice.call(arguments);
var validationErrors = this.validateEventArgs_(args);
if (validationErrors) {
console.error(validationErrors);
return {validationErrors: validationErrors};
}
+
+ var listeners = this.attachmentStrategy_.getListenersByIDs(listenerIDs);
+
var results = [];
- for (var i = 0; i < this.listeners_.length; i++) {
+ for (var i = 0; i < listeners.length; i++) {
try {
- var result = this.listeners_[i].apply(null, args);
+ var result = listeners[i].callback.apply(null, args);
if (result !== undefined)
results.push(result);
} catch (e) {
@@ -244,39 +374,17 @@
}
if (results.length)
return {results: results};
- };
-
- // Attaches this event object to its name. Only one object can have a given
- // name.
- chrome.Event.prototype.attach_ = function() {
- AttachEvent(this.eventName_);
- allAttachedEvents[allAttachedEvents.length] = this;
- if (!this.eventName_)
- return;
-
- if (attachedNamedEvents[this.eventName_]) {
- throw new Error("chrome.Event '" + this.eventName_ +
- "' is already attached.");
- }
+ }
- attachedNamedEvents[this.eventName_] = this;
+ // Dispatches this event object to all listeners, passing all supplied
+ // arguments to this function each listener.
+ chrome.Event.prototype.dispatch = function(varargs) {
+ return this.dispatch_(Array.prototype.slice.call(arguments), undefined);
};
// Detaches this event object from its name.
- chrome.Event.prototype.detach_ = function(manual) {
- var i = allAttachedEvents.indexOf(this);
- if (i >= 0)
- delete allAttachedEvents[i];
- DetachEvent(this.eventName_, manual);
- if (!this.eventName_)
- return;
-
- if (!attachedNamedEvents[this.eventName_]) {
- throw new Error("chrome.Event '" + this.eventName_ +
- "' is not attached.");
- }
-
- delete attachedNamedEvents[this.eventName_];
+ chrome.Event.prototype.detach_ = function() {
+ this.attachmentStrategy_.detach(false);
};
chrome.Event.prototype.destroy_ = function() {
@@ -363,7 +471,7 @@
for (var i = 0; i < allAttachedEvents.length; ++i) {
var event = allAttachedEvents[i];
if (event)
- event.detach_(false);
+ event.detach_();
}
};
diff --git a/chrome/test/data/extensions/api_test/filtered_events/manifest.json b/chrome/test/data/extensions/api_test/filtered_events/manifest.json
new file mode 100644
index 0000000..73918bf
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/filtered_events/manifest.json
@@ -0,0 +1,11 @@
+{
+ "name": "filtered events apitest",
+ "description": "filtered events extension",
+ "version": "0.1",
+ "manifest_version": 2,
+ "background": {
+ "scripts": ["background.js"],
+ "persistent": false
+ },
+ "permissions": [ "webNavigation", "tabs" ]
+}
diff --git a/chrome/test/data/extensions/api_test/lazy_background_page/filters/background.js b/chrome/test/data/extensions/api_test/lazy_background_page/filters/background.js
new file mode 100644
index 0000000..4835058
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/lazy_background_page/filters/background.js
@@ -0,0 +1,9 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+console.log("running extension!");
+chrome.webNavigation.onBeforeNavigate.addListener(function(details) {
+ console.log("Got the event!");
+ chrome.test.succeed();
+});
diff --git a/chrome/test/data/extensions/api_test/lazy_background_page/filters/manifest.json b/chrome/test/data/extensions/api_test/lazy_background_page/filters/manifest.json
new file mode 100644
index 0000000..c6b541a
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/lazy_background_page/filters/manifest.json
@@ -0,0 +1,11 @@
+{
+ "name": "Lazy BG messaging test",
+ "description": "Test that message passing starts the background page",
+ "version": "1",
+ "manifest_version": 2,
+ "permissions": ["experimental", "webNavigation"],
+ "background": {
+ "scripts": ["background.js"],
+ "persistent": false
+ }
+}
diff --git a/chrome/test/data/extensions/api_test/webnavigation/filtered/a.html b/chrome/test/data/extensions/api_test/webnavigation/filtered/a.html
new file mode 100644
index 0000000..9c8afe8
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webnavigation/filtered/a.html
@@ -0,0 +1 @@
+<body onload="document.location='b.html'"></body>
diff --git a/chrome/test/data/extensions/api_test/webnavigation/filtered/b.html b/chrome/test/data/extensions/api_test/webnavigation/filtered/b.html
new file mode 100644
index 0000000..18ecdcb
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webnavigation/filtered/b.html
@@ -0,0 +1 @@
+<html></html>
diff --git a/chrome/test/data/extensions/api_test/webnavigation/test_filtered.html b/chrome/test/data/extensions/api_test/webnavigation/test_filtered.html
new file mode 100644
index 0000000..dbf62b6
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webnavigation/test_filtered.html
@@ -0,0 +1,5 @@
+<script src="test_filtered.js"></script>
+<script src="framework.js"></script>
+<script>
+ runTests();
+</script>
diff --git a/chrome/test/data/extensions/api_test/webnavigation/test_filtered.js b/chrome/test/data/extensions/api_test/webnavigation/test_filtered.js
new file mode 100644
index 0000000..b262b92
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webnavigation/test_filtered.js
@@ -0,0 +1,28 @@
+// 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.
+
+function runTests() {
+ var getURL = chrome.extension.getURL;
+ chrome.tabs.create({"url": "about:blank"}, function(tab) {
+ var tabId = tab.id;
+ chrome.test.runTests([
+ function dontGetEventToWrongUrl() {
+ var a_visited = false;
+ chrome.webNavigation.onCommitted.addListener(function(details) {
+ chrome.test.fail();
+ }, { url: [{pathSuffix: 'never-navigated.html'}] });
+ chrome.webNavigation.onCommitted.addListener(function(details) {
+ chrome.test.assertTrue(details.url == getURL('filtered/a.html'));
+ a_visited = true;
+ }, { url: [{pathSuffix: 'a.html'}] });
+ chrome.webNavigation.onCommitted.addListener(function(details) {
+ chrome.test.assertTrue(details.url == getURL('filtered/b.html'));
+ chrome.test.assertTrue(a_visited);
+ chrome.test.succeed();
+ }, { url: [{pathSuffix: 'b.html'}] });
+ chrome.tabs.update(tabId, { url: getURL('filtered/a.html') });
+ }
+ ]);
+ });
+}