зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1709997 - [devtools] Add AbortController support to EventEmitter. r=jdescottes.
Differential Revision: https://phabricator.services.mozilla.com/D119510
This commit is contained in:
Родитель
f6510e1b99
Коммит
80f1e7fb5f
|
@ -28,14 +28,23 @@ class EventEmitter {
|
||||||
* The type of event.
|
* The type of event.
|
||||||
* @param {Function|Object} listener
|
* @param {Function|Object} listener
|
||||||
* The listener that processes the event.
|
* The listener that processes the event.
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {AbortSignal} options.signal
|
||||||
|
* The listener will be removed when linked AbortController’s abort() method is called
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
* A function that removes the listener when called.
|
* A function that removes the listener when called.
|
||||||
*/
|
*/
|
||||||
static on(target, type, listener) {
|
static on(target, type, listener, { signal } = {}) {
|
||||||
if (typeof listener !== "function" && !isEventHandler(listener)) {
|
if (typeof listener !== "function" && !isEventHandler(listener)) {
|
||||||
throw new Error(BAD_LISTENER);
|
throw new Error(BAD_LISTENER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (signal?.aborted === true) {
|
||||||
|
// The signal is already aborted so don't setup the listener.
|
||||||
|
// We return an empty function as it's the expected returned value.
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
|
||||||
if (!(eventListeners in target)) {
|
if (!(eventListeners in target)) {
|
||||||
target[eventListeners] = new Map();
|
target[eventListeners] = new Map();
|
||||||
}
|
}
|
||||||
|
@ -48,7 +57,13 @@ class EventEmitter {
|
||||||
events.set(type, new Set([listener]));
|
events.set(type, new Set([listener]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => EventEmitter.off(target, type, listener);
|
const offFn = () => EventEmitter.off(target, type, listener);
|
||||||
|
|
||||||
|
if (signal) {
|
||||||
|
signal.addEventListener("abort", offFn, { once: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return offFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,7 +138,7 @@ class EventEmitter {
|
||||||
/**
|
/**
|
||||||
* Registers an event `listener` that is called only the next time an event
|
* 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 the given event `target`.
|
||||||
* It returns a promised resolved once the specified event `type` is emitted.
|
* It returns a Promise resolved once the specified event `type` is emitted.
|
||||||
*
|
*
|
||||||
* @param {Object} target
|
* @param {Object} target
|
||||||
* Event target object.
|
* Event target object.
|
||||||
|
@ -131,10 +146,13 @@ class EventEmitter {
|
||||||
* The type of the event.
|
* The type of the event.
|
||||||
* @param {Function|Object} [listener]
|
* @param {Function|Object} [listener]
|
||||||
* The listener that processes the event.
|
* The listener that processes the event.
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {AbortSignal} options.signal
|
||||||
|
* The listener will be removed when linked AbortController’s abort() method is called
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
* The promise resolved once the event `type` is emitted.
|
* The promise resolved once the event `type` is emitted.
|
||||||
*/
|
*/
|
||||||
static once(target, type, listener) {
|
static once(target, type, listener, options) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
// This is the actual listener that will be added to the target's listener, it wraps
|
// This is the actual listener that will be added to the target's listener, it wraps
|
||||||
// the call to the original `listener` given.
|
// the call to the original `listener` given.
|
||||||
|
@ -164,7 +182,7 @@ class EventEmitter {
|
||||||
};
|
};
|
||||||
|
|
||||||
newListener[onceOriginalListener] = listener;
|
newListener[onceOriginalListener] = listener;
|
||||||
EventEmitter.on(target, type, newListener);
|
EventEmitter.on(target, type, newListener, options);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
|
|
||||||
|
add_task(function testAbortSingleListener() {
|
||||||
|
// Test a simple case with AbortController
|
||||||
|
info("Create an EventEmitter");
|
||||||
|
const emitter = new EventEmitter();
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const { signal } = abortController;
|
||||||
|
|
||||||
|
info("Setup an event listener on test-event, controlled by an AbortSignal");
|
||||||
|
let eventsReceived = 0;
|
||||||
|
emitter.on("test-event", () => eventsReceived++, { signal });
|
||||||
|
|
||||||
|
info("Emit test-event");
|
||||||
|
emitter.emit("test-event");
|
||||||
|
equal(eventsReceived, 1, "We received one event, as expected");
|
||||||
|
|
||||||
|
info("Abort the AbortController…");
|
||||||
|
abortController.abort();
|
||||||
|
info("… and emit test-event again");
|
||||||
|
emitter.emit("test-event");
|
||||||
|
equal(eventsReceived, 1, "We didn't receive new event after aborting");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function testAbortSingleListenerOnce() {
|
||||||
|
// Test a simple case with AbortController and once
|
||||||
|
info("Create an EventEmitter");
|
||||||
|
const emitter = new EventEmitter();
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const { signal } = abortController;
|
||||||
|
|
||||||
|
info("Setup an event listener on test-event, controlled by an AbortSignal");
|
||||||
|
let eventReceived = false;
|
||||||
|
emitter.once(
|
||||||
|
"test-event",
|
||||||
|
() => {
|
||||||
|
eventReceived = true;
|
||||||
|
},
|
||||||
|
{ signal }
|
||||||
|
);
|
||||||
|
|
||||||
|
info("Abort the AbortController…");
|
||||||
|
abortController.abort();
|
||||||
|
info("… and emit test-event");
|
||||||
|
emitter.emit("test-event");
|
||||||
|
equal(eventReceived, false, "We didn't receive the event after aborting");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function testAbortMultipleListener() {
|
||||||
|
// Test aborting multiple event listeners with one call to abort
|
||||||
|
info("Create an EventEmitter");
|
||||||
|
const emitter = new EventEmitter();
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const { signal } = abortController;
|
||||||
|
|
||||||
|
info("Setup 3 event listeners controlled by an AbortSignal");
|
||||||
|
let eventsReceived = 0;
|
||||||
|
emitter.on("test-event", () => eventsReceived++, { signal });
|
||||||
|
emitter.on("test-event", () => eventsReceived++, { signal });
|
||||||
|
emitter.on("other-test-event", () => eventsReceived++, { signal });
|
||||||
|
|
||||||
|
info("Emit test-event and other-test-event");
|
||||||
|
emitter.emit("test-event");
|
||||||
|
emitter.emit("other-test-event");
|
||||||
|
equal(eventsReceived, 3, "We received 3 events, as expected");
|
||||||
|
|
||||||
|
info("Abort the AbortController…");
|
||||||
|
abortController.abort();
|
||||||
|
info("… and emit events again");
|
||||||
|
emitter.emit("test-event");
|
||||||
|
emitter.emit("other-test-event");
|
||||||
|
equal(eventsReceived, 3, "We didn't receive new event after aborting");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function testAbortMultipleEmitter() {
|
||||||
|
// Test aborting multiple event listeners on different emitters with one call to abort
|
||||||
|
info("Create 2 EventEmitter");
|
||||||
|
const emitter1 = new EventEmitter();
|
||||||
|
const emitter2 = new EventEmitter();
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const { signal } = abortController;
|
||||||
|
|
||||||
|
info("Setup 2 event listeners on test-event, controlled by an AbortSignal");
|
||||||
|
let eventsReceived = 0;
|
||||||
|
emitter1.on("test-event", () => eventsReceived++, { signal });
|
||||||
|
emitter2.on("other-test-event", () => eventsReceived++, { signal });
|
||||||
|
|
||||||
|
info("Emit test-event and other-test-event");
|
||||||
|
emitter1.emit("test-event");
|
||||||
|
emitter2.emit("other-test-event");
|
||||||
|
equal(eventsReceived, 2, "We received 2 events, as expected");
|
||||||
|
|
||||||
|
info("Abort the AbortController…");
|
||||||
|
abortController.abort();
|
||||||
|
info("… and emit events again");
|
||||||
|
emitter1.emit("test-event");
|
||||||
|
emitter2.emit("other-test-event");
|
||||||
|
equal(eventsReceived, 2, "We didn't receive new event after aborting");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function testAbortBeforeEmitting() {
|
||||||
|
// Check that aborting before emitting does unregister the event listener
|
||||||
|
info("Create an EventEmitter");
|
||||||
|
const emitter = new EventEmitter();
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const { signal } = abortController;
|
||||||
|
|
||||||
|
info("Setup an event listener on test-event, controlled by an AbortSignal");
|
||||||
|
let eventsReceived = 0;
|
||||||
|
emitter.on("test-event", () => eventsReceived++, { signal });
|
||||||
|
|
||||||
|
info("Abort the AbortController…");
|
||||||
|
abortController.abort();
|
||||||
|
|
||||||
|
info("… and emit test-event");
|
||||||
|
emitter.emit("test-event");
|
||||||
|
equal(eventsReceived, 0, "We didn't receive any event");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function testAbortBeforeSettingListener() {
|
||||||
|
// Check that aborting before creating the event listener won't register it
|
||||||
|
info("Create an EventEmitter");
|
||||||
|
const emitter = new EventEmitter();
|
||||||
|
|
||||||
|
info("Create an AbortController and abort it immediately");
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const { signal } = abortController;
|
||||||
|
abortController.abort();
|
||||||
|
|
||||||
|
info(
|
||||||
|
"Setup an event listener on test-event, controlled by the aborted AbortSignal"
|
||||||
|
);
|
||||||
|
let eventsReceived = 0;
|
||||||
|
const off = emitter.on("test-event", () => eventsReceived++, { signal });
|
||||||
|
|
||||||
|
info("Emit test-event");
|
||||||
|
emitter.emit("test-event");
|
||||||
|
equal(eventsReceived, 0, "We didn't receive any event");
|
||||||
|
|
||||||
|
equal(typeof off, "function", "emitter.on still returned a function");
|
||||||
|
// check that calling off does not throw
|
||||||
|
off();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function testAbortAfterEventListenerIsRemoved() {
|
||||||
|
// Check that aborting after there's no more event listener does not throw
|
||||||
|
info("Create an EventEmitter");
|
||||||
|
const emitter = new EventEmitter();
|
||||||
|
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const { signal } = abortController;
|
||||||
|
|
||||||
|
info(
|
||||||
|
"Setup an event listener on test-event, controlled by the aborted AbortSignal"
|
||||||
|
);
|
||||||
|
let eventsReceived = 0;
|
||||||
|
const off = emitter.on("test-event", () => eventsReceived++, { signal });
|
||||||
|
|
||||||
|
info("Emit test-event");
|
||||||
|
emitter.emit("test-event");
|
||||||
|
equal(eventsReceived, 1, "We received the expected event");
|
||||||
|
|
||||||
|
info("Remove the event listener with the function returned by `on`");
|
||||||
|
off();
|
||||||
|
|
||||||
|
info("Emit test-event a second time");
|
||||||
|
emitter.emit("test-event");
|
||||||
|
equal(
|
||||||
|
eventsReceived,
|
||||||
|
1,
|
||||||
|
"We didn't receive new event after removing the event listener"
|
||||||
|
);
|
||||||
|
|
||||||
|
info("Abort to check it doesn't throw");
|
||||||
|
abortController.abort();
|
||||||
|
});
|
|
@ -17,6 +17,7 @@ support-files =
|
||||||
# CSS properties are behind compile-time flags, and there is no automatic rebuild
|
# CSS properties are behind compile-time flags, and there is no automatic rebuild
|
||||||
# process for uplifts, so this test breaks on uplift.
|
# process for uplifts, so this test breaks on uplift.
|
||||||
run-if = nightly_build
|
run-if = nightly_build
|
||||||
|
[test_eventemitter_abort_controller.js]
|
||||||
[test_eventemitter_basic.js]
|
[test_eventemitter_basic.js]
|
||||||
[test_eventemitter_destroy.js]
|
[test_eventemitter_destroy.js]
|
||||||
[test_eventemitter_static.js]
|
[test_eventemitter_static.js]
|
||||||
|
|
Загрузка…
Ссылка в новой задаче