/**
* This module provides functionality for constructing events similar to
* WebExtensions `events.Event` objects.
*
* @module events
*/
import * as debugging from "./debugging.js";
/**
* @constant {debugging.debuggingLogger}
* @private
*/
const debugLog = debugging.getDebuggingLog("events");
/**
* A callback function that is called immediately before a listener is added.
* @callback addListenerCallback
* @param {Function} listener - The listener that is being added.
* @param {Object} options - The options for the listener.
*/
/**
* A callback function that is called immediately after a listener is removed.
* @callback removeListenerCallback
* @param {Function} listener - The listener that was removed.
* @param {Object} options - The options that the listener was added with.
*/
/**
* A callback function that is called when a listener may be notified via
* `notifyListeners()`.
* @callback notifyListenersCallback
* @param {Function} listener - The listener that may be called.
* @param {Array} listenerArguments - The arguments that would be passed to the listener
* function.
* @param {Options} options - The options that the listener was added with.
* @returns {boolean} Whether to call the listener.
*/
/**
* A class that provides an event API similar to WebExtensions `events.Event` objects.
* Use the `createEvent` function to create an `Event` object.
* @hideconstructor
*/
class Event {
/**
* Creates an event instance similar to WebExtensions `events.Event` objects.
* @param {Object} [options] - A set of options for the event.
* @param {name} [options.name] - The name of the event.
* @param {addListenerCallback} [options.addListenerCallback] - A function that is
* called when a listener is added.
* @param {removeListenerCallback} [options.removeListenerCallback] - A function
* that is called when a listener is removed.
* @param {notifyListenersCallback} [options.notifyListenersCallback] - A function
* that is called before a listener is notified and can filter the notification.
*/
constructor({
name = null,
addListenerCallback = null,
removeListenerCallback = null,
notifyListenersCallback = null
} = {
name: null,
addListenerCallback: null,
removeListenerCallback: null,
notifyListenersCallback: null
}) {
this.name = name;
this.addListenerCallback = addListenerCallback;
this.removeListenerCallback = removeListenerCallback;
this.notifyListenersCallback = notifyListenersCallback;
this.listeners = new Map();
}
/**
* Add an event listener with the specified options. If the listener has
* previously been added for the event, the listener's options will be
* updated.
* @param {Function} listener - The listener to call when the event fires.
* @param {Object} options - Options for when the listener should be called.
*/
addListener(listener, options) {
if(this.addListenerCallback !== null) {
this.addListenerCallback(listener, options);
}
this.listeners.set(listener, options);
// If the event has a name, annotate the listener with the name
if(typeof this.name === "string") {
listener.webScienceEventName = this.name;
}
}
/**
* Remove an event listener.
* @param {Function} listener - The listener to remove.
*/
removeListener(listener) {
if(this.removeListenerCallback !== null) {
this.removeListenerCallback(listener, this.listeners.get(listener));
}
this.listeners.delete(listener);
}
/**
* Check whether a particular event listener has been added.
* @param {EventCallbackFunction} listener - The listener to check.
* @returns {boolean} Whether the listener has been added.
*/
hasListener(listener) {
return this.listeners.has(listener);
}
/**
* Check whether there are any listeners for the event.
* @returns {boolean} Whether there are any listeners for the event.
*/
hasAnyListeners() {
return this.listeners.size > 0;
}
/**
* Notify the listeners for the event.
* @param {Array} [listenerArguments=[]] - The arguments that will be passed to the
* listeners.
*/
notifyListeners(listenerArguments = []) {
this.listeners.forEach((options, listener) => {
try {
if((this.notifyListenersCallback === null) || this.notifyListenersCallback(listener, listenerArguments, options)) {
listener.apply(null, listenerArguments);
}
}
catch(error) {
debugLog(`Error in listener notification: ${error}`);
}
});
}
}
/**
* An extension of the Event class that permits only one listener at a time.
* @template EventCallbackFunction
* @template EventOptions
* @extends {Event<EventCallbackFunction, EventOptions>}
* @private
*/
class EventSingleton extends Event {
/**
* A function that adds an event listener, with optional parameters. If the
* listener has previously been added for the event, the listener's options
* (if any) will be updated.
* @param {EventCallbackFunction} listener - The function to call when the event fires.
* @param {EventOptions} options - Options for when the listener should be called.
* The supported option(s) depend on the event type.
* @throws {Error} This function throws an Error if there is already a listener for
* the event.
*/
addListener(listener, options) {
if(this.listeners.size > 0)
throw new Error("Error: cannot add more than one listener to EventSingleton event.");
super.addListener(listener, options);
}
}
/**
* Create a new Event object that implements WebExtensions event syntax, with the
* provided options.
* @param {Object} [options] - The options for the event.
* @param {string} options.name - The name of the event.
* @param {addListenerCallback} [options.addListenerCallback] - A function that is
* called when a listener is added.
* @param {removeListenerCallback} [options.removeListenerCallback] - A function
* that is called when a listener is removed.
* @param {notifyListenersCallback} [options.notifyListenersCallback] - A function
* that is called before a listener is notified and can filter the notification.
* @param {boolean} [options.singleton = false] - Whether to allow only one listener
* for the event.
* @returns {Event} - The created `Event` object.
*/
export function createEvent({
name = null,
addListenerCallback = null,
removeListenerCallback = null,
notifyListenersCallback = null,
singleton = false
} = {
name: null,
addListenerCallback: null,
removeListenerCallback: null,
notifyListenersCallback: null,
singleton: false
}) {
if(singleton) {
return /*@__PURE__*/new EventSingleton({
name,
addListenerCallback,
removeListenerCallback,
notifyListenersCallback
});
}
return /*@__PURE__*/new Event({
name,
addListenerCallback,
removeListenerCallback,
notifyListenersCallback
});
}