Allow restricting WebUI-enabled extension APIs to URL patterns.
BUG=391944
[email protected], [email protected]
Review URL: https://codereview.chromium.org/422433005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@286564 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/api/idle/idle_api_unittest.cc b/chrome/browser/extensions/api/idle/idle_api_unittest.cc
index cb49004..82779fe 100644
--- a/chrome/browser/extensions/api/idle/idle_api_unittest.cc
+++ b/chrome/browser/extensions/api/idle/idle_api_unittest.cc
@@ -107,16 +107,14 @@
const std::string& extension_id)
: idle_manager_(idle_manager),
extension_id_(extension_id) {
- const EventListenerInfo details(idle::OnStateChanged::kEventName,
- extension_id_,
- NULL);
+ const EventListenerInfo details(
+ idle::OnStateChanged::kEventName, extension_id_, GURL(), NULL);
idle_manager_->OnListenerAdded(details);
}
ScopedListen::~ScopedListen() {
- const EventListenerInfo details(idle::OnStateChanged::kEventName,
- extension_id_,
- NULL);
+ const EventListenerInfo details(
+ idle::OnStateChanged::kEventName, extension_id_, GURL(), NULL);
idle_manager_->OnListenerRemoved(details);
}
diff --git a/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_manager_unittest.cc b/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_manager_unittest.cc
index 9621438..a7d59394 100644
--- a/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_manager_unittest.cc
+++ b/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_manager_unittest.cc
@@ -32,6 +32,7 @@
EventListenerInfo info(api::signed_in_devices::OnDeviceInfoChange::kEventName,
"extension1",
+ GURL(),
profile.get());
// Add a listener.
diff --git a/chrome/browser/extensions/extension_webui_apitest.cc b/chrome/browser/extensions/extension_webui_apitest.cc
index 1eca94d..dcd40d3 100644
--- a/chrome/browser/extensions/extension_webui_apitest.cc
+++ b/chrome/browser/extensions/extension_webui_apitest.cc
@@ -40,7 +40,10 @@
// Tests running extension APIs on WebUI.
class ExtensionWebUITest : public ExtensionApiTest {
protected:
- testing::AssertionResult RunTest(const char* name) {
+ testing::AssertionResult RunTest(const char* name,
+ const GURL& page_url,
+ const GURL& frame_url,
+ bool expected_result) {
// Tests are located in chrome/test/data/extensions/webui/$(name).
base::FilePath path;
PathService::Get(chrome::DIR_TEST_DATA, &path);
@@ -55,38 +58,64 @@
script = "(function(){'use strict';" + script + "}());";
// Run the test.
- bool result = false;
- content::RenderFrameHost* webui = NavigateToWebUI();
+ bool actual_result = false;
+ content::RenderFrameHost* webui = NavigateToWebUI(page_url, frame_url);
if (!webui)
return testing::AssertionFailure() << "Failed to navigate to WebUI";
- CHECK(content::ExecuteScriptAndExtractBool(webui, script, &result));
- return result ? testing::AssertionSuccess()
- : (testing::AssertionFailure() << "Check console output");
+ CHECK(content::ExecuteScriptAndExtractBool(webui, script, &actual_result));
+ return (expected_result == actual_result)
+ ? testing::AssertionSuccess()
+ : (testing::AssertionFailure() << "Check console output");
}
- private:
- // Navigates the browser to a WebUI page and returns the RenderFrameHost for
- // that page.
- content::RenderFrameHost* NavigateToWebUI() {
- // Use the chrome://extensions page, cos, why not.
- ui_test_utils::NavigateToURL(browser(), GURL("chrome://extensions/"));
-
+ testing::AssertionResult RunTestOnExtensions(const char* name) {
// In the current design the URL of the chrome://extensions page it's
// actually chrome://extensions-frame/ -- and it's important we find it,
// because the top-level frame doesn't execute any code, so a script
// context is never created, so the bindings are never set up, and
// apparently the call to ExecuteScriptAndExtractString doesn't adequately
// set them up either.
+ return RunTest(name,
+ GURL("chrome://extensions"),
+ GURL("chrome://extensions-frame"),
+ true); // tests on chrome://extensions should succeed
+ }
+
+ testing::AssertionResult RunTestOnAbout(const char* name) {
+ // chrome://about is an innocuous page that doesn't have any bindings.
+ // Tests should fail.
+ return RunTest(name,
+ GURL("chrome://about"),
+ GURL("chrome://about"),
+ false); // tests on chrome://about should fail
+ }
+
+ private:
+ // Navigates the browser to a WebUI page and returns the RenderFrameHost for
+ // that page.
+ content::RenderFrameHost* NavigateToWebUI(const GURL& page_url,
+ const GURL& frame_url) {
+ ui_test_utils::NavigateToURL(browser(), page_url);
+
+ content::WebContents* active_web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ if (active_web_contents->GetLastCommittedURL() == frame_url)
+ return active_web_contents->GetMainFrame();
+
content::RenderFrameHost* frame_host = NULL;
- browser()->tab_strip_model()->GetActiveWebContents()->ForEachFrame(
- base::Bind(
- &FindFrame, GURL("chrome://extensions-frame/"), &frame_host));
+ active_web_contents->ForEachFrame(
+ base::Bind(&FindFrame, frame_url, &frame_host));
return frame_host;
}
};
IN_PROC_BROWSER_TEST_F(ExtensionWebUITest, SanityCheckAvailableAPIs) {
- ASSERT_TRUE(RunTest("sanity_check_available_apis.js"));
+ ASSERT_TRUE(RunTestOnExtensions("sanity_check_available_apis.js"));
+}
+
+IN_PROC_BROWSER_TEST_F(ExtensionWebUITest, SanityCheckUnavailableAPIs) {
+ ASSERT_TRUE(RunTestOnAbout("sanity_check_available_apis.js"));
}
// Tests chrome.test.sendMessage, which exercises WebUI making a
@@ -95,7 +124,7 @@
scoped_ptr<ExtensionTestMessageListener> listener(
new ExtensionTestMessageListener("ping", true));
- ASSERT_TRUE(RunTest("send_message.js"));
+ ASSERT_TRUE(RunTestOnExtensions("send_message.js"));
ASSERT_TRUE(listener->WaitUntilSatisfied());
listener->Reply("pong");
@@ -108,7 +137,7 @@
// Tests chrome.runtime.onMessage, which exercises WebUI registering and
// receiving an event.
IN_PROC_BROWSER_TEST_F(ExtensionWebUITest, OnMessage) {
- ASSERT_TRUE(RunTest("on_message.js"));
+ ASSERT_TRUE(RunTestOnExtensions("on_message.js"));
OnMessage::Info info;
info.data = "hi";
@@ -128,7 +157,7 @@
scoped_ptr<ExtensionTestMessageListener> listener(
new ExtensionTestMessageListener("ping", true));
- ASSERT_TRUE(RunTest("runtime_last_error.js"));
+ ASSERT_TRUE(RunTestOnExtensions("runtime_last_error.js"));
ASSERT_TRUE(listener->WaitUntilSatisfied());
listener->ReplyWithError("unknown host");
diff --git a/chrome/test/data/extensions/webui/on_message.js b/chrome/test/data/extensions/webui/on_message.js
index e564224..48464e5 100644
--- a/chrome/test/data/extensions/webui/on_message.js
+++ b/chrome/test/data/extensions/webui/on_message.js
@@ -4,6 +4,13 @@
// out/Debug/browser_tests --gtest_filter=ExtensionWebUITest.OnMessage
+if (!chrome || !chrome.test || !chrome.test.onMessage) {
+ console.error('chrome.test.onMessage is unavailable on ' +
+ document.location.href);
+ domAutomationController.send(false);
+ return;
+}
+
chrome.test.listenOnce(chrome.test.onMessage, function(args) {
if (args.data != 'hi') {
console.error('Expected "hi", Actual ' + JSON.stringify(args.data));
diff --git a/chrome/test/data/extensions/webui/sanity_check_available_apis.js b/chrome/test/data/extensions/webui/sanity_check_available_apis.js
index a57b832b..3d53055 100644
--- a/chrome/test/data/extensions/webui/sanity_check_available_apis.js
+++ b/chrome/test/data/extensions/webui/sanity_check_available_apis.js
@@ -22,10 +22,14 @@
];
var actual = Object.keys(chrome).sort();
-if (!chrome.test.checkDeepEq(expected, actual)) {
+var isEqual = expected.length == actual.length;
+for (var i = 0; i < expected.length && isEqual; i++) {
+ if (expected[i] != actual[i])
+ isEqual = false;
+}
+
+if (!isEqual) {
console.error('Expected: ' + JSON.stringify(expected) + ', ' +
'Actual: ' + JSON.stringify(actual));
- domAutomationController.send(false);
-} else {
- domAutomationController.send(true);
}
+domAutomationController.send(isEqual);
diff --git a/chrome/test/data/extensions/webui/send_message.js b/chrome/test/data/extensions/webui/send_message.js
index 69ab3142..2524e2c 100644
--- a/chrome/test/data/extensions/webui/send_message.js
+++ b/chrome/test/data/extensions/webui/send_message.js
@@ -4,6 +4,13 @@
// out/Debug/browser_tests --gtest_filter=ExtensionWebUITest.SendMessage
+if (!chrome || !chrome.test || !chrome.test.sendMessage) {
+ console.error('chrome.test.sendMessage is unavailable on ' +
+ document.location.href);
+ domAutomationController.send(false);
+ return;
+}
+
chrome.test.sendMessage('ping', function(reply) {
if (reply != 'pong') {
console.error('Expected "pong", Actual ' + JSON.stringify(reply));
diff --git a/extensions/browser/event_listener_map.cc b/extensions/browser/event_listener_map.cc
index 5d90e980..d6e7d2d8 100644
--- a/extensions/browser/event_listener_map.cc
+++ b/extensions/browser/event_listener_map.cc
@@ -8,6 +8,7 @@
#include "content/public/browser/render_process_host.h"
#include "extensions/browser/event_router.h"
#include "ipc/ipc_message.h"
+#include "url/gurl.h"
using base::DictionaryValue;
@@ -15,15 +16,24 @@
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) {
+// static
+scoped_ptr<EventListener> EventListener::ForExtension(
+ const std::string& event_name,
+ const std::string& extension_id,
+ content::RenderProcessHost* process,
+ scoped_ptr<base::DictionaryValue> filter) {
+ return make_scoped_ptr(new EventListener(
+ event_name, extension_id, GURL(), process, filter.Pass()));
+}
+
+// static
+scoped_ptr<EventListener> EventListener::ForURL(
+ const std::string& event_name,
+ const GURL& listener_url,
+ content::RenderProcessHost* process,
+ scoped_ptr<base::DictionaryValue> filter) {
+ return make_scoped_ptr(
+ new EventListener(event_name, "", listener_url, process, filter.Pass()));
}
EventListener::~EventListener() {}
@@ -33,7 +43,8 @@
// 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_ &&
+ extension_id_ == other->extension_id_ &&
+ listener_url_ == other->listener_url_ && process_ == other->process_ &&
((!!filter_.get()) == (!!other->filter_.get())) &&
(!filter_.get() || filter_->Equals(other->filter_.get()));
}
@@ -43,7 +54,7 @@
if (filter_)
filter_copy.reset(filter_->DeepCopy());
return scoped_ptr<EventListener>(new EventListener(
- event_name_, extension_id_, process_, filter_copy.Pass()));
+ event_name_, extension_id_, listener_url_, process_, filter_copy.Pass()));
}
bool EventListener::IsLazy() const {
@@ -58,6 +69,19 @@
return process_ ? process_->GetBrowserContext() : NULL;
}
+EventListener::EventListener(const std::string& event_name,
+ const std::string& extension_id,
+ const GURL& listener_url,
+ content::RenderProcessHost* process,
+ scoped_ptr<DictionaryValue> filter)
+ : event_name_(event_name),
+ extension_id_(extension_id),
+ listener_url_(listener_url),
+ process_(process),
+ filter_(filter.Pass()),
+ matcher_id_(-1) {
+}
+
EventListenerMap::EventListenerMap(Delegate* delegate)
: delegate_(delegate) {
}
@@ -86,7 +110,7 @@
scoped_ptr<EventMatcher> EventListenerMap::ParseEventMatcher(
DictionaryValue* filter_dict) {
return scoped_ptr<EventMatcher>(new EventMatcher(
- scoped_ptr<DictionaryValue>(filter_dict->DeepCopy()), MSG_ROUTING_NONE));
+ make_scoped_ptr(filter_dict->DeepCopy()), MSG_ROUTING_NONE));
}
bool EventListenerMap::RemoveListener(const EventListener* listener) {
@@ -173,8 +197,8 @@
const std::set<std::string>& event_names) {
for (std::set<std::string>::const_iterator it = event_names.begin();
it != event_names.end(); ++it) {
- AddListener(scoped_ptr<EventListener>(new EventListener(
- *it, extension_id, NULL, scoped_ptr<DictionaryValue>())));
+ AddListener(EventListener::ForExtension(
+ *it, extension_id, NULL, scoped_ptr<DictionaryValue>()));
}
}
@@ -190,9 +214,8 @@
const DictionaryValue* filter = NULL;
if (!filter_list->GetDictionary(i, &filter))
continue;
- AddListener(scoped_ptr<EventListener>(new EventListener(
- it.key(), extension_id, NULL,
- scoped_ptr<DictionaryValue>(filter->DeepCopy()))));
+ AddListener(EventListener::ForExtension(
+ it.key(), extension_id, NULL, make_scoped_ptr(filter->DeepCopy())));
}
}
}
diff --git a/extensions/browser/event_listener_map.h b/extensions/browser/event_listener_map.h
index 163fc8f..6f5cea9 100644
--- a/extensions/browser/event_listener_map.h
+++ b/extensions/browser/event_listener_map.h
@@ -12,6 +12,7 @@
#include "base/memory/scoped_ptr.h"
#include "extensions/common/event_filter.h"
+#include "url/gurl.h"
namespace base {
class DictionaryValue;
@@ -37,9 +38,10 @@
// 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.
-//
class EventListener {
public:
+ // Constructs EventListeners for either an Extension or a URL.
+ //
// |filter| represents a generic filter structure that EventFilter knows how
// to filter events with. A typical filter instance will look like
//
@@ -47,10 +49,17 @@
// url: [{hostSuffix: 'google.com'}],
// tabId: 5
// }
- EventListener(const std::string& event_name,
- const std::string& extension_id,
- content::RenderProcessHost* process,
- scoped_ptr<base::DictionaryValue> filter);
+ static scoped_ptr<EventListener> ForExtension(
+ const std::string& event_name,
+ const std::string& extension_id,
+ content::RenderProcessHost* process,
+ scoped_ptr<base::DictionaryValue> filter);
+ static scoped_ptr<EventListener> ForURL(
+ const std::string& event_name,
+ const GURL& listener_url,
+ content::RenderProcessHost* process,
+ scoped_ptr<base::DictionaryValue> filter);
+
~EventListener();
bool Equals(const EventListener* other) const;
@@ -67,16 +76,24 @@
// IsLazy.
content::BrowserContext* GetBrowserContext() const;
- const std::string event_name() const { return event_name_; }
- const std::string extension_id() const { return extension_id_; }
+ const std::string& event_name() const { return event_name_; }
+ const std::string& extension_id() const { return extension_id_; }
+ const GURL& listener_url() const { return listener_url_; }
content::RenderProcessHost* process() const { return process_; }
base::DictionaryValue* filter() const { return filter_.get(); }
EventFilter::MatcherID matcher_id() const { return matcher_id_; }
void set_matcher_id(EventFilter::MatcherID id) { matcher_id_ = id; }
private:
+ EventListener(const std::string& event_name,
+ const std::string& extension_id,
+ const GURL& listener_url,
+ content::RenderProcessHost* process,
+ scoped_ptr<base::DictionaryValue> filter);
+
const std::string event_name_;
const std::string extension_id_;
+ const GURL listener_url_;
content::RenderProcessHost* process_;
scoped_ptr<base::DictionaryValue> filter_;
EventFilter::MatcherID matcher_id_; // -1 if unset.
diff --git a/extensions/browser/event_listener_map_unittest.cc b/extensions/browser/event_listener_map_unittest.cc
index 9f4c5c3..3a45c68 100644
--- a/extensions/browser/event_listener_map_unittest.cc
+++ b/extensions/browser/event_listener_map_unittest.cc
@@ -4,10 +4,12 @@
#include "extensions/browser/event_listener_map.h"
+#include "base/bind.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "extensions/browser/event_router.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
using base::DictionaryValue;
using base::ListValue;
@@ -21,20 +23,26 @@
const char kExt2Id[] = "extension_2";
const char kEvent1Name[] = "event1";
const char kEvent2Name[] = "event2";
+const char kURL[] = "https://google.com/some/url";
+
+typedef base::Callback<scoped_ptr<EventListener>(
+ const std::string&, // event_name
+ content::RenderProcessHost*, // process
+ base::DictionaryValue* // filter (takes ownership)
+ )> EventListenerConstructor;
class EmptyDelegate : public EventListenerMap::Delegate {
virtual void OnListenerAdded(const EventListener* listener) OVERRIDE {};
virtual void OnListenerRemoved(const EventListener* listener) OVERRIDE {};
};
-class EventListenerMapUnittest : public testing::Test {
+class EventListenerMapTest : public testing::Test {
public:
- EventListenerMapUnittest()
- : delegate_(new EmptyDelegate),
- listeners_(new EventListenerMap(delegate_.get())),
- browser_context_(new content::TestBrowserContext),
- process_(new content::MockRenderProcessHost(browser_context_.get())) {
- }
+ EventListenerMapTest()
+ : delegate_(new EmptyDelegate),
+ listeners_(new EventListenerMap(delegate_.get())),
+ browser_context_(new content::TestBrowserContext),
+ process_(new content::MockRenderProcessHost(browser_context_.get())) {}
scoped_ptr<DictionaryValue> CreateHostSuffixFilter(
const std::string& suffix) {
@@ -64,27 +72,64 @@
}
protected:
+ void TestUnfilteredEventsGoToAllListeners(
+ const EventListenerConstructor& constructor);
+ void TestRemovingByProcess(const EventListenerConstructor& constructor);
+ void TestRemovingByListener(const EventListenerConstructor& constructor);
+ void TestAddExistingUnfilteredListener(
+ const EventListenerConstructor& constructor);
+ void TestHasListenerForEvent(const EventListenerConstructor& constructor);
+
scoped_ptr<EventListenerMap::Delegate> delegate_;
scoped_ptr<EventListenerMap> listeners_;
scoped_ptr<content::TestBrowserContext> browser_context_;
scoped_ptr<content::MockRenderProcessHost> process_;
};
-TEST_F(EventListenerMapUnittest, UnfilteredEventsGoToAllListeners) {
- listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
- kEvent1Name, kExt1Id, NULL, scoped_ptr<DictionaryValue>())));
-
- scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
- std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
- ASSERT_EQ(1u, targets.size());
+scoped_ptr<EventListener> CreateEventListenerForExtension(
+ const std::string& extension_id,
+ const std::string& event_name,
+ content::RenderProcessHost* process,
+ base::DictionaryValue* filter) {
+ return EventListener::ForExtension(
+ event_name, extension_id, process, make_scoped_ptr(filter));
}
-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<EventListener> CreateEventListenerForURL(
+ const GURL& listener_url,
+ const std::string& event_name,
+ content::RenderProcessHost* process,
+ base::DictionaryValue* filter) {
+ return EventListener::ForURL(
+ event_name, listener_url, process, make_scoped_ptr(filter));
+}
+
+void EventListenerMapTest::TestUnfilteredEventsGoToAllListeners(
+ const EventListenerConstructor& constructor) {
+ listeners_->AddListener(
+ constructor.Run(kEvent1Name, NULL, new DictionaryValue()));
+ scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
+ ASSERT_EQ(1u, listeners_->GetEventListeners(*event).size());
+}
+
+TEST_F(EventListenerMapTest, UnfilteredEventsGoToAllListenersForExtensions) {
+ TestUnfilteredEventsGoToAllListeners(
+ base::Bind(&CreateEventListenerForExtension, kExt1Id));
+}
+
+TEST_F(EventListenerMapTest, UnfilteredEventsGoToAllListenersForURLs) {
+ TestUnfilteredEventsGoToAllListeners(
+ base::Bind(&CreateEventListenerForURL, GURL(kURL)));
+}
+
+TEST_F(EventListenerMapTest, FilteredEventsGoToAllMatchingListeners) {
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name,
+ kExt1Id,
+ NULL,
+ scoped_ptr<DictionaryValue>(new DictionaryValue)));
scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
event->filter_info.SetURL(GURL("http://www.google.com"));
@@ -92,11 +137,11 @@
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"))));
+TEST_F(EventListenerMapTest, FilteredEventsOnlyGoToMatchingListeners) {
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("yahoo.com")));
scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
event->filter_info.SetURL(GURL("http://www.google.com"));
@@ -104,13 +149,15 @@
ASSERT_EQ(1u, targets.size());
}
-TEST_F(EventListenerMapUnittest, LazyAndUnlazyListenersGetReturned) {
- listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
- kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com"))));
+TEST_F(EventListenerMapTest, LazyAndUnlazyListenersGetReturned) {
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
- listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
- kEvent1Name, kExt1Id, process_.get(),
- CreateHostSuffixFilter("google.com"))));
+ listeners_->AddListener(
+ EventListener::ForExtension(kEvent1Name,
+ kExt1Id,
+ process_.get(),
+ CreateHostSuffixFilter("google.com")));
scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
event->filter_info.SetURL(GURL("http://www.google.com"));
@@ -118,48 +165,68 @@
ASSERT_EQ(2u, targets.size());
}
-TEST_F(EventListenerMapUnittest, TestRemovingByProcess) {
- listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
- kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com"))));
+void EventListenerMapTest::TestRemovingByProcess(
+ const EventListenerConstructor& constructor) {
+ listeners_->AddListener(constructor.Run(
+ kEvent1Name, NULL, CreateHostSuffixFilter("google.com").release()));
- listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
- kEvent1Name, kExt1Id, process_.get(),
- CreateHostSuffixFilter("google.com"))));
+ listeners_->AddListener(
+ constructor.Run(kEvent1Name,
+ process_.get(),
+ CreateHostSuffixFilter("google.com").release()));
listeners_->RemoveListenersForProcess(process_.get());
scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
event->filter_info.SetURL(GURL("http://www.google.com"));
- std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
- ASSERT_EQ(1u, targets.size());
+ ASSERT_EQ(1u, listeners_->GetEventListeners(*event).size());
}
-TEST_F(EventListenerMapUnittest, TestRemovingByListener) {
- listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
- kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com"))));
+TEST_F(EventListenerMapTest, TestRemovingByProcessForExtension) {
+ TestRemovingByProcess(base::Bind(&CreateEventListenerForExtension, kExt1Id));
+}
- listeners_->AddListener(scoped_ptr<EventListener>(new EventListener(
- kEvent1Name, kExt1Id, process_.get(),
- CreateHostSuffixFilter("google.com"))));
+TEST_F(EventListenerMapTest, TestRemovingByProcessForURL) {
+ TestRemovingByProcess(base::Bind(&CreateEventListenerForURL, GURL(kURL)));
+}
- scoped_ptr<EventListener> listener(new EventListener(kEvent1Name, kExt1Id,
- process_.get(), CreateHostSuffixFilter("google.com")));
+void EventListenerMapTest::TestRemovingByListener(
+ const EventListenerConstructor& constructor) {
+ listeners_->AddListener(constructor.Run(
+ kEvent1Name, NULL, CreateHostSuffixFilter("google.com").release()));
+
+ listeners_->AddListener(
+ constructor.Run(kEvent1Name,
+ process_.get(),
+ CreateHostSuffixFilter("google.com").release()));
+
+ scoped_ptr<EventListener> listener(
+ constructor.Run(kEvent1Name,
+ process_.get(),
+ CreateHostSuffixFilter("google.com").release()));
listeners_->RemoveListener(listener.get());
scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
event->filter_info.SetURL(GURL("http://www.google.com"));
- std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
- ASSERT_EQ(1u, targets.size());
+ ASSERT_EQ(1u, listeners_->GetEventListeners(*event).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"))));
+TEST_F(EventListenerMapTest, TestRemovingByListenerForExtension) {
+ TestRemovingByListener(base::Bind(&CreateEventListenerForExtension, kExt1Id));
+}
- scoped_ptr<EventListener> listener(new EventListener(
- kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+TEST_F(EventListenerMapTest, TestRemovingByListenerForURL) {
+ TestRemovingByListener(base::Bind(&CreateEventListenerForURL, GURL(kURL)));
+}
+
+TEST_F(EventListenerMapTest, TestLazyDoubleAddIsUndoneByRemove) {
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+
+ scoped_ptr<EventListener> listener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
listeners_->RemoveListener(listener.get());
scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
@@ -168,17 +235,17 @@
ASSERT_EQ(0u, targets.size());
}
-TEST_F(EventListenerMapUnittest, HostSuffixFilterEquality) {
+TEST_F(EventListenerMapTest, 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"))));
+TEST_F(EventListenerMapTest, RemoveLazyListeners) {
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent2Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
listeners_->RemoveLazyListenersForExtension(kExt1Id);
@@ -192,29 +259,25 @@
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"))));
+TEST_F(EventListenerMapTest, AddExistingFilteredListener) {
+ bool first_new = listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+ bool second_new = listeners_->AddListener(EventListener::ForExtension(
+ 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>())));
+void EventListenerMapTest::TestAddExistingUnfilteredListener(
+ const EventListenerConstructor& constructor) {
+ bool first_add = listeners_->AddListener(
+ constructor.Run(kEvent1Name, NULL, new DictionaryValue()));
+ bool second_add = listeners_->AddListener(
+ constructor.Run(kEvent1Name, NULL, new DictionaryValue()));
scoped_ptr<EventListener> listener(
- new EventListener(kEvent1Name, kExt1Id, NULL,
- scoped_ptr<DictionaryValue>()));
+ constructor.Run(kEvent1Name, NULL, new DictionaryValue()));
bool first_remove = listeners_->RemoveListener(listener.get());
bool second_remove = listeners_->RemoveListener(listener.get());
@@ -224,20 +287,31 @@
ASSERT_FALSE(second_remove);
}
-TEST_F(EventListenerMapUnittest, RemovingRouters) {
- listeners_->AddListener(scoped_ptr<EventListener>(
- new EventListener(kEvent1Name, kExt1Id, process_.get(),
- scoped_ptr<DictionaryValue>())));
+TEST_F(EventListenerMapTest, AddExistingUnfilteredListenerForExtensions) {
+ TestAddExistingUnfilteredListener(
+ base::Bind(&CreateEventListenerForExtension, kExt1Id));
+}
+
+TEST_F(EventListenerMapTest, AddExistingUnfilteredListenerForURLs) {
+ TestAddExistingUnfilteredListener(
+ base::Bind(&CreateEventListenerForURL, GURL(kURL)));
+}
+
+TEST_F(EventListenerMapTest, RemovingRouters) {
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, process_.get(), scoped_ptr<DictionaryValue>()));
+ listeners_->AddListener(EventListener::ForURL(
+ kEvent1Name, GURL(kURL), process_.get(), scoped_ptr<DictionaryValue>()));
listeners_->RemoveListenersForProcess(process_.get());
ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent1Name));
}
-TEST_F(EventListenerMapUnittest, HasListenerForEvent) {
+void EventListenerMapTest::TestHasListenerForEvent(
+ const EventListenerConstructor& constructor) {
ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent1Name));
- listeners_->AddListener(scoped_ptr<EventListener>(
- new EventListener(kEvent1Name, kExt1Id, process_.get(),
- scoped_ptr<DictionaryValue>())));
+ listeners_->AddListener(
+ constructor.Run(kEvent1Name, process_.get(), new DictionaryValue()));
ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent2Name));
ASSERT_TRUE(listeners_->HasListenerForEvent(kEvent1Name));
@@ -245,17 +319,24 @@
ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent1Name));
}
-TEST_F(EventListenerMapUnittest, HasListenerForExtension) {
+TEST_F(EventListenerMapTest, HasListenerForEventForExtension) {
+ TestHasListenerForEvent(
+ base::Bind(&CreateEventListenerForExtension, kExt1Id));
+}
+
+TEST_F(EventListenerMapTest, HasListenerForEventForURL) {
+ TestHasListenerForEvent(base::Bind(&CreateEventListenerForURL, GURL(kURL)));
+}
+
+TEST_F(EventListenerMapTest, HasListenerForExtension) {
ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
// Non-lazy listener.
- listeners_->AddListener(scoped_ptr<EventListener>(
- new EventListener(kEvent1Name, kExt1Id, process_.get(),
- scoped_ptr<DictionaryValue>())));
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, process_.get(), scoped_ptr<DictionaryValue>()));
// Lazy listener.
- listeners_->AddListener(scoped_ptr<EventListener>(
- new EventListener(kEvent1Name, kExt1Id, NULL,
- scoped_ptr<DictionaryValue>())));
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, scoped_ptr<DictionaryValue>()));
ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent2Name));
ASSERT_TRUE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
@@ -266,7 +347,7 @@
ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
}
-TEST_F(EventListenerMapUnittest, AddLazyListenersFromPreferences) {
+TEST_F(EventListenerMapTest, AddLazyListenersFromPreferences) {
scoped_ptr<DictionaryValue> filter1(CreateHostSuffixFilter("google.com"));
scoped_ptr<DictionaryValue> filter2(CreateHostSuffixFilter("yahoo.com"));
@@ -283,12 +364,12 @@
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")));
+ scoped_ptr<EventListener> listener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
ASSERT_TRUE((*targets.begin())->Equals(listener.get()));
}
-TEST_F(EventListenerMapUnittest, CorruptedExtensionPrefsShouldntCrash) {
+TEST_F(EventListenerMapTest, CorruptedExtensionPrefsShouldntCrash) {
scoped_ptr<DictionaryValue> filter1(CreateHostSuffixFilter("google.com"));
DictionaryValue filtered_listeners;
diff --git a/extensions/browser/event_router.cc b/extensions/browser/event_router.cc
index 0efddb7..21985b1 100644
--- a/extensions/browser/event_router.cc
+++ b/extensions/browser/event_router.cc
@@ -188,16 +188,31 @@
void EventRouter::AddEventListener(const std::string& event_name,
content::RenderProcessHost* process,
const std::string& extension_id) {
- listeners_.AddListener(scoped_ptr<EventListener>(new EventListener(
- event_name, extension_id, process, scoped_ptr<DictionaryValue>())));
+ listeners_.AddListener(EventListener::ForExtension(
+ event_name, extension_id, process, scoped_ptr<DictionaryValue>()));
}
void EventRouter::RemoveEventListener(const std::string& event_name,
content::RenderProcessHost* process,
const std::string& extension_id) {
- EventListener listener(event_name, extension_id, process,
- scoped_ptr<DictionaryValue>());
- listeners_.RemoveListener(&listener);
+ scoped_ptr<EventListener> listener = EventListener::ForExtension(
+ event_name, extension_id, process, scoped_ptr<DictionaryValue>());
+ listeners_.RemoveListener(listener.get());
+}
+
+void EventRouter::AddEventListenerForURL(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const GURL& listener_url) {
+ listeners_.AddListener(EventListener::ForURL(
+ event_name, listener_url, process, scoped_ptr<DictionaryValue>()));
+}
+
+void EventRouter::RemoveEventListenerForURL(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const GURL& listener_url) {
+ scoped_ptr<EventListener> listener = EventListener::ForURL(
+ event_name, listener_url, process, scoped_ptr<DictionaryValue>());
+ listeners_.RemoveListener(listener.get());
}
void EventRouter::RegisterObserver(Observer* observer,
@@ -221,6 +236,7 @@
void EventRouter::OnListenerAdded(const EventListener* listener) {
const EventListenerInfo details(listener->event_name(),
listener->extension_id(),
+ listener->listener_url(),
listener->GetBrowserContext());
std::string base_event_name = GetBaseEventName(listener->event_name());
ObserverMap::iterator observer = observers_.find(base_event_name);
@@ -231,6 +247,7 @@
void EventRouter::OnListenerRemoved(const EventListener* listener) {
const EventListenerInfo details(listener->event_name(),
listener->extension_id(),
+ listener->listener_url(),
listener->GetBrowserContext());
std::string base_event_name = GetBaseEventName(listener->event_name());
ObserverMap::iterator observer = observers_.find(base_event_name);
@@ -240,9 +257,8 @@
void EventRouter::AddLazyEventListener(const std::string& event_name,
const std::string& extension_id) {
- scoped_ptr<EventListener> listener(new EventListener(
+ bool is_new = listeners_.AddListener(EventListener::ForExtension(
event_name, extension_id, NULL, scoped_ptr<DictionaryValue>()));
- bool is_new = listeners_.AddListener(listener.Pass());
if (is_new) {
std::set<std::string> events = GetRegisteredEvents(extension_id);
@@ -254,9 +270,9 @@
void EventRouter::RemoveLazyEventListener(const std::string& event_name,
const std::string& extension_id) {
- EventListener listener(event_name, extension_id, NULL,
- scoped_ptr<DictionaryValue>());
- bool did_exist = listeners_.RemoveListener(&listener);
+ scoped_ptr<EventListener> listener = EventListener::ForExtension(
+ event_name, extension_id, NULL, scoped_ptr<DictionaryValue>());
+ bool did_exist = listeners_.RemoveListener(listener.get());
if (did_exist) {
std::set<std::string> events = GetRegisteredEvents(extension_id);
@@ -271,14 +287,18 @@
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()))));
+ listeners_.AddListener(EventListener::ForExtension(
+ 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()))));
+ bool added = listeners_.AddListener(EventListener::ForExtension(
+ event_name,
+ extension_id,
+ NULL,
+ scoped_ptr<DictionaryValue>(filter.DeepCopy())));
if (added)
AddFilterToEvent(event_name, extension_id, &filter);
@@ -291,14 +311,17 @@
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()));
+ scoped_ptr<EventListener> listener = EventListener::ForExtension(
+ event_name,
+ extension_id,
+ process,
+ scoped_ptr<DictionaryValue>(filter.DeepCopy()));
- listeners_.RemoveListener(&listener);
+ listeners_.RemoveListener(listener.get());
if (remove_lazy_listener) {
- listener.MakeLazy();
- bool removed = listeners_.RemoveListener(&listener);
+ listener->MakeLazy();
+ bool removed = listeners_.RemoveListener(listener.get());
if (removed)
RemoveFilterFromEvent(event_name, extension_id, &filter);
@@ -471,8 +494,10 @@
EventDispatchIdentifier dispatch_id(listener->GetBrowserContext(),
listener->extension_id());
if (!ContainsKey(already_dispatched, dispatch_id)) {
- DispatchEventToProcess(
- listener->extension_id(), listener->process(), event);
+ DispatchEventToProcess(listener->extension_id(),
+ listener->listener_url(),
+ listener->process(),
+ event);
}
}
}
@@ -511,6 +536,7 @@
}
void EventRouter::DispatchEventToProcess(const std::string& extension_id,
+ const GURL& listener_url,
content::RenderProcessHost* process,
const linked_ptr<Event>& event) {
BrowserContext* listener_context = process->GetBrowserContext();
@@ -556,7 +582,7 @@
->HasWebUIBindings(process->GetID())) {
// Dispatching event to WebUI.
if (!ExtensionAPI::GetSharedInstance()->IsAvailableToWebUI(
- event->event_name)) {
+ event->event_name, listener_url)) {
return;
}
} else {
@@ -691,8 +717,9 @@
if (listeners_.HasProcessListener(host->render_process_host(),
host->extension()->id())) {
- DispatchEventToProcess(host->extension()->id(),
- host->render_process_host(), event);
+ // URL events cannot be lazy therefore can't be pending, hence the GURL().
+ DispatchEventToProcess(
+ host->extension()->id(), GURL(), host->render_process_host(), event);
}
}
@@ -800,9 +827,12 @@
EventListenerInfo::EventListenerInfo(const std::string& event_name,
const std::string& extension_id,
+ const GURL& listener_url,
content::BrowserContext* browser_context)
: event_name(event_name),
extension_id(extension_id),
- browser_context(browser_context) {}
+ listener_url(listener_url),
+ browser_context(browser_context) {
+}
} // namespace extensions
diff --git a/extensions/browser/event_router.h b/extensions/browser/event_router.h
index a69d0422..c8c5226 100644
--- a/extensions/browser/event_router.h
+++ b/extensions/browser/event_router.h
@@ -13,7 +13,6 @@
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/containers/hash_tables.h"
-#include "base/gtest_prod_util.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/values.h"
@@ -93,7 +92,8 @@
ExtensionPrefs* extension_prefs);
virtual ~EventRouter();
- // Add or remove the process/extension pair as a listener for |event_name|.
+ // Add or remove an extension as an event listener for |event_name|.
+ //
// Note that multiple extensions can share a process due to process
// collapsing. Also, a single extension can have 2 processes if it is a split
// mode extension.
@@ -104,6 +104,14 @@
content::RenderProcessHost* process,
const std::string& extension_id);
+ // Add or remove a URL as an event listener for |event_name|.
+ void AddEventListenerForURL(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const GURL& listener_url);
+ void RemoveEventListenerForURL(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const GURL& listener_url);
+
EventListenerMap& listeners() { return listeners_; }
// Registers an observer to be notified when an event listener for
@@ -171,7 +179,7 @@
const std::string& extension_id);
private:
- FRIEND_TEST_ALL_PREFIXES(EventRouterTest, EventRouterObserver);
+ friend class EventRouterTest;
// The extension and process that contains the event listener for a given
// event.
@@ -221,8 +229,10 @@
const linked_ptr<Event>& event,
std::set<EventDispatchIdentifier>* already_dispatched);
- // Dispatches the event to the specified extension running in |process|.
+ // Dispatches the event to the specified extension or URL running in
+ // |process|.
void DispatchEventToProcess(const std::string& extension_id,
+ const GURL& listener_url,
content::RenderProcessHost* process,
const linked_ptr<Event>& event);
@@ -357,12 +367,14 @@
struct EventListenerInfo {
EventListenerInfo(const std::string& event_name,
const std::string& extension_id,
+ const GURL& listener_url,
content::BrowserContext* browser_context);
// The event name including any sub-event, e.g. "runtime.onStartup" or
// "webRequest.onCompleted/123".
const std::string event_name;
const std::string extension_id;
+ const GURL listener_url;
content::BrowserContext* browser_context;
};
diff --git a/extensions/browser/event_router_unittest.cc b/extensions/browser/event_router_unittest.cc
index c47b7f20..aa068ced 100644
--- a/extensions/browser/event_router_unittest.cc
+++ b/extensions/browser/event_router_unittest.cc
@@ -6,6 +6,7 @@
#include <string>
+#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/memory/scoped_ptr.h"
#include "base/values.h"
@@ -53,9 +54,37 @@
DISALLOW_COPY_AND_ASSIGN(MockEventRouterObserver);
};
+typedef base::Callback<scoped_ptr<EventListener>(
+ const std::string&, // event_name
+ content::RenderProcessHost*, // process
+ base::DictionaryValue* // filter (takes ownership)
+ )> EventListenerConstructor;
+
+scoped_ptr<EventListener> CreateEventListenerForExtension(
+ const std::string& extension_id,
+ const std::string& event_name,
+ content::RenderProcessHost* process,
+ base::DictionaryValue* filter) {
+ return EventListener::ForExtension(
+ event_name, extension_id, process, make_scoped_ptr(filter));
+}
+
+scoped_ptr<EventListener> CreateEventListenerForURL(
+ const GURL& listener_url,
+ const std::string& event_name,
+ content::RenderProcessHost* process,
+ base::DictionaryValue* filter) {
+ return EventListener::ForURL(
+ event_name, listener_url, process, make_scoped_ptr(filter));
+}
+
} // namespace
-typedef testing::Test EventRouterTest;
+class EventRouterTest : public testing::Test {
+ protected:
+ // Tests adding and removing observers from EventRouter.
+ void RunEventRouterObserverTest(const EventListenerConstructor& constructor);
+};
TEST_F(EventRouterTest, GetBaseEventName) {
// Normal event names are passed through unchanged.
@@ -66,14 +95,15 @@
}
// Tests adding and removing observers from EventRouter.
-TEST_F(EventRouterTest, EventRouterObserver) {
+void EventRouterTest::RunEventRouterObserverTest(
+ const EventListenerConstructor& constructor) {
EventRouter router(NULL, NULL);
- EventListener listener(
- "event_name", "extension_id", NULL, scoped_ptr<base::DictionaryValue>());
+ scoped_ptr<EventListener> listener =
+ constructor.Run("event_name", NULL, new base::DictionaryValue());
// Add/remove works without any observers.
- router.OnListenerAdded(&listener);
- router.OnListenerRemoved(&listener);
+ router.OnListenerAdded(listener.get());
+ router.OnListenerRemoved(listener.get());
// Register observers that both match and don't match the event above.
MockEventRouterObserver matching_observer;
@@ -82,43 +112,51 @@
router.RegisterObserver(&non_matching_observer, "other");
// Adding a listener notifies the appropriate observers.
- router.OnListenerAdded(&listener);
+ router.OnListenerAdded(listener.get());
EXPECT_EQ(1, matching_observer.listener_added_count());
EXPECT_EQ(0, non_matching_observer.listener_added_count());
// Removing a listener notifies the appropriate observers.
- router.OnListenerRemoved(&listener);
+ router.OnListenerRemoved(listener.get());
EXPECT_EQ(1, matching_observer.listener_removed_count());
EXPECT_EQ(0, non_matching_observer.listener_removed_count());
// Adding the listener again notifies again.
- router.OnListenerAdded(&listener);
+ router.OnListenerAdded(listener.get());
EXPECT_EQ(2, matching_observer.listener_added_count());
EXPECT_EQ(0, non_matching_observer.listener_added_count());
// Removing the listener again notifies again.
- router.OnListenerRemoved(&listener);
+ router.OnListenerRemoved(listener.get());
EXPECT_EQ(2, matching_observer.listener_removed_count());
EXPECT_EQ(0, non_matching_observer.listener_removed_count());
// Adding a listener with a sub-event notifies the main observer with
// proper details.
matching_observer.Reset();
- EventListener sub_event_listener("event_name/1",
- "extension_id",
- NULL,
- scoped_ptr<base::DictionaryValue>());
- router.OnListenerAdded(&sub_event_listener);
+ scoped_ptr<EventListener> sub_event_listener =
+ constructor.Run("event_name/1", NULL, new base::DictionaryValue());
+ router.OnListenerAdded(sub_event_listener.get());
EXPECT_EQ(1, matching_observer.listener_added_count());
EXPECT_EQ(0, matching_observer.listener_removed_count());
EXPECT_EQ("event_name/1", matching_observer.last_event_name());
// Ditto for removing the listener.
matching_observer.Reset();
- router.OnListenerRemoved(&sub_event_listener);
+ router.OnListenerRemoved(sub_event_listener.get());
EXPECT_EQ(0, matching_observer.listener_added_count());
EXPECT_EQ(1, matching_observer.listener_removed_count());
EXPECT_EQ("event_name/1", matching_observer.last_event_name());
}
+TEST_F(EventRouterTest, EventRouterObserverForExtensions) {
+ RunEventRouterObserverTest(
+ base::Bind(&CreateEventListenerForExtension, "extension_id"));
+}
+
+TEST_F(EventRouterTest, EventRouterObserverForURLs) {
+ RunEventRouterObserverTest(
+ base::Bind(&CreateEventListenerForURL, GURL("http://google.com/path")));
+}
+
} // namespace extensions
diff --git a/extensions/browser/extension_function_dispatcher.cc b/extensions/browser/extension_function_dispatcher.cc
index 9701f03..ee9651c 100644
--- a/extensions/browser/extension_function_dispatcher.cc
+++ b/extensions/browser/extension_function_dispatcher.cc
@@ -486,7 +486,7 @@
} else if (content::ChildProcessSecurityPolicy::GetInstance()
->HasWebUIBindings(requesting_process_id)) {
// WebUI is calling this API.
- if (!api->IsAvailableToWebUI(params.name)) {
+ if (!api->IsAvailableToWebUI(params.name, params.source_url)) {
disallowed_reason = "WebUI can only call webui-enabled APIs";
}
} else {
diff --git a/extensions/browser/extension_message_filter.cc b/extensions/browser/extension_message_filter.cc
index f7e794b..c9c6c9b 100644
--- a/extensions/browser/extension_message_filter.cc
+++ b/extensions/browser/extension_message_filter.cc
@@ -13,6 +13,7 @@
#include "extensions/browser/extension_system.h"
#include "extensions/browser/info_map.h"
#include "extensions/browser/process_manager.h"
+#include "extensions/common/extension.h"
#include "extensions/common/extension_messages.h"
#include "ipc/ipc_message_macros.h"
@@ -95,6 +96,7 @@
void ExtensionMessageFilter::OnExtensionAddListener(
const std::string& extension_id,
+ const GURL& listener_url,
const std::string& event_name) {
RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
if (!process)
@@ -102,11 +104,20 @@
EventRouter* router = EventRouter::Get(browser_context_);
if (!router)
return;
- router->AddEventListener(event_name, process, extension_id);
+
+ if (Extension::IdIsValid(extension_id)) {
+ router->AddEventListener(event_name, process, extension_id);
+ } else if (listener_url.is_valid()) {
+ router->AddEventListenerForURL(event_name, process, listener_url);
+ } else {
+ NOTREACHED() << "Tried to add an event listener without a valid "
+ << "extension ID nor listener URL";
+ }
}
void ExtensionMessageFilter::OnExtensionRemoveListener(
const std::string& extension_id,
+ const GURL& listener_url,
const std::string& event_name) {
RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
if (!process)
@@ -114,7 +125,15 @@
EventRouter* router = EventRouter::Get(browser_context_);
if (!router)
return;
- router->RemoveEventListener(event_name, process, extension_id);
+
+ if (Extension::IdIsValid(extension_id)) {
+ router->RemoveEventListener(event_name, process, extension_id);
+ } else if (listener_url.is_valid()) {
+ router->RemoveEventListenerForURL(event_name, process, listener_url);
+ } else {
+ NOTREACHED() << "Tried to remove an event listener without a valid "
+ << "extension ID nor listener URL";
+ }
}
void ExtensionMessageFilter::OnExtensionAddLazyListener(
diff --git a/extensions/browser/extension_message_filter.h b/extensions/browser/extension_message_filter.h
index 1ed785e5..c45fb03 100644
--- a/extensions/browser/extension_message_filter.h
+++ b/extensions/browser/extension_message_filter.h
@@ -12,6 +12,7 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "content/public/browser/browser_message_filter.h"
+#include "url/gurl.h"
struct ExtensionHostMsg_Request_Params;
@@ -52,8 +53,10 @@
// Message handlers on the UI thread.
void OnExtensionAddListener(const std::string& extension_id,
+ const GURL& listener_url,
const std::string& event_name);
void OnExtensionRemoveListener(const std::string& extension_id,
+ const GURL& listener_url,
const std::string& event_name);
void OnExtensionAddLazyListener(const std::string& extension_id,
const std::string& event_name);
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index fbabf29..4555a10 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -122,18 +122,21 @@
"dependencies": ["permission:storage"],
"contexts": ["blessed_extension", "unblessed_extension", "content_script"]
},
- "test": {
+ "test": [{
"internal": true,
"channel": "stable",
"extension_types": "all",
- // Everything except web pages.
- "contexts": [
- "blessed_extension",
- "content_script",
- "unblessed_extension",
- "webui"
+ // Everything except web pages and WebUI. WebUI is declared in a separate
+ // rule to keep the "matches" property isolated.
+ "contexts": ["blessed_extension", "content_script", "unblessed_extension"]
+ }, {
+ "internal": true,
+ "channel": "stable",
+ "contexts": ["webui"],
+ "matches": [
+ "chrome://extensions-frame/*"
]
- },
+ }],
"types": {
"channel": "stable",
"extension_types": ["extension", "legacy_packaged_app", "platform_app"],
diff --git a/extensions/common/extension_api.cc b/extensions/common/extension_api.cc
index 3f17488..d10084c 100644
--- a/extensions/common/extension_api.cc
+++ b/extensions/common/extension_api.cc
@@ -303,8 +303,9 @@
.is_available();
}
-bool ExtensionAPI::IsAvailableToWebUI(const std::string& name) {
- return IsAvailable(name, NULL, Feature::WEBUI_CONTEXT, GURL()).is_available();
+bool ExtensionAPI::IsAvailableToWebUI(const std::string& name,
+ const GURL& url) {
+ return IsAvailable(name, NULL, Feature::WEBUI_CONTEXT, url).is_available();
}
const base::DictionaryValue* ExtensionAPI::GetSchema(
diff --git a/extensions/common/extension_api.h b/extensions/common/extension_api.h
index 8ce0245..d03c339 100644
--- a/extensions/common/extension_api.h
+++ b/extensions/common/extension_api.h
@@ -109,8 +109,8 @@
bool IsAvailableInUntrustedContext(const std::string& name,
const Extension* extension);
- // Returns true if |name| is available to webui contexts.
- bool IsAvailableToWebUI(const std::string& name);
+ // Returns true if |name| is available to WebUI contexts on |url|.
+ bool IsAvailableToWebUI(const std::string& name, const GURL& url);
// Gets the schema for the extension API with namespace |full_name|.
// Ownership remains with this object.
diff --git a/extensions/common/extension_api_stub.cc b/extensions/common/extension_api_stub.cc
index 439b7e1..4b44f97 100644
--- a/extensions/common/extension_api_stub.cc
+++ b/extensions/common/extension_api_stub.cc
@@ -8,6 +8,7 @@
#include "extensions/common/extension_api.h"
#include "extensions/common/features/feature.h"
+#include "url/gurl.h"
namespace extensions {
@@ -41,7 +42,8 @@
return false;
}
-bool ExtensionAPI::IsAvailableToWebUI(const std::string& name) {
+bool ExtensionAPI::IsAvailableToWebUI(const std::string& name,
+ const GURL& url) {
return false;
}
diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h
index e3154ca..5878603 100644
--- a/extensions/common/extension_messages.h
+++ b/extensions/common/extension_messages.h
@@ -507,14 +507,16 @@
ExtensionHostMsg_Request_Params)
// Notify the browser that the given extension added a listener to an event.
-IPC_MESSAGE_CONTROL2(ExtensionHostMsg_AddListener,
+IPC_MESSAGE_CONTROL3(ExtensionHostMsg_AddListener,
std::string /* extension_id */,
+ GURL /* listener_url */,
std::string /* name */)
// Notify the browser that the given extension removed a listener from an
// event.
-IPC_MESSAGE_CONTROL2(ExtensionHostMsg_RemoveListener,
+IPC_MESSAGE_CONTROL3(ExtensionHostMsg_RemoveListener,
std::string /* extension_id */,
+ GURL /* listener_url */,
std::string /* name */)
// Notify the browser that the given extension added a listener to an event from
diff --git a/extensions/common/features/simple_feature.cc b/extensions/common/features/simple_feature.cc
index bdf8fae..e623bcf 100644
--- a/extensions/common/features/simple_feature.cc
+++ b/extensions/common/features/simple_feature.cc
@@ -291,8 +291,8 @@
&component_extensions_auto_granted_);
// NOTE: ideally we'd sanity check that "matches" can be specified if and
- // only if there's a "web_page" context, but without (Simple)Features being
- // aware of their own heirarchy this is impossible.
+ // only if there's a "web_page" or "webui" context, but without
+ // (Simple)Features being aware of their own heirarchy this is impossible.
//
// For example, we might have feature "foo" available to "web_page" context
// and "matches" google.com/*. Then a sub-feature "foo.bar" might override
@@ -402,8 +402,13 @@
if (!contexts_.empty() && contexts_.find(context) == contexts_.end())
return CreateAvailability(INVALID_CONTEXT, context);
- if (context == WEB_PAGE_CONTEXT && !matches_.MatchesURL(url))
+ // TODO(kalman): Consider checking |matches_| regardless of context type.
+ // Fewer surprises, and if the feature configuration wants to isolate
+ // "matches" from say "blessed_extension" then they can use complex features.
+ if ((context == WEB_PAGE_CONTEXT || context == WEBUI_CONTEXT) &&
+ !matches_.MatchesURL(url)) {
return CreateAvailability(INVALID_URL, url);
+ }
for (FilterList::const_iterator filter_iter = filters_.begin();
filter_iter != filters_.end();
@@ -414,6 +419,8 @@
return availability;
}
+ // TODO(kalman): Assert that if the context was a webpage or WebUI context
+ // then at some point a "matches" restriction was checked.
return CheckDependencies(base::Bind(
&IsAvailableToContextForBind, extension, context, url, platform));
}
diff --git a/extensions/renderer/event_bindings.cc b/extensions/renderer/event_bindings.cc
index 331e6512..948d3ded 100644
--- a/extensions/renderer/event_bindings.cc
+++ b/extensions/renderer/event_bindings.cc
@@ -150,8 +150,8 @@
std::string extension_id = context()->GetExtensionID();
EventListenerCounts& listener_counts = g_listener_counts.Get()[extension_id];
if (++listener_counts[event_name] == 1) {
- content::RenderThread::Get()->Send(
- new ExtensionHostMsg_AddListener(extension_id, event_name));
+ content::RenderThread::Get()->Send(new ExtensionHostMsg_AddListener(
+ extension_id, context()->GetURL(), event_name));
}
// This is called the first time the page has added a listener. Since
@@ -177,8 +177,8 @@
EventListenerCounts& listener_counts = g_listener_counts.Get()[extension_id];
if (--listener_counts[event_name] == 0) {
- content::RenderThread::Get()->Send(
- new ExtensionHostMsg_RemoveListener(extension_id, event_name));
+ content::RenderThread::Get()->Send(new ExtensionHostMsg_RemoveListener(
+ extension_id, context()->GetURL(), event_name));
}
// DetachEvent is called when the last listener for the context is
diff --git a/extensions/renderer/script_context.cc b/extensions/renderer/script_context.cc
index 97e6c76..0f7a77c5 100644
--- a/extensions/renderer/script_context.cc
+++ b/extensions/renderer/script_context.cc
@@ -169,8 +169,7 @@
blink::WebDataSource* data_source = frame->provisionalDataSource()
? frame->provisionalDataSource()
: frame->dataSource();
- CHECK(data_source);
- return GURL(data_source->request().url());
+ return data_source ? GURL(data_source->request().url()) : GURL();
}
// static
diff --git a/extensions/renderer/script_context.h b/extensions/renderer/script_context.h
index 7b19ac1..db7130a2 100644
--- a/extensions/renderer/script_context.h
+++ b/extensions/renderer/script_context.h
@@ -104,6 +104,7 @@
// Utility to get the URL we will match against for a frame. If the frame has
// committed, this is the commited URL. Otherwise it is the provisional URL.
+ // The returned URL may be invalid.
static GURL GetDataSourceURLForFrame(const blink::WebFrame* frame);
// Returns the first non-about:-URL in the document hierarchy above and