tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mdevtools/shared/event-emitter.js | 159+++++++++++++++++++++++++++++--------------------------------------------------
Ddevtools/shared/tests/xpcshell/test_eventemitter_static.js | 283-------------------------------------------------------------------------------
Mdevtools/shared/tests/xpcshell/xpcshell.toml | 2--
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'",