blob: 23291f76389fe865fba76b5d4484b6407bf8459d [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/event_router.h"
#include <memory>
#include <string>
#include <utility>
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/values.h"
#include "build/build_config.h"
#include "content/public/browser/browser_context.h"
#include "content/public/test/mock_render_process_host.h"
#include "extensions/browser/event_listener_map.h"
#include "extensions/browser/extensions_test.h"
#include "extensions/browser/process_map.h"
#include "extensions/browser/process_map_factory.h"
#include "extensions/browser/test_event_router_observer.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension_api.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/features/feature_provider.h"
#include "extensions/common/features/simple_feature.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/common/mojom/event_dispatcher.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom-blink-forward.h"
using base::Value;
namespace extensions {
namespace {
// A simple mock to keep track of listener additions and removals.
class MockEventRouterObserver : public EventRouter::Observer {
public:
MockEventRouterObserver()
: listener_added_count_(0),
listener_removed_count_(0) {}
MockEventRouterObserver(const MockEventRouterObserver&) = delete;
MockEventRouterObserver& operator=(const MockEventRouterObserver&) = delete;
~MockEventRouterObserver() override {}
int listener_added_count() const { return listener_added_count_; }
int listener_removed_count() const { return listener_removed_count_; }
const std::string& last_event_name() const { return last_event_name_; }
void Reset() {
listener_added_count_ = 0;
listener_removed_count_ = 0;
last_event_name_.clear();
}
// EventRouter::Observer overrides:
void OnListenerAdded(const EventListenerInfo& details) override {
listener_added_count_++;
last_event_name_ = details.event_name;
}
void OnListenerRemoved(const EventListenerInfo& details) override {
listener_removed_count_++;
last_event_name_ = details.event_name;
}
private:
int listener_added_count_;
int listener_removed_count_;
std::string last_event_name_;
};
class MockEventDispatcher : public mojom::EventDispatcher {
public:
MockEventDispatcher() = default;
~MockEventDispatcher() override = default;
mojo::PendingAssociatedRemote<mojom::EventDispatcher> BindAndPassRemote() {
return receiver_.BindNewEndpointAndPassDedicatedRemote();
}
// mojom::EventDispatcher:
void DispatchEvent(mojom::DispatchEventParamsPtr params,
base::Value::List event_args,
DispatchEventCallback callback) override {
std::move(callback).Run(
/*event_will_run_in_lazy_background_page_script=*/false);
}
private:
mojo::AssociatedReceiver<mojom::EventDispatcher> receiver_{this};
};
using EventListenerConstructor =
base::RepeatingCallback<std::unique_ptr<EventListener>(
const std::string& /* event_name */,
content::RenderProcessHost* /* process */,
base::Value::Dict /* filter */)>;
std::unique_ptr<EventListener> CreateEventListenerForExtension(
const ExtensionId& extension_id,
const std::string& event_name,
content::RenderProcessHost* process,
base::Value::Dict filter) {
return EventListener::ForExtension(event_name, extension_id, process,
std::move(filter));
}
std::unique_ptr<EventListener> CreateEventListenerForURL(
const GURL& listener_url,
const std::string& event_name,
content::RenderProcessHost* process,
base::Value::Dict filter) {
return EventListener::ForURL(event_name, listener_url, process,
std::move(filter));
}
std::unique_ptr<EventListener> CreateEventListenerForExtensionServiceWorker(
const ExtensionId& extension_id,
int64_t service_worker_version_id,
int worker_thread_id,
const std::string& event_name,
content::RenderProcessHost* process,
base::Value::Dict filter) {
content::BrowserContext* browser_context =
process ? process->GetBrowserContext() : nullptr;
return EventListener::ForExtensionServiceWorker(
event_name, extension_id, process, browser_context,
Extension::GetBaseURLFromExtensionId(extension_id),
service_worker_version_id, worker_thread_id, std::move(filter));
}
// Creates an extension. If |component| is true, it is created as a component
// extension. If |persistent| is true, it is created with a persistent
// background page; otherwise it is created with an event page.
scoped_refptr<const Extension> CreateExtension(bool component,
bool persistent) {
ExtensionBuilder builder;
auto manifest = base::Value::Dict()
.Set("name", "foo")
.Set("version", "1.0.0")
.Set("manifest_version", 2);
manifest.SetByDottedPath("background.page", "background.html");
manifest.SetByDottedPath("background.persistent", persistent);
builder.SetManifest(std::move(manifest));
if (component) {
builder.SetLocation(mojom::ManifestLocation::kComponent);
}
return builder.Build();
}
scoped_refptr<const Extension> CreateServiceWorkerExtension() {
ExtensionBuilder builder;
auto manifest = base::Value::Dict()
.Set("name", "foo")
.Set("version", "1.0.0")
.Set("manifest_version", 2);
manifest.SetByDottedPath("background.service_worker", "worker.js");
builder.SetManifest(std::move(manifest));
return builder.Build();
}
base::Value::Dict CreateHostSuffixFilter(const std::string& suffix) {
base::Value::Dict filter_dict;
filter_dict.Set("hostSuffix", Value(suffix));
base::Value::List filter_list;
filter_list.Append(std::move(filter_dict));
base::Value::Dict filter;
filter.Set("url", std::move(filter_list));
return filter;
}
} // namespace
bool operator<(const EventTarget& e1, const EventTarget& e2) {
return std::tie(e1.extension_id, e1.render_process_id,
e1.service_worker_version_id, e1.worker_thread_id) <
std::tie(e2.extension_id, e2.render_process_id,
e2.service_worker_version_id, e2.worker_thread_id);
}
bool operator==(const EventTarget& e1, const EventTarget& e2) {
return std::tie(e1.extension_id, e1.render_process_id,
e1.service_worker_version_id, e1.worker_thread_id) ==
std::tie(e2.extension_id, e2.render_process_id,
e2.service_worker_version_id, e2.worker_thread_id);
}
std::ostream& operator<<(std::ostream& os, const EventTarget& e) {
return os << "EventTarget{" << e.extension_id << "," << e.render_process_id
<< "," << e.service_worker_version_id << "," << e.worker_thread_id
<< "}";
}
class EventRouterTest : public ExtensionsTest {
public:
EventRouterTest() = default;
EventRouterTest(const EventRouterTest&) = delete;
EventRouterTest& operator=(const EventRouterTest&) = delete;
void SetUp() override {
ExtensionsTest::SetUp();
render_process_host_ =
std::make_unique<content::MockRenderProcessHost>(browser_context());
}
void TearDown() override {
render_process_host_.reset();
ExtensionsTest::TearDown();
}
content::RenderProcessHost* render_process_host() const {
return render_process_host_.get();
}
protected:
// Tests adding and removing observers from EventRouter.
void RunEventRouterObserverTest(const EventListenerConstructor& constructor);
// Tests that the correct counts are recorded for the Extensions.Events
// histograms.
void ExpectHistogramCounts(int dispatch_count,
int component_count,
int persistent_count,
int suspended_count,
int running_count,
int service_worker_count) {
histogram_tester_.ExpectBucketCount("Extensions.Events.Dispatch",
events::HistogramValue::FOR_TEST,
dispatch_count);
histogram_tester_.ExpectBucketCount("Extensions.Events.DispatchToComponent",
events::HistogramValue::FOR_TEST,
component_count);
histogram_tester_.ExpectBucketCount(
"Extensions.Events.DispatchWithPersistentBackgroundPage",
events::HistogramValue::FOR_TEST, persistent_count);
histogram_tester_.ExpectBucketCount(
"Extensions.Events.DispatchWithSuspendedEventPage",
events::HistogramValue::FOR_TEST, suspended_count);
histogram_tester_.ExpectBucketCount(
"Extensions.Events.DispatchWithRunningEventPage",
events::HistogramValue::FOR_TEST, running_count);
histogram_tester_.ExpectBucketCount(
"Extensions.Events.DispatchWithServiceWorkerBackground",
events::HistogramValue::FOR_TEST, service_worker_count);
}
private:
base::HistogramTester histogram_tester_;
std::unique_ptr<content::RenderProcessHost> render_process_host_;
};
class EventRouterFilterTest : public ExtensionsTest,
public testing::WithParamInterface<bool> {
public:
EventRouterFilterTest() = default;
EventRouterFilterTest(const EventRouterFilterTest&) = delete;
EventRouterFilterTest& operator=(const EventRouterFilterTest&) = delete;
void SetUp() override {
ExtensionsTest::SetUp();
render_process_host_ =
std::make_unique<content::MockRenderProcessHost>(browser_context());
ASSERT_TRUE(event_router()); // constructs EventRouter
}
void TearDown() override {
render_process_host_.reset();
ExtensionsTest::TearDown();
}
content::RenderProcessHost* render_process_host() const {
return render_process_host_.get();
}
EventRouter* event_router() { return EventRouter::Get(browser_context()); }
const base::Value::Dict* GetFilteredEvents(const ExtensionId& extension_id) {
return event_router()->GetFilteredEvents(
extension_id, is_for_service_worker()
? EventRouter::RegisteredEventType::kServiceWorker
: EventRouter::RegisteredEventType::kLazy);
}
bool ContainsFilter(const ExtensionId& extension_id,
const std::string& event_name,
const base::Value::Dict& to_check) {
const base::Value::List* filter_list =
GetFilterList(extension_id, event_name);
if (!filter_list) {
ADD_FAILURE();
return false;
}
for (const base::Value& filter : *filter_list) {
if (!filter.is_dict()) {
ADD_FAILURE();
return false;
}
if (filter.GetDict() == to_check) {
return true;
}
}
return false;
}
bool is_for_service_worker() const { return GetParam(); }
private:
const base::Value::List* GetFilterList(const ExtensionId& extension_id,
const std::string& event_name) {
const base::Value::Dict* filtered_events = GetFilteredEvents(extension_id);
const auto iter = filtered_events->begin();
if (iter->first != event_name) {
return nullptr;
}
return iter->second.is_list() ? &iter->second.GetList() : nullptr;
}
std::unique_ptr<content::RenderProcessHost> render_process_host_;
};
TEST_F(EventRouterTest, GetBaseEventName) {
// Normal event names are passed through unchanged.
EXPECT_EQ("foo.onBar", EventRouter::GetBaseEventName("foo.onBar"));
// Sub-events are converted to the part before the slash.
EXPECT_EQ("foo.onBar", EventRouter::GetBaseEventName("foo.onBar/123"));
}
// Tests adding and removing observers from EventRouter.
void EventRouterTest::RunEventRouterObserverTest(
const EventListenerConstructor& constructor) {
EventRouter router(browser_context(), nullptr);
std::unique_ptr<EventListener> listener =
constructor.Run("event_name", render_process_host(), base::Value::Dict());
// Add/remove works without any observers.
router.OnListenerAdded(listener.get());
router.OnListenerRemoved(listener.get());
// Register observers that both match and don't match the event above.
MockEventRouterObserver matching_observer;
router.RegisterObserver(&matching_observer, "event_name");
MockEventRouterObserver non_matching_observer;
router.RegisterObserver(&non_matching_observer, "other");
// Adding a listener notifies the appropriate observers.
router.OnListenerAdded(listener.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.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.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.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();
std::unique_ptr<EventListener> sub_event_listener = constructor.Run(
"event_name/1", render_process_host(), base::Value::Dict());
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.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::BindRepeating(&CreateEventListenerForExtension, "extension_id"));
}
TEST_F(EventRouterTest, EventRouterObserverForURLs) {
RunEventRouterObserverTest(base::BindRepeating(
&CreateEventListenerForURL, GURL("http://google.com/path")));
}
TEST_F(EventRouterTest, EventRouterObserverForServiceWorkers) {
RunEventRouterObserverTest(base::BindRepeating(
&CreateEventListenerForExtensionServiceWorker, "extension_id",
// Dummy version_id and thread_id.
99, 199));
}
namespace {
// Tracks event dispatches to a specific process.
class EventRouterObserver : public EventRouter::TestObserver {
public:
// Only counts events that match |process_id|.
explicit EventRouterObserver(int process_id) : process_id_(process_id) {}
void OnWillDispatchEvent(const Event& event) override {
// Do nothing.
}
void OnDidDispatchEventToProcess(const Event& event,
int process_id) override {
if (process_id == process_id_) {
++dispatch_count;
}
}
int dispatch_count = 0;
const int process_id_;
};
// A fake that pretends that all contexts are WebUI.
class ProcessMapFake : public ProcessMap {
public:
explicit ProcessMapFake(content::BrowserContext* browser_context)
: ProcessMap(browser_context) {}
mojom::ContextType GetMostLikelyContextType(const Extension* extension,
int process_id,
const GURL* url) const override {
return mojom::ContextType::kWebUi;
}
};
std::unique_ptr<KeyedService> BuildProcessMap(
content::BrowserContext* profile) {
return std::make_unique<ProcessMapFake>(profile);
}
} // namespace
TEST_F(EventRouterTest, WebUIEventsDoNotCrossIncognitoBoundaries) {
// Override ProcessMap to allow routing to WebUI.
ProcessMapFactory::GetInstance()->SetTestingFactory(
browser_context(), base::BindRepeating(&BuildProcessMap));
ProcessMapFactory::GetInstance()->SetTestingFactory(
incognito_context(), base::BindRepeating(&BuildProcessMap));
// Create a SimpleFeature to allow this API call to be routed to our test URL.
std::string event_name = "testapi.onEvent";
FeatureProvider provider;
auto feature = std::make_unique<SimpleFeature>();
feature->set_name("test feature");
feature->set_matches({"chrome://settings/*"});
provider.AddFeature(event_name, std::move(feature));
ExtensionAPI api;
api.RegisterDependencyProvider("api", &provider);
ExtensionAPI::OverrideSharedInstanceForTest scope(&api);
EventRouter router(browser_context(), nullptr);
content::MockRenderProcessHost regular_rph(browser_context());
content::MockRenderProcessHost otr_rph(incognito_context());
// Add event listeners, as if we had created two real WebUIs, one in a regular
// profile and one in an otr profile. Note that the string chrome://settings
// is hardcoded into the api permissions of settingsPrivate.
GURL dummy_url("chrome://settings/test");
router.AddEventListenerForURL(event_name, &regular_rph, dummy_url);
router.AddEventListenerForURL(event_name, &otr_rph, dummy_url);
// Hook up some test observers
EventRouterObserver regular_counter(regular_rph.GetDeprecatedID());
router.AddObserverForTesting(&regular_counter);
EventRouterObserver otr_counter(otr_rph.GetDeprecatedID());
router.AddObserverForTesting(&otr_counter);
EXPECT_EQ(0, regular_counter.dispatch_count);
EXPECT_EQ(0, otr_counter.dispatch_count);
// Sending an otr event should not trigger the regular observer.
auto otr_event =
std::make_unique<Event>(extensions::events::FOR_TEST, event_name,
base::Value::List(), incognito_context());
router.BroadcastEvent(std::move(otr_event));
EXPECT_EQ(0, regular_counter.dispatch_count);
EXPECT_EQ(1, otr_counter.dispatch_count);
// Setting a regular event should not trigger the otr observer.
std::unique_ptr<Event> regular_event =
std::make_unique<Event>(extensions::events::FOR_TEST, event_name,
base::Value::List(), browser_context());
router.BroadcastEvent(std::move(regular_event));
EXPECT_EQ(1, regular_counter.dispatch_count);
EXPECT_EQ(1, otr_counter.dispatch_count);
}
TEST_F(EventRouterTest, MultipleEventRouterObserver) {
EventRouter router(browser_context(), nullptr);
std::unique_ptr<EventListener> listener =
EventListener::ForURL("event_name", GURL("http://google.com/path"),
render_process_host(), base::Value::Dict());
// Add/remove works without any observers.
router.OnListenerAdded(listener.get());
router.OnListenerRemoved(listener.get());
// Register two observers for same event name.
MockEventRouterObserver matching_observer1;
router.RegisterObserver(&matching_observer1, "event_name");
MockEventRouterObserver matching_observer2;
router.RegisterObserver(&matching_observer2, "event_name");
// Adding a listener notifies the appropriate observers.
router.OnListenerAdded(listener.get());
EXPECT_EQ(1, matching_observer1.listener_added_count());
EXPECT_EQ(1, matching_observer2.listener_added_count());
// Removing a listener notifies the appropriate observers.
router.OnListenerRemoved(listener.get());
EXPECT_EQ(1, matching_observer1.listener_removed_count());
EXPECT_EQ(1, matching_observer2.listener_removed_count());
// Unregister the observer so that the current observer no longer receives
// monitoring, but the other observer still continues to receive monitoring.
router.UnregisterObserver(&matching_observer1);
router.OnListenerAdded(listener.get());
EXPECT_EQ(1, matching_observer1.listener_added_count());
EXPECT_EQ(2, matching_observer2.listener_added_count());
}
TEST_F(EventRouterTest, TestReportEvent) {
EventRouter router(browser_context(), nullptr);
scoped_refptr<const Extension> normal = ExtensionBuilder("Test").Build();
router.ReportEvent(events::HistogramValue::FOR_TEST, normal.get(),
false /** did_enqueue */);
ExpectHistogramCounts(1 /** Dispatch */, 0 /** DispatchToComponent */,
0 /** DispatchWithPersistentBackgroundPage */,
0 /** DispatchWithSuspendedEventPage */,
0 /** DispatchWithRunningEventPage */,
0 /** DispatchWithServiceWorkerBackground */);
scoped_refptr<const Extension> component =
CreateExtension(true /** component */, true /** persistent */);
router.ReportEvent(events::HistogramValue::FOR_TEST, component.get(),
false /** did_enqueue */);
ExpectHistogramCounts(2, 1, 1, 0, 0, 0);
scoped_refptr<const Extension> persistent = CreateExtension(false, true);
router.ReportEvent(events::HistogramValue::FOR_TEST, persistent.get(),
false /** did_enqueue */);
ExpectHistogramCounts(3, 1, 2, 0, 0, 0);
scoped_refptr<const Extension> event = CreateExtension(false, false);
router.ReportEvent(events::HistogramValue::FOR_TEST, event.get(),
false /** did_enqueue */);
ExpectHistogramCounts(4, 1, 2, 0, 1, 0);
router.ReportEvent(events::HistogramValue::FOR_TEST, event.get(),
true /** did_enqueue */);
ExpectHistogramCounts(5, 1, 2, 1, 1, 0);
scoped_refptr<const Extension> component_event = CreateExtension(true, false);
router.ReportEvent(events::HistogramValue::FOR_TEST, component_event.get(),
false /** did_enqueue */);
ExpectHistogramCounts(6, 2, 2, 1, 2, 0);
router.ReportEvent(events::HistogramValue::FOR_TEST, component_event.get(),
true /** did_enqueue */);
ExpectHistogramCounts(7, 3, 2, 2, 2, 0);
scoped_refptr<const Extension> service_worker_extension =
CreateServiceWorkerExtension();
router.ReportEvent(events::HistogramValue::FOR_TEST,
service_worker_extension.get(), true /** did_enqueue */);
ExpectHistogramCounts(8, 3, 2, 2, 2, 1);
}
// Tests adding and removing events with filters.
// TODO(crbug.com/40281129): test is flaky across platforms.
TEST_P(EventRouterFilterTest, DISABLED_Basic) {
// For the purpose of this test, "." is important in |event_name| as it
// exercises the code path that uses |event_name| as a key in
// base::Value::Dict.
const std::string kEventName = "webNavigation.onBeforeNavigate";
const std::string kExtensionId = "mbflcebpggnecokmikipoihdbecnjfoj";
auto param = mojom::EventListenerOwner::NewExtensionId(kExtensionId);
const std::string kHostSuffixes[] = {"foo.com", "bar.com", "baz.com"};
std::unique_ptr<mojom::ServiceWorkerContext> worker_context;
if (is_for_service_worker()) {
worker_context = std::make_unique<mojom::ServiceWorkerContext>(
Extension::GetBaseURLFromExtensionId(kExtensionId),
99, // Dummy version_id.
199); // Dummy thread_id.
}
std::vector<base::Value::Dict> filters;
for (const auto& host_suffix : kHostSuffixes) {
base::Value::Dict filter = CreateHostSuffixFilter(host_suffix);
event_router()->AddFilteredEventListener(
kEventName, render_process_host(), param.Clone(), worker_context.get(),
filter, true);
filters.push_back(std::move(filter));
}
const base::Value::Dict* filtered_events = GetFilteredEvents(kExtensionId);
ASSERT_TRUE(filtered_events);
ASSERT_EQ(1u, filtered_events->size());
const auto iter = filtered_events->begin();
ASSERT_EQ(kEventName, iter->first);
ASSERT_TRUE(iter->second.is_list());
ASSERT_EQ(3u, iter->second.GetList().size());
ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, filters[0]));
ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, filters[1]));
ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, filters[2]));
// Remove the second filter.
event_router()->RemoveFilteredEventListener(
kEventName, render_process_host(), param.Clone(), worker_context.get(),
filters[1], true);
ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, filters[0]));
ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, filters[1]));
ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, filters[2]));
// Remove the first filter.
event_router()->RemoveFilteredEventListener(
kEventName, render_process_host(), param.Clone(), worker_context.get(),
filters[0], true);
ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, filters[0]));
ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, filters[1]));
ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, filters[2]));
// Remove the third filter.
event_router()->RemoveFilteredEventListener(
kEventName, render_process_host(), param.Clone(), worker_context.get(),
filters[2], true);
ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, filters[0]));
ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, filters[1]));
ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, filters[2]));
}
// TODO(crbug.com/40281129): test is flaky across platforms.
TEST_P(EventRouterFilterTest, DISABLED_URLBasedFilteredEventListener) {
const std::string kEventName = "windows.onRemoved";
const GURL kUrl("chrome-untrusted://terminal");
base::Value::Dict filter;
bool lazy = false;
EXPECT_FALSE(event_router()->HasEventListener(kEventName));
event_router()->AddFilteredEventListener(
kEventName, render_process_host(),
mojom::EventListenerOwner::NewListenerUrl(kUrl), nullptr, filter, lazy);
EXPECT_TRUE(event_router()->HasEventListener(kEventName));
event_router()->RemoveFilteredEventListener(
kEventName, render_process_host(),
mojom::EventListenerOwner::NewListenerUrl(kUrl), nullptr, filter, lazy);
EXPECT_FALSE(event_router()->HasEventListener(kEventName));
}
INSTANTIATE_TEST_SUITE_P(Lazy, EventRouterFilterTest, testing::Values(false));
INSTANTIATE_TEST_SUITE_P(ServiceWorker,
EventRouterFilterTest,
testing::Values(true));
class EventRouterDispatchTest : public ExtensionsTest {
public:
EventRouterDispatchTest() = default;
EventRouterDispatchTest(const EventRouterDispatchTest&) = delete;
EventRouterDispatchTest& operator=(const EventRouterDispatchTest&) = delete;
void SetUp() override {
ExtensionsTest::SetUp();
render_process_host_ =
std::make_unique<content::MockRenderProcessHost>(browser_context());
ASSERT_TRUE(event_router()); // constructs EventRouter
}
void TearDown() override {
render_process_host_.reset();
ExtensionsTest::TearDown();
}
content::RenderProcessHost* process() const {
return render_process_host_.get();
}
EventRouter* event_router() { return EventRouter::Get(browser_context()); }
private:
std::unique_ptr<content::RenderProcessHost> render_process_host_;
};
TEST_F(EventRouterDispatchTest, TestDispatch) {
std::string ext1 = "ext1";
std::string ext2 = "ext2";
GURL webui1("chrome-untrusted://one");
GURL webui2("chrome-untrusted://two");
std::string event_name = "testapi.onEvent";
FeatureProvider provider;
auto feature = std::make_unique<SimpleFeature>();
feature->set_name("test feature");
feature->set_matches({webui1.spec().c_str(), webui2.spec().c_str()});
provider.AddFeature(event_name, std::move(feature));
ExtensionAPI api;
api.RegisterDependencyProvider("api", &provider);
ExtensionAPI::OverrideSharedInstanceForTest scope(&api);
TestEventRouterObserver observer(event_router());
auto add_extension = [&](const std::string& id) {
scoped_refptr<const Extension> extension =
ExtensionBuilder()
.SetID(id)
.SetManifest(base::Value::Dict()
.Set("name", "Test app")
.Set("version", "1.0")
.Set("manifest_version", 2))
.Build();
ExtensionRegistry::Get(browser_context())->AddEnabled(extension);
};
add_extension(ext1);
add_extension(ext2);
auto event = [](std::string name) {
return std::make_unique<extensions::Event>(extensions::events::FOR_TEST,
name, base::Value::List());
};
// Register both extensions and both URLs for event.
event_router()->AddEventListener(event_name, process(), ext1);
event_router()->AddEventListener(event_name, process(), ext2);
event_router()->AddEventListenerForURL(event_name, process(), webui1);
event_router()->AddEventListenerForURL(event_name, process(), webui2);
// Should only dispatch to the single specified extension or url.
event_router()->DispatchEventToExtension(ext1, event(event_name));
EXPECT_EQ(1u, observer.dispatched_events().size());
observer.ClearEvents();
event_router()->DispatchEventToExtension(ext2, event(event_name));
EXPECT_EQ(1u, observer.dispatched_events().size());
observer.ClearEvents();
event_router()->DispatchEventToURL(webui1, event(event_name));
EXPECT_EQ(1u, observer.dispatched_events().size());
observer.ClearEvents();
event_router()->DispatchEventToURL(webui2, event(event_name));
EXPECT_EQ(1u, observer.dispatched_events().size());
observer.ClearEvents();
// No listeners registered for 'api.other' event.
event_router()->DispatchEventToExtension(ext1, event("api.other"));
EXPECT_EQ(0u, observer.dispatched_events().size());
event_router()->DispatchEventToURL(webui1, event("api.other"));
EXPECT_EQ(0u, observer.dispatched_events().size());
}
// TODO(crbug.com/40281129): test is flaky across platforms.
TEST_F(EventRouterDispatchTest, DISABLED_TestDispatchCallback) {
std::string ext1 = "ext1";
std::string ext2 = "ext2";
std::string ext3 = "ext3";
std::string event_name = "testapi.onEvent";
FeatureProvider provider;
auto feature = std::make_unique<SimpleFeature>();
feature->set_name("test feature");
provider.AddFeature(event_name, std::move(feature));
ExtensionAPI api;
api.RegisterDependencyProvider("api", &provider);
ExtensionAPI::OverrideSharedInstanceForTest scope(&api);
auto add_extension = [&](const std::string& id) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("test extension")
.SetManifestVersion(3)
.SetID(id)
.Build();
ExtensionRegistry::Get(browser_context())->AddEnabled(extension);
};
add_extension(ext1);
add_extension(ext2);
add_extension(ext3);
std::vector<extensions::EventTarget> dispatched;
auto create_event = [&](const std::string& name) {
auto event = std::make_unique<extensions::Event>(
extensions::events::FOR_TEST, name, base::Value::List());
return event;
};
auto create_event_with_callback = [&](const std::string& name) {
auto e = create_event(name);
e->did_dispatch_callback =
base::BindLambdaForTesting([&](const extensions::EventTarget& target) {
dispatched.push_back(target);
});
// To ensure did_dispatch_callback is copied properly.
return e->DeepCopy();
};
auto process1 =
std::make_unique<content::MockRenderProcessHost>(browser_context());
auto process2 =
std::make_unique<content::MockRenderProcessHost>(browser_context());
auto process3 =
std::make_unique<content::MockRenderProcessHost>(browser_context());
auto process4 =
std::make_unique<content::MockRenderProcessHost>(browser_context());
// Register all extensions for the event:
// 1) single listener for ext1
event_router()->AddEventListener(event_name, process1.get(), ext1);
// 2) two listeners for two processes for ext2
event_router()->AddEventListener(event_name, process2.get(), ext2);
event_router()->AddEventListener(event_name, process3.get(), ext2);
// 3) service worker listeners for ext3
const int sw_version_id = 10;
const int sw_thread_id = 100;
MockEventDispatcher sw_event_dispatcher;
event_router()->AddServiceWorkerEventListener(
mojom::EventListener::New(
mojom::EventListenerOwner::NewExtensionId(ext3), event_name,
mojom::ServiceWorkerContext::New(GURL(), sw_version_id, sw_thread_id),
/*event_filter=*/std::nullopt),
process4.get());
event_router()->BindServiceWorkerEventDispatcher(
process4->GetDeprecatedID(), sw_thread_id,
sw_event_dispatcher.BindAndPassRemote());
// Dispatch without callback set.
event_router()->DispatchEventToExtension(ext1, create_event(event_name));
event_router()->DispatchEventToExtension(ext2, create_event(event_name));
event_router()->DispatchEventToExtension(ext3, create_event(event_name));
EXPECT_EQ(0u, dispatched.size());
dispatched.clear();
// Dispatch with a post-dispatch callback set.
event_router()->DispatchEventToExtension(
ext1, create_event_with_callback(event_name));
event_router()->DispatchEventToExtension(
ext2, create_event_with_callback(event_name));
event_router()->DispatchEventToExtension(
ext3, create_event_with_callback(event_name));
const int sw_invalid_version_id =
blink::mojom::kInvalidServiceWorkerVersionId;
std::vector<EventTarget> expected{
{ext1, process1->GetDeprecatedID(), sw_invalid_version_id, kMainThreadId},
{ext2, process2->GetDeprecatedID(), sw_invalid_version_id, kMainThreadId},
{ext2, process3->GetDeprecatedID(), sw_invalid_version_id, kMainThreadId},
{ext3, process4->GetDeprecatedID(), sw_version_id, sw_thread_id},
};
std::sort(std::begin(dispatched), std::end(dispatched));
EXPECT_EQ(dispatched, expected);
dispatched.clear();
// Repeat the same event, but with broadcast: should have the same dispatch
// targets.
event_router()->BroadcastEvent(create_event_with_callback(event_name));
std::sort(std::begin(dispatched), std::end(dispatched));
EXPECT_EQ(dispatched, expected);
dispatched.clear();
// No listeners registered for 'api.other' event.
event_router()->DispatchEventToExtension(
ext1, create_event_with_callback("api.other"));
event_router()->DispatchEventToExtension(
ext2, create_event_with_callback("api.other"));
event_router()->DispatchEventToExtension(
ext3, create_event_with_callback("api.other"));
EXPECT_EQ(0u, dispatched.size());
}
} // namespace extensions