#include "extensions/browser/event_router.h"
#include <stddef.h>
#include <algorithm>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include "base/atomic_sequence_num.h"
#include "base/check_is_test.h"
#include "base/containers/contains.h"
#include "base/debug/crash_logging.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "components/crx_file/id_util.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/storage_partition.h"
#include "extensions/browser/api_activity_monitor.h"
#include "extensions/browser/bad_message.h"
#include "extensions/browser/event_router_factory.h"
#include "extensions/browser/events/event_dispatch_helper.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/process_map.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_api.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/features/feature_provider.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/common/mojom/event_dispatcher.mojom.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/utils/extension_utils.h"
#include "ipc/ipc_channel_proxy.h"
#include "url/origin.h"
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
#include "cef/ohos_cef_ext/libcef/browser/extensions/api/downloads/download_api_ext_router.h"
#endif
using content::BrowserContext;
using content::BrowserThread;
using content::RenderProcessHost;
namespace extensions {
base::TimeDelta kEventAckMetricTimeLimit = base::Minutes(5);
namespace {
constexpr char kFilteredEvents[] = "filtered_events";
constexpr char kFilteredServiceWorkerEvents[] =
"filtered_service_worker_events";
constexpr char kAddEventListenerWithInvalidParam[] =
"Tried to add an event listener without a valid extension ID nor listener "
"URL";
constexpr char kAddEventListenerWithInvalidWorkerScopeURL[] =
"Tried to add an event listener for a service worker without a valid "
"worker scope URL.";
constexpr char kAddEventListenerWithInvalidExtensionID[] =
"Tried to add an event listener for a service worker without a valid "
"extension ID.";
constexpr char kRemoveEventListenerWithInvalidParam[] =
"Tried to remove an event listener without a valid extension ID nor "
"listener URL";
constexpr char kRemoveEventListenerWithInvalidWorkerScopeURL[] =
"Tried to remove an event listener for a service worker without a valid "
"worker scope URL.";
constexpr char kRemoveEventListenerWithInvalidExtensionID[] =
"Tried to remove an event listener for a service worker without a valid "
"extension ID.";
void NotifyEventDispatched(content::BrowserContext* browser_context,
const ExtensionId& extension_id,
const std::string& event_name,
const base::Value::List& args) {
activity_monitor::OnApiEventDispatched(browser_context, extension_id,
event_name, args);
}
base::AtomicSequenceNumber g_extension_event_id;
base::debug::CrashKeyString* GetEventNameCrashKey() {
static auto* crash_key = base::debug::AllocateCrashKeyString(
"ext_event_name", base::debug::CrashKeySize::Size256);
return crash_key;
}
}
namespace debug {
class ScopedOOMCrashKey {
public:
explicit ScopedOOMCrashKey(const std::string& event_name)
: event_name_crash_key_(GetEventNameCrashKey(), event_name) {}
~ScopedOOMCrashKey() = default;
private:
base::debug::ScopedCrashKeyString event_name_crash_key_;
};
}
const char EventRouter::kRegisteredLazyEvents[] = "events";
const char EventRouter::kRegisteredServiceWorkerEvents[] =
"serviceworkerevents";
void EventRouter::DispatchExtensionMessage(
content::RenderProcessHost* rph,
int worker_thread_id,
content::BrowserContext* browser_context,
const mojom::HostID& host_id,
int event_id,
const std::string& event_name,
base::Value::List event_args,
UserGestureState user_gesture,
mojom::EventFilteringInfoPtr info,
mojom::EventDispatcher::DispatchEventCallback callback) {
if (host_id.type == mojom::HostID::HostType::kExtensions) {
NotifyEventDispatched(browser_context,
GenerateExtensionIdFromHostId(host_id), event_name,
event_args);
}
auto params = mojom::DispatchEventParams::New();
params->worker_thread_id = worker_thread_id;
params->host_id = host_id.Clone();
params->event_name = event_name;
params->event_id = event_id;
params->is_user_gesture = user_gesture == UserGestureState::kEnabled;
params->filtering_info = std::move(info);
RouteDispatchEvent(rph, std::move(params), std::move(event_args),
std::move(callback));
}
void EventRouter::RouteDispatchEvent(
content::RenderProcessHost* rph,
mojom::DispatchEventParamsPtr params,
base::Value::List event_args,
mojom::EventDispatcher::DispatchEventCallback callback) {
CHECK(base::Contains(observed_process_set_, rph));
int worker_thread_id = params->worker_thread_id;
mojo::AssociatedRemote<mojom::EventDispatcher>& dispatcher =
rph_dispatcher_map_[rph][worker_thread_id];
if (!dispatcher.is_bound()) {
if (worker_thread_id == kMainThreadId) {
IPC::ChannelProxy* channel = rph->GetChannel();
if (!channel) {
return;
}
channel->GetRemoteAssociatedInterface(
dispatcher.BindNewEndpointAndPassReceiver());
} else {
return;
}
}
CHECK(!rph->IsInitializedAndNotDead() || dispatcher.is_connected());
debug::ScopedOOMCrashKey oom_crash_key(params->event_name);
dispatcher->DispatchEvent(std::move(params), std::move(event_args),
std::move(callback));
}
EventRouter* EventRouter::Get(content::BrowserContext* browser_context) {
return EventRouterFactory::GetForBrowserContext(browser_context);
}
std::string EventRouter::GetBaseEventName(const std::string& full_event_name) {
size_t slash_sep = full_event_name.find('/');
return full_event_name.substr(0, slash_sep);
}
void EventRouter::DispatchEventToSender(
content::RenderProcessHost* rph,
content::BrowserContext* browser_context,
const mojom::HostID& host_id,
events::HistogramValue histogram_value,
const std::string& event_name,
int worker_thread_id,
int64_t service_worker_version_id,
base::Value::List event_args,
mojom::EventFilteringInfoPtr info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
int event_id = g_extension_event_id.GetNext();
auto* registry = ExtensionRegistry::Get(browser_context);
CHECK(registry);
const Extension* extension = nullptr;
if (host_id.type == mojom::HostID::HostType::kExtensions) {
extension = registry->enabled_extensions().GetByID(host_id.id);
}
if (!extension) {
for (TestObserver& observer : test_observers_) {
observer.OnNonExtensionEventDispatched(event_name);
}
ObserveProcess(rph);
DispatchExtensionMessage(rph, worker_thread_id, browser_context, host_id,
event_id, event_name, std::move(event_args),
UserGestureState::kUnknown, std::move(info),
base::DoNothing());
return;
}
IncrementInFlightEvents(
browser_context, rph, extension, event_id, event_name,
base::TimeTicks::Now(), service_worker_version_id,
worker_thread_id, EventDispatchSource::kDispatchEventToSender,
true,
events::HistogramValue::UNKNOWN);
ReportEvent(histogram_value, extension,
false);
mojom::EventDispatcher::DispatchEventCallback callback;
if (worker_thread_id != kMainThreadId) {
callback = base::BindOnce(
&EventRouter::DecrementInFlightEventsForServiceWorker,
weak_factory_.GetWeakPtr(),
WorkerId{GenerateExtensionIdFromHostId(host_id), rph->GetDeprecatedID(),
service_worker_version_id, worker_thread_id},
event_id);
} else if (BackgroundInfo::HasBackgroundPage(extension)) {
callback =
base::BindOnce(&EventRouter::DecrementInFlightEventsForRenderFrameHost,
weak_factory_.GetWeakPtr(), rph->GetDeprecatedID(),
host_id.id, event_id);
} else {
callback = base::DoNothing();
}
ObserveProcess(rph);
DispatchExtensionMessage(rph, worker_thread_id, browser_context, host_id,
event_id, event_name, std::move(event_args),
UserGestureState::kUnknown, std::move(info),
std::move(callback));
}
void EventRouter::BindForRenderer(
int render_process_id,
mojo::PendingAssociatedReceiver<mojom::EventRouter> receiver) {
auto* host = RenderProcessHost::FromID(render_process_id);
if (!host) {
return;
}
EventRouter* event_router = EventRouter::Get(host->GetBrowserContext());
if (!event_router) {
return;
}
event_router->receivers_.Add(event_router, std::move(receiver),
render_process_id);
}
void EventRouter::SwapReceiverForTesting(int render_process_id,
mojom::EventRouter* new_impl) {
std::map<mojo::ReceiverId, int*> receiver_contexts =
receivers_.GetAllContexts();
for (auto& [receiver_id, render_process_id_ptr] :
receivers_.GetAllContexts()) {
if (render_process_id_ptr && *render_process_id_ptr == render_process_id) {
std::ignore =
receivers_.SwapImplForTesting(receiver_id, new_impl);
return;
}
}
NOTREACHED();
}
EventRouter::EventRouter(BrowserContext* browser_context,
ExtensionPrefs* extension_prefs)
: browser_context_(browser_context),
extension_prefs_(extension_prefs),
lazy_event_dispatch_util_(browser_context_) {
extension_registry_observation_.Observe(
ExtensionRegistry::Get(browser_context_));
process_manager_observation_.Observe(ProcessManager::Get(browser_context_));
}
EventRouter::~EventRouter() {
for (content::RenderProcessHost* process : observed_process_set_) {
process->RemoveObserver(this);
}
}
content::RenderProcessHost*
EventRouter::GetRenderProcessHostForCurrentReceiver() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* process = RenderProcessHost::FromID(receivers_.current_context());
return process;
}
void EventRouter::AddListenerForMainThread(
mojom::EventListenerPtr event_listener) {
auto* process = GetRenderProcessHostForCurrentReceiver();
if (!process) {
return;
}
const mojom::EventListenerOwner& listener_owner =
*event_listener->listener_owner;
LOG(INFO) << "EventRouter: AddListenerForMainThread: " << event_listener->event_name;
if (listener_owner.is_extension_id()) {
AddEventListener(event_listener->event_name, process,
listener_owner.get_extension_id());
} else if (listener_owner.is_listener_url() &&
listener_owner.get_listener_url().is_valid()) {
AddEventListenerForURL(event_listener->event_name, process,
listener_owner.get_listener_url());
} else {
mojo::ReportBadMessage(kAddEventListenerWithInvalidParam);
}
}
void EventRouter::AddListenerForServiceWorker(
mojom::EventListenerPtr event_listener) {
auto* process = GetRenderProcessHostForCurrentReceiver();
if (!process) {
return;
}
const mojom::EventListenerOwner& listener_owner =
*event_listener->listener_owner;
LOG(INFO) << "EventRouter: AddListenerForServiceWorker: " << event_listener->event_name;
if (!listener_owner.is_extension_id()) {
mojo::ReportBadMessage(kAddEventListenerWithInvalidExtensionID);
return;
}
if (!event_listener->service_worker_context->scope_url.is_valid()) {
mojo::ReportBadMessage(kAddEventListenerWithInvalidWorkerScopeURL);
return;
}
AddServiceWorkerEventListener(std::move(event_listener), process);
}
void EventRouter::AddLazyListenerForMainThread(const ExtensionId& extension_id,
const std::string& event_name) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::unique_ptr<EventListener> listener = EventListener::CreateLazyListener(
event_name, extension_id, browser_context_, false, GURL(), std::nullopt);
AddLazyEventListenerImpl(std::move(listener), RegisteredEventType::kLazy);
}
void EventRouter::AddLazyListenerForServiceWorker(
const ExtensionId& extension_id,
const GURL& worker_scope_url,
const std::string& event_name) {
std::unique_ptr<EventListener> listener = EventListener::CreateLazyListener(
event_name, extension_id, browser_context_,
true, worker_scope_url,
std::nullopt);
AddLazyEventListenerImpl(std::move(listener),
RegisteredEventType::kServiceWorker);
}
void EventRouter::AddFilteredListenerForMainThread(
mojom::EventListenerOwnerPtr listener_owner,
const std::string& event_name,
base::Value::Dict filter,
bool add_lazy_listener) {
auto* process = GetRenderProcessHostForCurrentReceiver();
if (!process) {
return;
}
AddFilteredEventListener(event_name, process, std::move(listener_owner),
nullptr, std::move(filter), add_lazy_listener);
}
void EventRouter::AddFilteredListenerForServiceWorker(
const ExtensionId& extension_id,
const std::string& event_name,
mojom::ServiceWorkerContextPtr service_worker_context,
base::Value::Dict filter,
bool add_lazy_listener) {
auto* process = GetRenderProcessHostForCurrentReceiver();
if (!process) {
return;
}
AddFilteredEventListener(
event_name, process,
mojom::EventListenerOwner::NewExtensionId(extension_id),
service_worker_context.get(), std::move(filter), add_lazy_listener);
}
void EventRouter::RemoveListenerForMainThread(
mojom::EventListenerPtr event_listener) {
auto* process = GetRenderProcessHostForCurrentReceiver();
if (!process) {
return;
}
const mojom::EventListenerOwner& listener_owner =
*event_listener->listener_owner;
LOG(INFO) << "EventRouter: RemoveListenerForMainThread: " << event_listener->event_name;
if (listener_owner.is_extension_id()) {
RemoveEventListener(event_listener->event_name, process,
listener_owner.get_extension_id());
} else if (listener_owner.is_listener_url() &&
listener_owner.get_listener_url().is_valid()) {
RemoveEventListenerForURL(event_listener->event_name, process,
listener_owner.get_listener_url());
} else {
mojo::ReportBadMessage(kRemoveEventListenerWithInvalidParam);
}
}
void EventRouter::RemoveListenerForServiceWorker(
mojom::EventListenerPtr event_listener) {
auto* process = GetRenderProcessHostForCurrentReceiver();
if (!process) {
return;
}
const mojom::EventListenerOwner& listener_owner =
*event_listener->listener_owner;
LOG(INFO) << "EventRouter: RemoveListenerForServiceWorker: " << event_listener->event_name;
if (!listener_owner.is_extension_id()) {
mojo::ReportBadMessage(kRemoveEventListenerWithInvalidExtensionID);
return;
}
if (!event_listener->service_worker_context->scope_url.is_valid()) {
mojo::ReportBadMessage(kRemoveEventListenerWithInvalidWorkerScopeURL);
return;
}
RemoveServiceWorkerEventListener(std::move(event_listener), process);
}
void EventRouter::RemoveLazyListenerForMainThread(
const ExtensionId& extension_id,
const std::string& event_name) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::unique_ptr<EventListener> listener = EventListener::CreateLazyListener(
event_name, extension_id, browser_context_, false, GURL(), std::nullopt);
RemoveLazyEventListenerImpl(std::move(listener), RegisteredEventType::kLazy);
}
void EventRouter::RemoveLazyListenerForServiceWorker(
const ExtensionId& extension_id,
const GURL& worker_scope_url,
const std::string& event_name) {
std::unique_ptr<EventListener> listener = EventListener::CreateLazyListener(
event_name, extension_id, browser_context_, true, worker_scope_url,
std::nullopt);
RemoveLazyEventListenerImpl(std::move(listener),
RegisteredEventType::kServiceWorker);
}
void EventRouter::RemoveFilteredListenerForMainThread(
mojom::EventListenerOwnerPtr listener_owner,
const std::string& event_name,
base::Value::Dict filter,
bool remove_lazy_listener) {
auto* process = GetRenderProcessHostForCurrentReceiver();
if (!process) {
return;
}
RemoveFilteredEventListener(event_name, process, std::move(listener_owner),
nullptr, std::move(filter), remove_lazy_listener);
}
void EventRouter::RemoveFilteredListenerForServiceWorker(
const ExtensionId& extension_id,
const std::string& event_name,
mojom::ServiceWorkerContextPtr service_worker_context,
base::Value::Dict filter,
bool remove_lazy_listener) {
auto* process = GetRenderProcessHostForCurrentReceiver();
if (!process) {
return;
}
RemoveFilteredEventListener(
event_name, process,
mojom::EventListenerOwner::NewExtensionId(extension_id),
service_worker_context.get(), std::move(filter), remove_lazy_listener);
}
void EventRouter::AddEventListener(const std::string& event_name,
RenderProcessHost* process,
const ExtensionId& extension_id) {
listeners_.AddListener(EventListener::ForExtension(event_name, extension_id,
process, std::nullopt));
CHECK(base::Contains(observed_process_set_, process));
}
void EventRouter::AddServiceWorkerEventListener(
mojom::EventListenerPtr event_listener,
RenderProcessHost* process) {
const mojom::ServiceWorkerContext& service_worker =
*event_listener->service_worker_context;
listeners_.AddListener(EventListener::ForExtensionServiceWorker(
event_listener->event_name,
event_listener->listener_owner->get_extension_id(), process,
process->GetBrowserContext(), service_worker.scope_url,
service_worker.version_id, service_worker.thread_id, std::nullopt));
CHECK(base::Contains(observed_process_set_, process));
}
void EventRouter::RemoveEventListener(const std::string& event_name,
RenderProcessHost* process,
const ExtensionId& extension_id) {
std::unique_ptr<EventListener> listener = EventListener::ForExtension(
event_name, extension_id, process, std::nullopt);
listeners_.RemoveListener(listener.get());
}
void EventRouter::RemoveServiceWorkerEventListener(
mojom::EventListenerPtr event_listener,
RenderProcessHost* process) {
const mojom::ServiceWorkerContext& service_worker =
*event_listener->service_worker_context;
std::unique_ptr<EventListener> listener =
EventListener::ForExtensionServiceWorker(
event_listener->event_name,
event_listener->listener_owner->get_extension_id(), process,
process->GetBrowserContext(), service_worker.scope_url,
service_worker.version_id, service_worker.thread_id, std::nullopt);
listeners_.RemoveListener(listener.get());
}
void EventRouter::AddEventListenerForURL(const std::string& event_name,
RenderProcessHost* process,
const GURL& listener_url) {
listeners_.AddListener(
EventListener::ForURL(event_name, listener_url, process, std::nullopt));
CHECK(base::Contains(observed_process_set_, process));
}
void EventRouter::RemoveEventListenerForURL(const std::string& event_name,
RenderProcessHost* process,
const GURL& listener_url) {
std::unique_ptr<EventListener> listener =
EventListener::ForURL(event_name, listener_url, process, std::nullopt);
listeners_.RemoveListener(listener.get());
}
void EventRouter::RegisterObserver(Observer* observer,
const std::string& event_name) {
DCHECK(!base::Contains(event_name, '/'));
auto& observers = observer_map_[event_name];
if (!observers) {
observers = std::make_unique<Observers>();
}
observers->AddObserver(observer);
}
void EventRouter::UnregisterObserver(Observer* observer) {
for (auto& it : observer_map_) {
it.second->RemoveObserver(observer);
}
}
void EventRouter::AddObserverForTesting(TestObserver* observer) {
test_observers_.AddObserver(observer);
}
void EventRouter::RemoveObserverForTesting(TestObserver* observer) {
test_observers_.RemoveObserver(observer);
}
void EventRouter::OnListenerAdded(const EventListener* listener) {
RenderProcessHost* process = listener->process();
if (process) {
ObserveProcess(process);
}
const EventListenerInfo details(
listener->event_name(), listener->extension_id(),
listener->listener_url(), listener->browser_context(),
listener->worker_thread_id(), listener->service_worker_version_id(),
listener->IsLazy());
std::string base_event_name = GetBaseEventName(listener->event_name());
auto it = observer_map_.find(base_event_name);
if (it != observer_map_.end()) {
for (auto& observer : *it->second) {
observer.OnListenerAdded(details);
}
}
}
void EventRouter::OnListenerRemoved(const EventListener* listener) {
const EventListenerInfo details(
listener->event_name(), listener->extension_id(),
listener->listener_url(), listener->browser_context(),
listener->worker_thread_id(), listener->service_worker_version_id(),
listener->IsLazy());
std::string base_event_name = GetBaseEventName(listener->event_name());
auto it = observer_map_.find(base_event_name);
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
ExtensionDownloadsEventRouterEx::GetInstance().OnListenerRemoved(details);
#endif
if (it != observer_map_.end()) {
for (auto& observer : *it->second) {
observer.OnListenerRemoved(details);
}
}
}
void EventRouter::ObserveProcess(RenderProcessHost* process) {
CHECK(process);
bool inserted = observed_process_set_.insert(process).second;
if (inserted) {
process->AddObserver(this);
}
}
void EventRouter::RenderProcessExited(
RenderProcessHost* host,
const content::ChildProcessTerminationInfo& info) {
listeners_.RemoveListenersForProcess(host);
event_ack_data_.ClearUnackedEventsForRenderProcess(host->GetDeprecatedID());
observed_process_set_.erase(host);
rph_dispatcher_map_.erase(host);
host->RemoveObserver(this);
}
void EventRouter::RenderProcessHostDestroyed(RenderProcessHost* host) {
listeners_.RemoveListenersForProcess(host);
event_ack_data_.ClearUnackedEventsForRenderProcess(host->GetDeprecatedID());
observed_process_set_.erase(host);
rph_dispatcher_map_.erase(host);
host->RemoveObserver(this);
}
void EventRouter::AddFilteredEventListener(
const std::string& event_name,
RenderProcessHost* process,
mojom::EventListenerOwnerPtr listener_owner,
mojom::ServiceWorkerContext* service_worker_context,
const base::Value::Dict& filter,
bool add_lazy_listener) {
const bool is_for_service_worker = !!service_worker_context;
std::unique_ptr<EventListener> regular_listener;
std::unique_ptr<EventListener> lazy_listener;
if (is_for_service_worker && listener_owner->is_extension_id()) {
regular_listener = EventListener::ForExtensionServiceWorker(
event_name, listener_owner->get_extension_id(), process,
process->GetBrowserContext(), service_worker_context->scope_url,
service_worker_context->version_id, service_worker_context->thread_id,
filter.Clone());
if (add_lazy_listener) {
lazy_listener = EventListener::CreateLazyListener(
event_name, listener_owner->get_extension_id(), browser_context_,
true, service_worker_context->scope_url, filter.Clone());
}
} else if (listener_owner->is_extension_id()) {
regular_listener = EventListener::ForExtension(
event_name, listener_owner->get_extension_id(), process,
filter.Clone());
if (add_lazy_listener) {
lazy_listener = EventListener::CreateLazyListener(
event_name, listener_owner->get_extension_id(), browser_context_,
false, GURL(), filter.Clone());
}
} else if (listener_owner->is_listener_url() && !add_lazy_listener) {
regular_listener =
EventListener::ForURL(event_name, listener_owner->get_listener_url(),
process, filter.Clone());
} else {
mojo::ReportBadMessage(kAddEventListenerWithInvalidParam);
return;
}
listeners_.AddListener(std::move(regular_listener));
CHECK(base::Contains(observed_process_set_, process));
DCHECK_EQ(add_lazy_listener, !!lazy_listener);
if (lazy_listener) {
bool added = listeners_.AddListener(std::move(lazy_listener));
if (added) {
AddFilterToEvent(event_name, listener_owner->get_extension_id(),
is_for_service_worker, filter);
}
}
}
void EventRouter::RemoveFilteredEventListener(
const std::string& event_name,
RenderProcessHost* process,
mojom::EventListenerOwnerPtr listener_owner,
mojom::ServiceWorkerContext* service_worker_context,
const base::Value::Dict& filter,
bool remove_lazy_listener) {
const bool is_for_service_worker = !!service_worker_context;
std::unique_ptr<EventListener> listener;
if (is_for_service_worker && listener_owner->is_extension_id()) {
listener = EventListener::ForExtensionServiceWorker(
event_name, listener_owner->get_extension_id(), process,
process->GetBrowserContext(), service_worker_context->scope_url,
service_worker_context->version_id, service_worker_context->thread_id,
filter.Clone());
} else if (listener_owner->is_extension_id()) {
listener = EventListener::ForExtension(event_name,
listener_owner->get_extension_id(),
process, filter.Clone());
} else if (listener_owner->is_listener_url() && !remove_lazy_listener) {
listener =
EventListener::ForURL(event_name, listener_owner->get_listener_url(),
process, filter.Clone());
} else {
mojo::ReportBadMessage(kRemoveEventListenerWithInvalidParam);
return;
}
listeners_.RemoveListener(listener.get());
if (remove_lazy_listener) {
listener->MakeLazy();
bool removed = listeners_.RemoveListener(listener.get());
if (removed) {
RemoveFilterFromEvent(event_name, listener_owner->get_extension_id(),
is_for_service_worker, filter);
}
}
}
bool EventRouter::HasEventListener(const std::string& event_name) const {
return listeners_.HasListenerForEvent(event_name);
}
bool EventRouter::ExtensionHasEventListener(
const ExtensionId& extension_id,
const std::string& event_name) const {
return listeners_.HasListenerForExtension(extension_id, event_name);
}
bool EventRouter::URLHasEventListener(const GURL& url,
const std::string& event_name) const {
return listeners_.HasListenerForURL(url, event_name);
}
std::set<std::string> EventRouter::GetRegisteredEvents(
const ExtensionId& extension_id,
RegisteredEventType type) const {
std::set<std::string> events;
if (!extension_prefs_) {
return events;
}
const char* pref_key = type == RegisteredEventType::kLazy
? kRegisteredLazyEvents
: kRegisteredServiceWorkerEvents;
const base::Value::List* events_value =
extension_prefs_->ReadPrefAsList(extension_id, pref_key);
if (!events_value) {
return events;
}
for (const base::Value& event_val : *events_value) {
const std::string* event = event_val.GetIfString();
if (event) {
events.insert(*event);
}
}
return events;
}
void EventRouter::ClearRegisteredEventsForTest(
const ExtensionId& extension_id) {
SetRegisteredEvents(extension_id, std::set<std::string>(),
RegisteredEventType::kLazy);
SetRegisteredEvents(extension_id, std::set<std::string>(),
RegisteredEventType::kServiceWorker);
}
bool EventRouter::HasLazyEventListenerForTesting(
const std::string& event_name) {
const EventListenerMap::ListenerList& listeners =
listeners_.GetEventListenersByName(event_name);
return std::ranges::any_of(
listeners, [](const std::unique_ptr<EventListener>& listener) {
return listener->IsLazy();
});
}
bool EventRouter::HasNonLazyEventListenerForTesting(
const std::string& event_name) {
const EventListenerMap::ListenerList& listeners =
listeners_.GetEventListenersByName(event_name);
return std::ranges::any_of(
listeners, [](const std::unique_ptr<EventListener>& listener) {
return !listener->IsLazy();
});
}
void EventRouter::RemoveFilterFromEvent(const std::string& event_name,
const ExtensionId& extension_id,
bool is_for_service_worker,
const base::Value::Dict& filter) {
ExtensionPrefs::ScopedDictionaryUpdate update(
extension_prefs_, extension_id,
is_for_service_worker ? kFilteredServiceWorkerEvents : kFilteredEvents);
auto filtered_events = update.Create();
base::Value::List* filter_list = nullptr;
if (!filtered_events ||
!filtered_events->GetListWithoutPathExpansion(event_name, &filter_list)) {
return;
}
const base::Value::Dict& (base::Value::*get_dict)() const =
&base::Value::GetDict;
filter_list->erase(std::ranges::find(*filter_list, filter, get_dict));
}
const base::Value::Dict* EventRouter::GetFilteredEvents(
const ExtensionId& extension_id,
RegisteredEventType type) {
const char* pref_key = type == RegisteredEventType::kLazy
? kFilteredEvents
: kFilteredServiceWorkerEvents;
return extension_prefs_->ReadPrefAsDict(extension_id, pref_key);
}
void EventRouter::BroadcastEvent(std::unique_ptr<Event> event) {
DispatchEventImpl(std::string(), GURL(), std::move(event));
}
void EventRouter::DispatchEventToExtension(const ExtensionId& extension_id,
std::unique_ptr<Event> event) {
DCHECK(!extension_id.empty());
DispatchEventImpl(extension_id, GURL(), std::move(event));
}
void EventRouter::DispatchEventToURL(const GURL& url,
std::unique_ptr<Event> event) {
DCHECK(!url.is_empty());
DispatchEventImpl(std::string(), url, std::move(event));
}
void EventRouter::DispatchEventWithLazyListener(const ExtensionId& extension_id,
std::unique_ptr<Event> event) {
CHECK(crx_file::id_util::IdIsValid(extension_id));
const Extension* extension = ExtensionRegistry::Get(browser_context_)
->enabled_extensions()
.GetByID(extension_id);
if (!extension) {
return;
}
const bool is_service_worker_based_background =
BackgroundInfo::IsServiceWorkerBased(extension);
std::string event_name = event->event_name;
const bool has_listener = ExtensionHasEventListener(extension_id, event_name);
if (!has_listener) {
if (is_service_worker_based_background) {
AddLazyListenerForServiceWorker(
extension_id, Extension::GetBaseURLFromExtensionId(extension_id),
event_name);
} else {
AddLazyListenerForMainThread(extension_id, event_name);
}
}
DispatchEventToExtension(extension_id, std::move(event));
if (!has_listener) {
if (is_service_worker_based_background) {
RemoveLazyListenerForServiceWorker(
extension_id, Extension::GetBaseURLFromExtensionId(extension_id),
event_name);
} else {
RemoveLazyListenerForMainThread(extension_id, event_name);
}
}
}
void EventRouter::DispatchEventImpl(const std::string& restrict_to_extension_id,
const GURL& restrict_to_url,
std::unique_ptr<Event> event) {
event->dispatch_start_time = base::TimeTicks::Now();
DCHECK(event);
DCHECK(!event->restrict_to_browser_context ||
ExtensionsBrowserClient::Get()->IsSameContext(
browser_context_, event->restrict_to_browser_context));
if (browser_context_->ShutdownStarted()) {
return;
}
for (TestObserver& observer : test_observers_)
observer.OnWillDispatchEvent(*event);
EventDispatchHelper::DispatchEvent(
*browser_context_, listeners_,
base::BindRepeating(&EventRouter::DispatchPendingEvent,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&EventRouter::DispatchEventToProcess,
weak_factory_.GetWeakPtr()),
restrict_to_extension_id, restrict_to_url, std::move(event));
}
void EventRouter::DispatchEventToProcess(
const ExtensionId& extension_id,
const GURL& listener_url,
RenderProcessHost* process,
int64_t service_worker_version_id,
int worker_thread_id,
std::unique_ptr<Event> event,
bool did_enqueue) {
BrowserContext* listener_context = process->GetBrowserContext();
ProcessMap* process_map = ProcessMap::Get(listener_context);
const Extension* extension =
ExtensionRegistry::Get(browser_context_)->enabled_extensions().GetByID(
extension_id);
if (!extension && !extension_id.empty()) {
return;
}
const GURL* url =
service_worker_version_id == blink::mojom::kInvalidServiceWorkerVersionId
? &listener_url
: nullptr;
mojom::ContextType target_context = process_map->GetMostLikelyContextType(
extension, process->GetDeprecatedID(), url);
if (did_enqueue && !EventDispatchHelper::CheckFeatureAvailability(
*event, extension, listener_url, *process,
*listener_context, target_context)) {
return;
}
if (target_context == mojom::ContextType::kWebPage) {
CHECK(url);
bool is_new_webstore_origin =
url::Origin::Create(extension_urls::GetNewWebstoreLaunchURL())
.IsSameOriginWith(*url);
const Feature* feature =
ExtensionAPI::GetSharedInstance()->GetFeatureDependency(
event->event_name);
CHECK(feature->RequiresDelegatedAvailabilityCheck() ||
is_new_webstore_origin)
<< "Trying to dispatch event " << event->event_name << " to a webpage,"
<< " but this shouldn't be possible";
}
CHECK(event->will_dispatch_callback.is_null());
int event_id = g_extension_event_id.GetNext();
mojom::EventDispatcher::DispatchEventCallback callback;
if (!extension) {
callback = base::DoNothing();
} else if (worker_thread_id != kMainThreadId) {
callback =
base::BindOnce(&EventRouter::DecrementInFlightEventsForServiceWorker,
weak_factory_.GetWeakPtr(),
WorkerId{extension_id, process->GetDeprecatedID(),
service_worker_version_id, worker_thread_id},
event_id);
} else if (BackgroundInfo::HasBackgroundPage(extension)) {
callback =
base::BindOnce(&EventRouter::DecrementInFlightEventsForRenderFrameHost,
weak_factory_.GetWeakPtr(), process->GetDeprecatedID(),
extension_id, event_id);
} else {
callback = base::DoNothing();
}
DispatchExtensionMessage(process, worker_thread_id, listener_context,
GenerateHostIdFromExtensionId(extension_id),
event_id, event->event_name,
std::move(event->event_args), event->user_gesture,
std::move(event->filter_info), std::move(callback));
if (!event->did_dispatch_callback.is_null()) {
event->did_dispatch_callback.Run(
EventTarget{extension_id, process->GetDeprecatedID(),
service_worker_version_id, worker_thread_id});
}
for (TestObserver& observer : test_observers_) {
observer.OnDidDispatchEventToProcess(*event, process->GetDeprecatedID());
}
if (extension) {
ReportEvent(event->histogram_value, extension, did_enqueue);
IncrementInFlightEvents(
listener_context, process, extension, event_id, event->event_name,
event->dispatch_start_time, service_worker_version_id, worker_thread_id,
EventDispatchSource::kDispatchEventToProcess,
event->lazy_background_active_on_dispatch, event->histogram_value);
}
}
void EventRouter::DecrementInFlightEventsForServiceWorker(
const WorkerId& worker_id,
int event_id,
bool event_will_run_in_lazy_background_page_script) {
auto* process = RenderProcessHost::FromID(worker_id.render_process_id);
if (!process) {
return;
}
if (event_will_run_in_lazy_background_page_script) {
bad_message::ReceivedBadMessage(
process, bad_message::ER_SW_INVALID_LAZY_BACKGROUND_PARAM);
}
const bool worker_stopped = !ProcessManager::Get(process->GetBrowserContext())
->HasServiceWorker(worker_id);
content::ServiceWorkerContext* service_worker_context =
process->GetStoragePartition()->GetServiceWorkerContext();
event_ack_data_.DecrementInflightEvent(
service_worker_context, process->GetDeprecatedID(), worker_id.version_id,
worker_id.thread_id, event_id, worker_stopped,
base::BindOnce(
[](RenderProcessHost* process) {
bad_message::ReceivedBadMessage(process,
bad_message::ESWMF_BAD_EVENT_ACK);
},
base::Unretained(process)));
}
void EventRouter::DecrementInFlightEventsForRenderFrameHost(
int render_process_host,
const ExtensionId& extension_id,
int event_id,
bool event_will_run_in_background_page_script) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* process = RenderProcessHost::FromID(render_process_host);
if (!process) {
return;
}
ProcessManager* pm = ProcessManager::Get(process->GetBrowserContext());
ExtensionHost* host = pm->GetBackgroundHostForExtension(extension_id);
if (host) {
host->OnEventAck(event_id, event_will_run_in_background_page_script);
}
}
void EventRouter::IncrementInFlightEvents(
BrowserContext* context,
RenderProcessHost* process,
const Extension* extension,
int event_id,
const std::string& event_name,
base::TimeTicks dispatch_start_time,
int64_t service_worker_version_id,
int worker_thread_id,
EventDispatchSource dispatch_source,
bool lazy_background_active_on_dispatch,
events::HistogramValue histogram_value) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (BackgroundInfo::HasBackgroundPage(extension)) {
ProcessManager* pm = ProcessManager::Get(context);
ExtensionHost* host = pm->GetBackgroundHostForExtension(extension->id());
if (host && host->render_process_host() == process) {
if (BackgroundInfo::HasLazyBackgroundPage(extension)) {
pm->IncrementLazyKeepaliveCount(extension, Activity::EVENT, event_name);
}
host->OnBackgroundEventDispatched(event_name, dispatch_start_time,
event_id, dispatch_source,
lazy_background_active_on_dispatch);
}
} else if (service_worker_version_id !=
blink::mojom::kInvalidServiceWorkerVersionId) {
if (process) {
content::ServiceWorkerContext* service_worker_context =
process->GetStoragePartition()->GetServiceWorkerContext();
event_ack_data_.IncrementInflightEvent(
service_worker_context, process->GetDeprecatedID(),
service_worker_version_id, worker_thread_id, event_id,
dispatch_start_time, dispatch_source,
lazy_background_active_on_dispatch, histogram_value);
}
}
}
void EventRouter::OnEventAck(BrowserContext* context,
const ExtensionId& extension_id,
const std::string& event_name) {
ProcessManager* pm = ProcessManager::Get(context);
ExtensionHost* host = pm->GetBackgroundHostForExtension(extension_id);
CHECK(host);
if (host->extension() &&
BackgroundInfo::HasLazyBackgroundPage(host->extension()))
pm->DecrementLazyKeepaliveCount(host->extension(), Activity::EVENT,
event_name);
}
bool EventRouter::HasRegisteredEvents(const ExtensionId& extension_id) const {
return !GetRegisteredEvents(extension_id, RegisteredEventType::kLazy)
.empty() ||
!GetRegisteredEvents(extension_id, RegisteredEventType::kServiceWorker)
.empty();
}
void EventRouter::ReportEvent(events::HistogramValue histogram_value,
const Extension* extension,
bool did_enqueue) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
UMA_HISTOGRAM_ENUMERATION("Extensions.Events.Dispatch", histogram_value,
events::ENUM_BOUNDARY);
bool is_component = Manifest::IsComponentLocation(extension->location());
if (is_component) {
UMA_HISTOGRAM_ENUMERATION("Extensions.Events.DispatchToComponent",
histogram_value, events::ENUM_BOUNDARY);
}
if (BackgroundInfo::HasPersistentBackgroundPage(extension)) {
UMA_HISTOGRAM_ENUMERATION(
"Extensions.Events.DispatchWithPersistentBackgroundPage",
histogram_value, events::ENUM_BOUNDARY);
} else if (BackgroundInfo::HasLazyBackgroundPage(extension)) {
if (did_enqueue) {
UMA_HISTOGRAM_ENUMERATION(
"Extensions.Events.DispatchWithSuspendedEventPage", histogram_value,
events::ENUM_BOUNDARY);
} else {
UMA_HISTOGRAM_ENUMERATION(
"Extensions.Events.DispatchWithRunningEventPage", histogram_value,
events::ENUM_BOUNDARY);
}
} else if (BackgroundInfo::IsServiceWorkerBased(extension)) {
base::UmaHistogramEnumeration(
"Extensions.Events.DispatchWithServiceWorkerBackground",
histogram_value, events::ENUM_BOUNDARY);
}
}
void EventRouter::DispatchPendingEvent(
std::unique_ptr<Event> event,
std::unique_ptr<LazyContextTaskQueue::ContextInfo> params) {
if (!params) {
return;
}
DCHECK(event);
bool check_for_specific_event =
base::StartsWith(event->event_name, "webRequest");
bool dispatch_to_process =
check_for_specific_event
? listeners_.HasProcessListenerForEvent(
params->render_process_host, params->worker_thread_id,
params->extension_id, event->event_name)
: listeners_.HasProcessListener(params->render_process_host,
params->worker_thread_id,
params->extension_id);
if (dispatch_to_process) {
DispatchEventToProcess(
params->extension_id, params->url, params->render_process_host,
params->service_worker_version_id, params->worker_thread_id,
std::move(event), true);
} else if (event->cannot_dispatch_callback) {
event->cannot_dispatch_callback.Run();
}
}
void EventRouter::SetRegisteredEvents(const ExtensionId& extension_id,
const std::set<std::string>& events,
RegisteredEventType type) {
base::Value::List events_list;
for (const auto& event : events) {
events_list.Append(event);
}
const char* pref_key = type == RegisteredEventType::kLazy
? kRegisteredLazyEvents
: kRegisteredServiceWorkerEvents;
extension_prefs_->UpdateExtensionPref(extension_id, pref_key,
base::Value(std::move(events_list)));
}
void EventRouter::AddFilterToEvent(const std::string& event_name,
const ExtensionId& extension_id,
bool is_for_service_worker,
const base::Value::Dict& filter) {
ExtensionPrefs::ScopedDictionaryUpdate update(
extension_prefs_, extension_id,
is_for_service_worker ? kFilteredServiceWorkerEvents : kFilteredEvents);
auto filtered_events = update.Create();
base::Value::List* filter_list = nullptr;
if (!filtered_events->GetListWithoutPathExpansion(event_name, &filter_list)) {
filtered_events->SetKey(event_name, base::Value(base::Value::List()));
filtered_events->GetListWithoutPathExpansion(event_name, &filter_list);
}
filter_list->Append(filter.Clone());
}
void EventRouter::OnExtensionLoaded(content::BrowserContext* browser_context,
const Extension* extension) {
std::set<std::string> registered_events =
GetRegisteredEvents(extension->id(), RegisteredEventType::kLazy);
listeners_.LoadUnfilteredLazyListeners(browser_context, extension->id(),
false,
registered_events);
std::set<std::string> registered_worker_events =
GetRegisteredEvents(extension->id(), RegisteredEventType::kServiceWorker);
listeners_.LoadUnfilteredLazyListeners(browser_context, extension->id(),
true,
registered_worker_events);
const base::Value::Dict* filtered_events =
GetFilteredEvents(extension->id(), RegisteredEventType::kLazy);
if (filtered_events) {
listeners_.LoadFilteredLazyListeners(browser_context, extension->id(),
false,
*filtered_events);
}
const base::Value::Dict* filtered_worker_events =
GetFilteredEvents(extension->id(), RegisteredEventType::kServiceWorker);
if (filtered_worker_events) {
listeners_.LoadFilteredLazyListeners(browser_context, extension->id(),
true,
*filtered_worker_events);
}
}
void EventRouter::OnExtensionUnloaded(content::BrowserContext* browser_context,
const Extension* extension,
UnloadedExtensionReason reason) {
listeners_.RemoveListenersForExtension(extension->id());
}
void EventRouter::OnStoppedTrackingServiceWorkerInstance(
const WorkerId& worker_id) {
listeners_.RemoveActiveServiceWorkerListenersForExtension(worker_id);
content::StoragePartition* storage_partition =
util::GetStoragePartitionForExtensionId(
worker_id.extension_id, browser_context_, false);
content::ServiceWorkerContext* service_worker_context =
storage_partition ? storage_partition->GetServiceWorkerContext()
: nullptr;
event_ack_data_.ClearUnackedEventsForWorker(
service_worker_context, worker_id.render_process_id, worker_id.version_id,
worker_id.thread_id);
}
void EventRouter::AddLazyEventListenerImpl(
std::unique_ptr<EventListener> listener,
RegisteredEventType type) {
const ExtensionId extension_id = listener->extension_id();
const std::string event_name = listener->event_name();
bool is_new = listeners_.AddListener(std::move(listener));
if (is_new) {
std::set<std::string> events = GetRegisteredEvents(extension_id, type);
bool prefs_is_new = events.insert(event_name).second;
if (prefs_is_new) {
SetRegisteredEvents(extension_id, events, type);
}
}
}
void EventRouter::RemoveLazyEventListenerImpl(
std::unique_ptr<EventListener> listener,
RegisteredEventType type) {
const ExtensionId extension_id = listener->extension_id();
const std::string event_name = listener->event_name();
bool did_exist = listeners_.RemoveListener(listener.get());
if (did_exist) {
std::set<std::string> events = GetRegisteredEvents(extension_id, type);
bool prefs_did_exist = events.erase(event_name) > 0;
DCHECK(prefs_did_exist);
SetRegisteredEvents(extension_id, events, type);
}
}
void EventRouter::BindServiceWorkerEventDispatcher(
int render_process_id,
int worker_thread_id,
mojo::PendingAssociatedRemote<mojom::EventDispatcher> event_dispatcher) {
auto* process = RenderProcessHost::FromID(render_process_id);
if (!process) {
return;
}
ObserveProcess(process);
mojo::AssociatedRemote<mojom::EventDispatcher>& worker_dispatcher =
rph_dispatcher_map_[process][worker_thread_id];
CHECK(!worker_dispatcher);
worker_dispatcher.Bind(std::move(event_dispatcher));
worker_dispatcher.set_disconnect_handler(
base::BindOnce(&EventRouter::UnbindServiceWorkerEventDispatcher,
weak_factory_.GetWeakPtr(), process, worker_thread_id));
}
void EventRouter::UnbindServiceWorkerEventDispatcher(RenderProcessHost* host,
int worker_thread_id) {
auto map = rph_dispatcher_map_.find(host);
if (map == rph_dispatcher_map_.end()) {
return;
}
map->second.erase(worker_thread_id);
}
Event::Event(events::HistogramValue histogram_value,
std::string_view event_name,
base::Value::List event_args)
: Event(histogram_value, event_name, std::move(event_args), nullptr) {}
Event::Event(events::HistogramValue histogram_value,
std::string_view event_name,
base::Value::List event_args,
content::BrowserContext* restrict_to_browser_context,
std::optional<mojom::ContextType> restrict_to_context_type)
: Event(histogram_value,
event_name,
std::move(event_args),
restrict_to_browser_context,
restrict_to_context_type,
GURL(),
EventRouter::UserGestureState::kUnknown,
mojom::EventFilteringInfo::New()) {}
Event::Event(events::HistogramValue histogram_value,
std::string_view event_name,
base::Value::List event_args,
content::BrowserContext* restrict_to_browser_context,
std::optional<mojom::ContextType> restrict_to_context_type,
const GURL& event_url,
EventRouter::UserGestureState user_gesture,
mojom::EventFilteringInfoPtr info,
bool lazy_background_active_on_dispatch,
base::TimeTicks dispatch_start_time)
: histogram_value(histogram_value),
event_name(event_name),
event_args(std::move(event_args)),
restrict_to_browser_context(restrict_to_browser_context),
restrict_to_context_type(restrict_to_context_type),
event_url(event_url),
dispatch_start_time(dispatch_start_time),
lazy_background_active_on_dispatch(lazy_background_active_on_dispatch),
user_gesture(user_gesture),
filter_info(std::move(info)) {
DCHECK_NE(events::UNKNOWN, histogram_value)
<< "events::UNKNOWN cannot be used as a histogram value.\n"
<< "If this is a test, use events::FOR_TEST.\n"
<< "If this is production code, it is important that you use a realistic "
<< "value so that we can accurately track event usage. "
<< "See extension_event_histogram_value.h for inspiration.";
}
Event::~Event() = default;
std::unique_ptr<Event> Event::CopySelectively(bool copy_event_args,
bool copy_filter_info) const {
auto copied_event_args =
copy_event_args ? event_args.Clone() : base::Value::List();
auto copied_filter_info =
copy_filter_info ? filter_info.Clone() : mojom::EventFilteringInfo::New();
auto copy = std::make_unique<Event>(
histogram_value, event_name, std::move(copied_event_args),
restrict_to_browser_context, restrict_to_context_type, event_url,
user_gesture, std::move(copied_filter_info),
lazy_background_active_on_dispatch, dispatch_start_time);
copy->will_dispatch_callback = will_dispatch_callback;
copy->did_dispatch_callback = did_dispatch_callback;
copy->cannot_dispatch_callback = cannot_dispatch_callback;
return copy;
}
std::unique_ptr<Event> Event::DeepCopy() const {
return CopySelectively(true, true);
}
EventListenerInfo::EventListenerInfo(const std::string& event_name,
const ExtensionId& extension_id,
const GURL& listener_url,
content::BrowserContext* browser_context)
: event_name(event_name),
extension_id(extension_id),
listener_url(listener_url),
browser_context(browser_context),
worker_thread_id(kMainThreadId),
service_worker_version_id(blink::mojom::kInvalidServiceWorkerVersionId),
is_lazy(false) {}
EventListenerInfo::EventListenerInfo(const std::string& event_name,
const ExtensionId& extension_id,
const GURL& listener_url,
content::BrowserContext* browser_context,
int worker_thread_id,
int64_t service_worker_version_id,
bool is_lazy)
: event_name(event_name),
extension_id(extension_id),
listener_url(listener_url),
browser_context(browser_context),
worker_thread_id(worker_thread_id),
service_worker_version_id(service_worker_version_id),
is_lazy(is_lazy) {}
}