forked from mirrors/gecko-dev
186 lines
5.5 KiB
JavaScript
186 lines
5.5 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
ContextDescriptorType:
|
|
"chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs",
|
|
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
|
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
|
|
});
|
|
|
|
XPCOMUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
|
|
|
|
/**
|
|
* Helper to listen to events which rely on SessionData.
|
|
* In order to support the EventsDispatcher, a module emitting events should
|
|
* subscribe and unsubscribe to those events based on SessionData updates
|
|
* and should use the "event" SessionData category.
|
|
*/
|
|
export class EventsDispatcher {
|
|
// The MessageHandler owning this EventsDispatcher.
|
|
#messageHandler;
|
|
|
|
/**
|
|
* @typedef {Object} EventListenerInfo
|
|
* @property {ContextDescriptor} contextDescriptor
|
|
* The ContextDescriptor to which those callbacks are associated
|
|
* @property {Set<Function>} callbacks
|
|
* The callbacks to trigger when an event matching the ContextDescriptor
|
|
* is received.
|
|
*/
|
|
|
|
// Map from event name to map of strings (context keys) to EventListenerInfo.
|
|
#listenersByEventName;
|
|
|
|
/**
|
|
* Create a new EventsDispatcher instance.
|
|
*
|
|
* @param {MessageHandler} messageHandler
|
|
* The MessageHandler owning this EventsDispatcher.
|
|
*/
|
|
constructor(messageHandler) {
|
|
this.#messageHandler = messageHandler;
|
|
|
|
this.#listenersByEventName = new Map();
|
|
}
|
|
|
|
destroy() {
|
|
for (const event of this.#listenersByEventName.keys()) {
|
|
this.#messageHandler.off(event, this.#onMessageHandlerEvent);
|
|
}
|
|
|
|
this.#listenersByEventName = null;
|
|
}
|
|
|
|
/**
|
|
* Stop listening for an event relying on SessionData and relayed by the
|
|
* message handler.
|
|
*
|
|
* @param {string} event
|
|
* Name of the event to unsubscribe from.
|
|
* @param {ContextDescriptor} contextDescriptor
|
|
* Context descriptor for this event.
|
|
* @param {function} callback
|
|
* Event listener callback.
|
|
* @return {Promise}
|
|
* Promise which resolves when the event fully unsubscribed, including
|
|
* propagating the necessary session data.
|
|
*/
|
|
async off(event, contextDescriptor, callback) {
|
|
const listeners = this.#listenersByEventName.get(event);
|
|
if (!listeners) {
|
|
return;
|
|
}
|
|
|
|
const key = this.#getContextKey(contextDescriptor);
|
|
if (!listeners.has(key)) {
|
|
return;
|
|
}
|
|
|
|
const { callbacks } = listeners.get(key);
|
|
if (callbacks.has(callback)) {
|
|
callbacks.delete(callback);
|
|
if (callbacks.size === 0) {
|
|
listeners.delete(key);
|
|
if (listeners.size === 0) {
|
|
this.#messageHandler.off(event, this.#onMessageHandlerEvent);
|
|
this.#listenersByEventName.delete(event);
|
|
}
|
|
|
|
await this.#messageHandler.removeSessionData(
|
|
this.#getSessionDataItem(event, contextDescriptor)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Listen for an event relying on SessionData and relayed by the message
|
|
* handler.
|
|
*
|
|
* @param {string} event
|
|
* Name of the event to subscribe to.
|
|
* @param {ContextDescriptor} contextDescriptor
|
|
* Context descriptor for this event.
|
|
* @param {function} callback
|
|
* Event listener callback.
|
|
* @return {Promise}
|
|
* Promise which resolves when the event fully subscribed to, including
|
|
* propagating the necessary session data.
|
|
*/
|
|
async on(event, contextDescriptor, callback) {
|
|
if (!this.#listenersByEventName.has(event)) {
|
|
this.#listenersByEventName.set(event, new Map());
|
|
this.#messageHandler.on(event, this.#onMessageHandlerEvent);
|
|
}
|
|
|
|
const key = this.#getContextKey(contextDescriptor);
|
|
const listeners = this.#listenersByEventName.get(event);
|
|
if (listeners.has(key)) {
|
|
const { callbacks } = listeners.get(key);
|
|
callbacks.add(callback);
|
|
} else {
|
|
const callbacks = new Set([callback]);
|
|
listeners.set(key, { callbacks, contextDescriptor });
|
|
await this.#messageHandler.addSessionData(
|
|
this.#getSessionDataItem(event, contextDescriptor)
|
|
);
|
|
}
|
|
}
|
|
|
|
#getContextKey(contextDescriptor) {
|
|
const { id, type } = contextDescriptor;
|
|
return `${type}-${id}`;
|
|
}
|
|
|
|
#getSessionDataItem(event, contextDescriptor) {
|
|
const [moduleName] = event.split(".");
|
|
return {
|
|
moduleName,
|
|
category: "event",
|
|
contextDescriptor,
|
|
values: [event],
|
|
};
|
|
}
|
|
|
|
#matchesContext(contextInfo, contextDescriptor) {
|
|
if (contextDescriptor.type === lazy.ContextDescriptorType.All) {
|
|
return true;
|
|
}
|
|
|
|
if (
|
|
contextDescriptor.type === lazy.ContextDescriptorType.TopBrowsingContext
|
|
) {
|
|
const eventBrowsingContext = lazy.TabManager.getBrowsingContextById(
|
|
contextInfo.contextId
|
|
);
|
|
return eventBrowsingContext?.browserId === contextDescriptor.id;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#onMessageHandlerEvent = (name, event, contextInfo) => {
|
|
const listeners = this.#listenersByEventName.get(name);
|
|
for (const { callbacks, contextDescriptor } of listeners.values()) {
|
|
if (!this.#matchesContext(contextInfo, contextDescriptor)) {
|
|
continue;
|
|
}
|
|
|
|
for (const callback of callbacks) {
|
|
try {
|
|
callback(name, event);
|
|
} catch (e) {
|
|
lazy.logger.debug(
|
|
`Error while executing callback for ${name}: ${e.message}`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|