commit ecacb2f9a51c52952a3fe4717c1a1c0c7398228f
parent da3530396238bed78b46aaf9a1951f17144c2b5b
Author: Nicolas Chevobbe <nchevobbe@mozilla.com>
Date: Tue, 7 Oct 2025 05:35:37 +0000
Bug 1599082 - [devtools] Remove static EventEmitter methods. r=devtools-reviewers,jdescottes.
Let's always use instance methods instead so we're consistent in the whole codebase.
We only keep the EventEmitter.decorate that is used significantly in DevTools
to handle old-syle function + prototype "classes".
We might also save a few cycles by avoid wrapping the static methods that was
done from the inheritence based ones.
Remove the test that was asserting the static methods.
Differential Revision: https://phabricator.services.mozilla.com/D267590
Diffstat:
3 files changed, 59 insertions(+), 385 deletions(-)
diff --git a/devtools/shared/event-emitter.js b/devtools/shared/event-emitter.js
@@ -12,11 +12,24 @@ loader.lazyRequireGetter(this, "flags", "resource://devtools/shared/flags.js");
class EventEmitter {
/**
+ * Decorate an object with event emitter functionality; basically using the
+ * class' prototype as mixin.
+ *
+ * @param Object target
+ * The object to decorate.
+ * @return Object
+ * The object given, mixed.
+ */
+ static decorate(target) {
+ const descriptors = Object.getOwnPropertyDescriptors(this.prototype);
+ delete descriptors.constructor;
+ return Object.defineProperties(target, descriptors);
+ }
+
+ /**
* Registers an event `listener` that is called every time events of
- * specified `type` is emitted on the given event `target`.
+ * specified `type` is emitted on this instance.
*
- * @param {Object} target
- * Event target object.
* @param {String} type
* The type of event.
* @param {Function} listener
@@ -27,7 +40,7 @@ class EventEmitter {
* @returns {Function}
* A function that removes the listener when called.
*/
- static on(target, type, listener, { signal } = {}) {
+ on(type, listener, { signal } = {}) {
if (typeof listener !== "function") {
throw new Error(BAD_LISTENER);
}
@@ -38,11 +51,11 @@ class EventEmitter {
return () => {};
}
- if (!(eventListeners in target)) {
- target[eventListeners] = new Map();
+ if (!(eventListeners in this)) {
+ this[eventListeners] = new Map();
}
- const events = target[eventListeners];
+ const events = this[eventListeners];
if (events.has(type)) {
events.get(type).add(listener);
@@ -50,7 +63,7 @@ class EventEmitter {
events.set(type, new Set([listener]));
}
- const offFn = () => EventEmitter.off(target, type, listener);
+ const offFn = () => this.off(type, listener);
if (signal) {
signal.addEventListener("abort", offFn, { once: true });
@@ -60,28 +73,24 @@ class EventEmitter {
}
/**
- * Removes an event `listener` for the given event `type` on the given event
- * `target`. If no `listener` is passed removes all listeners of the given
- * `type`. If `type` is not passed removes all the listeners of the given
- * event `target`.
- * @param {Object} target
- * The event target object.
+ * Removes an event `listener` for the given event `type` on this instance
+ * If no `listener` is passed removes all listeners of the given
+ * `type`. If `type` is not passed removes all the listeners of this instance.
* @param {String} [type]
* The type of event.
* @param {Function} [listener]
* The listener that processes the event.
*/
- static off(target, type, listener) {
+ off(type, listener) {
const length = arguments.length;
- const events = target[eventListeners];
+ const events = this[eventListeners];
if (!events) {
return;
}
- if (length >= 3) {
- // Trying to remove from the `target` the `listener` specified for the
- // event's `type` given.
+ if (length >= 2) {
+ // Trying to remove from `this` the `listener` specified for the event's `type` given.
const listenersForType = events.get(type);
// If we don't have listeners for the event's type, we bail out.
@@ -94,20 +103,20 @@ class EventEmitter {
listenersForType.delete(listener);
delete listener[onceResolvers];
}
- } else if (length === 2) {
+ } else if (length === 1) {
// No listener was given, it means we're removing all the listeners from
// the given event's `type`.
if (events.has(type)) {
events.delete(type);
}
- } else if (length === 1) {
- // With only the `target` given, we're removing all the listeners from the object.
+ } else if (length === 0) {
+ // With no parameter passed, we're removing all the listeners from this.
events.clear();
}
}
- static clearEvents(target) {
- const events = target[eventListeners];
+ clearEvents() {
+ const events = this[eventListeners];
if (!events) {
return;
}
@@ -116,11 +125,9 @@ class EventEmitter {
/**
* Registers an event `listener` that is called only the next time an event
- * of the specified `type` is emitted on the given event `target`.
+ * of the specified `type` is emitted on this instance.
* It returns a Promise resolved once the specified event `type` is emitted.
*
- * @param {Object} target
- * Event target object.
* @param {String} type
* The type of the event.
* @param {Function} [listener]
@@ -131,29 +138,33 @@ class EventEmitter {
* @return {Promise}
* The promise resolved once the event `type` is emitted.
*/
- static once(target, type, listener = function () {}, options) {
+ once(type, listener = function () {}, options) {
const { promise, resolve } = Promise.withResolvers();
if (!listener[onceResolvers]) {
listener[onceResolvers] = [];
}
listener[onceResolvers].push(resolve);
- EventEmitter.on(target, type, listener, options);
+ this.on(type, listener, options);
return promise;
}
- static emit(target, type, ...rest) {
- EventEmitter._emit(target, type, false, rest);
+ emit(type, ...rest) {
+ this._emit(type, false, rest);
}
- static emitAsync(target, type, ...rest) {
- return EventEmitter._emit(target, type, true, rest);
+ emitAsync(type, ...rest) {
+ return this._emit(type, true, rest);
+ }
+
+ emitForTests(type, ...rest) {
+ if (flags.testing) {
+ this.emit(type, ...rest);
+ }
}
/**
- * Emit an event of a given `type` on a given `target` object.
+ * Emit an event of a given `type` on this instance.
*
- * @param {Object} target
- * Event target object.
* @param {String} type
* The type of the event.
* @param {Boolean} async
@@ -165,12 +176,12 @@ class EventEmitter {
* If `async` argument is true, returns the promise resolved once all listeners have resolved.
* Otherwise, this function returns undefined;
*/
- static _emit(target, type, async, args) {
+ _emit(type, async, args) {
if (loggingEnabled) {
logEvent(type, args);
}
- const targetEventListeners = target[eventListeners];
+ const targetEventListeners = this[eventListeners];
if (!targetEventListeners) {
return undefined;
}
@@ -186,7 +197,7 @@ class EventEmitter {
// in emit.
for (const listener of new Set(listeners)) {
// If the object was destroyed during event emission, stop emitting.
- if (!(eventListeners in target)) {
+ if (!(eventListeners in this)) {
break;
}
@@ -194,20 +205,20 @@ class EventEmitter {
// event handler we're going to fire wasn't removed.
if (listeners && listeners.has(listener)) {
try {
- // If this was a one-off listener (add via `EventEmitter.once`), unregister the
+ // If this was a one-off listener (add via `EventEmitter#once`), unregister the
// listener right away, before firing the listener, to prevent re-entry in case
// the listener fires the same event again.
const resolvers = listener[onceResolvers];
if (resolvers) {
- EventEmitter.off(target, type, listener);
+ this.off(type, listener);
}
- const promise = listener.apply(target, args);
- // Resolve the promise returned by `EventEmitter.once` only after having called
+ const promise = listener.apply(this, args);
+ // Resolve the promise returned by `EventEmitter#once` only after having called
// the listener.
if (resolvers) {
for (const resolver of resolvers) {
// Resolve with the first argument fired on the listened event
- // (`EventEmitter.once` listeners don't have access to all the other arguments).
+ // (`EventEmitter#once` listeners don't have access to all the other arguments).
resolver(args[0]);
}
}
@@ -239,19 +250,16 @@ class EventEmitter {
}
/**
- * Returns a number of event listeners registered for the given event `type`
- * on the given event `target`.
+ * Returns a number of event listeners registered for the given event `type` on this instance.
*
- * @param {Object} target
- * Event target object.
* @param {String} type
* The type of event.
* @return {Number}
* The number of event listeners.
*/
- static count(target, type) {
- if (eventListeners in target) {
- const listenersForType = target[eventListeners].get(type);
+ count(type) {
+ if (eventListeners in this) {
+ const listenersForType = this[eventListeners].get(type);
if (listenersForType) {
return listenersForType.size;
@@ -260,55 +268,6 @@ class EventEmitter {
return 0;
}
-
- /**
- * Decorate an object with event emitter functionality; basically using the
- * class' prototype as mixin.
- *
- * @param Object target
- * The object to decorate.
- * @return Object
- * The object given, mixed.
- */
- static decorate(target) {
- const descriptors = Object.getOwnPropertyDescriptors(this.prototype);
- delete descriptors.constructor;
- return Object.defineProperties(target, descriptors);
- }
-
- on(...args) {
- return EventEmitter.on(this, ...args);
- }
-
- off(...args) {
- EventEmitter.off(this, ...args);
- }
-
- clearEvents() {
- EventEmitter.clearEvents(this);
- }
-
- once(...args) {
- return EventEmitter.once(this, ...args);
- }
-
- emit(...args) {
- EventEmitter.emit(this, ...args);
- }
-
- emitAsync(...args) {
- return EventEmitter.emitAsync(this, ...args);
- }
-
- emitForTests(...args) {
- if (flags.testing) {
- EventEmitter.emit(this, ...args);
- }
- }
-
- count(...args) {
- return EventEmitter.count(this, ...args);
- }
}
module.exports = EventEmitter;
diff --git a/devtools/shared/tests/xpcshell/test_eventemitter_static.js b/devtools/shared/tests/xpcshell/test_eventemitter_static.js
@@ -1,283 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-const {
- ConsoleAPIListener,
-} = require("resource://devtools/server/actors/webconsole/listeners/console-api.js");
-const {
- on,
- once,
- off,
- emit,
- count,
-} = require("resource://devtools/shared/event-emitter.js");
-
-const pass = message => ok(true, message);
-const fail = message => ok(false, message);
-
-/**
- * Each method of this object is a test; tests can be synchronous or asynchronous:
- *
- * 1. Plain method are synchronous tests.
- * 2. methods with `async` keyword are asynchronous tests.
- * 3. methods with `done` as argument are asynchronous tests (`done` needs to be called to
- * complete the test).
- */
-const TESTS = {
- testAddListener() {
- const events = [{ name: "event#1" }, "event#2"];
- const target = { name: "target" };
-
- on(target, "message", function (message) {
- equal(this, target, "this is a target object");
- equal(message, events.shift(), "message is emitted event");
- });
-
- emit(target, "message", events[0]);
- emit(target, "message", events[0]);
- },
-
- testListenerIsUniquePerType() {
- const actual = [];
- const target = {};
- listener = () => actual.push(1);
-
- on(target, "message", listener);
- on(target, "message", listener);
- on(target, "message", listener);
- on(target, "foo", listener);
- on(target, "foo", listener);
-
- emit(target, "message");
- deepEqual([1], actual, "only one message listener added");
-
- emit(target, "foo");
- deepEqual([1, 1], actual, "same listener added for other event");
- },
-
- testEventTypeMatters() {
- const target = { name: "target" };
- on(target, "message", () => fail("no event is expected"));
- on(target, "done", () => pass("event is emitted"));
-
- emit(target, "foo");
- emit(target, "done");
- },
-
- testAllArgumentsArePassed() {
- const foo = { name: "foo" },
- bar = "bar";
- const target = { name: "target" };
-
- on(target, "message", (a, b) => {
- equal(a, foo, "first argument passed");
- equal(b, bar, "second argument passed");
- });
-
- emit(target, "message", foo, bar);
- },
-
- testNoSideEffectsInEmit() {
- const target = { name: "target" };
-
- on(target, "message", () => {
- pass("first listener is called");
-
- on(target, "message", () => fail("second listener is called"));
- });
- emit(target, "message");
- },
-
- testCanRemoveNextListener() {
- const target = { name: "target" };
-
- on(target, "data", () => {
- pass("first listener called");
- off(target, "data", fail);
- });
- on(target, "data", fail);
-
- emit(target, "data", "Listener should be removed");
- },
-
- testOrderOfPropagation() {
- const actual = [];
- const target = { name: "target" };
-
- on(target, "message", () => actual.push(1));
- on(target, "message", () => actual.push(2));
- on(target, "message", () => actual.push(3));
- emit(target, "message");
-
- deepEqual([1, 2, 3], actual, "called in order they were added");
- },
-
- testRemoveListener() {
- const target = { name: "target" };
- const actual = [];
-
- on(target, "message", function listener() {
- actual.push(1);
- on(target, "message", () => {
- off(target, "message", listener);
- actual.push(2);
- });
- });
-
- emit(target, "message");
- deepEqual([1], actual, "first listener called");
-
- emit(target, "message");
- deepEqual([1, 1, 2], actual, "second listener called");
-
- emit(target, "message");
- deepEqual([1, 1, 2, 2, 2], actual, "first listener removed");
- },
-
- testRemoveAllListenersForType() {
- const actual = [];
- const target = { name: "target" };
-
- on(target, "message", () => actual.push(1));
- on(target, "message", () => actual.push(2));
- on(target, "message", () => actual.push(3));
- on(target, "bar", () => actual.push("b"));
- off(target, "message");
-
- emit(target, "message");
- emit(target, "bar");
-
- deepEqual(["b"], actual, "all message listeners were removed");
- },
-
- testRemoveAllListeners() {
- const actual = [];
- const target = { name: "target" };
-
- on(target, "message", () => actual.push(1));
- on(target, "message", () => actual.push(2));
- on(target, "message", () => actual.push(3));
- on(target, "bar", () => actual.push("b"));
-
- off(target);
-
- emit(target, "message");
- emit(target, "bar");
-
- deepEqual([], actual, "all listeners events were removed");
- },
-
- testFalsyArgumentsAreFine() {
- let type, listener;
- const target = { name: "target" },
- actual = [];
- on(target, "bar", () => actual.push(0));
-
- off(target, "bar", listener);
- emit(target, "bar");
- deepEqual([0], actual, "3rd bad arg will keep listener");
-
- off(target, type);
- emit(target, "bar");
- deepEqual([0, 0], actual, "2nd bad arg will keep listener");
-
- off(target, type, listener);
- emit(target, "bar");
- deepEqual([0, 0, 0], actual, "2nd & 3rd bad args will keep listener");
- },
-
- testUnhandledExceptions(done) {
- const listener = new ConsoleAPIListener(null, message => {
- equal(message.level, "error", "Got the first exception");
- equal(
- message.arguments[0].message,
- "Boom!",
- "unhandled exception is logged"
- );
-
- listener.destroy();
- done();
- });
-
- listener.init();
-
- const target = {};
-
- on(target, "message", () => {
- throw Error("Boom!");
- });
-
- emit(target, "message");
- },
-
- testCount() {
- const target = { name: "target" };
-
- equal(count(target, "foo"), 0, "no listeners for 'foo' events");
- on(target, "foo", () => {});
- equal(count(target, "foo"), 1, "listener registered");
- on(target, "foo", () => {});
- equal(count(target, "foo"), 2, "another listener registered");
- off(target);
- equal(count(target, "foo"), 0, "listeners unregistered");
- },
-
- async testOnce() {
- const target = { name: "target" };
- const called = false;
-
- const pFoo = once(target, "foo", function (value) {
- ok(!called, "listener called only once");
- equal(value, "bar", "correct argument was passed");
- equal(this, target, "the contextual object is correct");
- });
- const pDone = once(target, "done");
-
- emit(target, "foo", "bar");
- emit(target, "foo", "baz");
- emit(target, "done", "");
-
- await Promise.all([pFoo, pDone]);
- },
-
- testRemovingOnce(done) {
- const target = { name: "target" };
-
- once(target, "foo", fail);
- once(target, "done", done);
-
- off(target, "foo", fail);
-
- emit(target, "foo", "listener was called");
- emit(target, "done", "");
- },
-
- testCallingOffWithMoreThan3Args() {
- const target = { name: "target" };
- on(target, "data", fail);
- off(target, "data", fail, undefined);
- emit(target, "data", "Listener should be removed");
- },
-};
-
-/**
- * Create a runnable tests based on the tests descriptor given.
- *
- * @param {Object} tests
- * The tests descriptor object, contains the tests to run.
- */
-const runnable = tests =>
- async function () {
- for (const name of Object.keys(tests)) {
- info(name);
- if (tests[name].length === 1) {
- await new Promise(resolve => tests[name](resolve));
- } else {
- await tests[name]();
- }
- }
- };
-
-add_task(runnable(TESTS));
diff --git a/devtools/shared/tests/xpcshell/xpcshell.toml b/devtools/shared/tests/xpcshell/xpcshell.toml
@@ -28,8 +28,6 @@ skip-if = [
["test_eventemitter_destroy.js"]
-["test_eventemitter_static.js"]
-
["test_executeSoon.js"]
skip-if = [
"os == 'android' && android_version == '24' && processor == 'x86_64'",