Skip to content

Commit 97a286b

Browse files
committed
lib: propagate aborted state to dependent signals before firing events
1 parent 67357ba commit 97a286b

File tree

2 files changed

+79
-4
lines changed

2 files changed

+79
-4
lines changed

lib/internal/abort_controller.js

+38-4
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
// in https://github.com/mysticatea/abort-controller (MIT license)
55

66
const {
7+
ArrayPrototypePush,
78
ObjectAssign,
89
ObjectDefineProperties,
910
ObjectDefineProperty,
1011
PromiseResolve,
1112
SafeFinalizationRegistry,
1213
SafeSet,
14+
SetPrototypeForEach,
1315
Symbol,
1416
SymbolToStringTag,
1517
WeakRef,
@@ -372,18 +374,50 @@ ObjectDefineProperty(AbortSignal.prototype, SymbolToStringTag, {
372374

373375
defineEventHandler(AbortSignal.prototype, 'abort');
374376

377+
// https://dom.spec.whatwg.org/#dom-abortsignal-abort
375378
function abortSignal(signal, reason) {
379+
// 1. If signal is aborted, then return.
376380
if (signal[kAborted]) return;
381+
382+
// 2. Set signal's abort reason to reason if it is given;
383+
// otherwise to a new "AbortError" DOMException.
377384
signal[kAborted] = true;
378385
signal[kReason] = reason;
386+
// 3. Let dependentSignalsToAbort be a new list.
387+
const dependentSignalsToAbort = [];
388+
// 4. For each dependentSignal of signal's dependent signals:
389+
if (signal[kDependantSignals]) {
390+
SetPrototypeForEach(signal[kDependantSignals], (s) => {
391+
const dependentSignal = s.deref();
392+
// 1. If dependentSignal is not aborted, then:
393+
if (dependentSignal && !dependentSignal[kAborted]) {
394+
// 1. Set dependentSignal's abort reason to signal's abort reason.
395+
dependentSignal[kReason] = reason;
396+
dependentSignal[kAborted] = true;
397+
// 2. Append dependentSignal to dependentSignalsToAbort.
398+
ArrayPrototypePush(dependentSignalsToAbort, dependentSignal);
399+
}
400+
});
401+
}
402+
// 5. Run the abort steps for signal
403+
runAbort(signal);
404+
// 6. For each dependentSignal of dependentSignalsToAbort,
405+
// run the abort steps for dependentSignal.
406+
for (let i = 0; i < dependentSignalsToAbort.length; i++) {
407+
const dependentSignal = dependentSignalsToAbort[i];
408+
runAbort(dependentSignal);
409+
};
410+
}
411+
412+
// To run the abort steps for an AbortSignal signal
413+
function runAbort(signal) {
414+
// TODO(jazelly): 1. For each algorithm of signal's abort algorithms: run algorithm.
415+
// TODO(jazelly): 2. Empty signal's abort algorithms.
416+
// 3. Fire an event named abort at signal.
379417
const event = new Event('abort', {
380418
[kTrustEvent]: true,
381419
});
382420
signal.dispatchEvent(event);
383-
signal[kDependantSignals]?.forEach((s) => {
384-
const signalRef = s.deref();
385-
if (signalRef) abortSignal(signalRef, reason);
386-
});
387421
}
388422

389423
class AbortController {

test/parallel/test-abortsignal-any.mjs

+41
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,45 @@ describe('AbortSignal.any()', { concurrency: !process.env.TEST_PARALLEL }, () =>
118118
controller.abort();
119119
assert.strictEqual(result, 1);
120120
});
121+
122+
// Refs: https://github.com/web-platform-tests/wpt/pull/47653
123+
it('marks dependent signals aborted before abort events fire', () => {
124+
const controller = new AbortController();
125+
const signal1 = AbortSignal.any([controller.signal]);
126+
const signal2 = AbortSignal.any([signal1]);
127+
let eventFired = false;
128+
129+
controller.signal.addEventListener('abort', () => {
130+
const signal3 = AbortSignal.any([signal2]);
131+
assert(controller.signal.aborted);
132+
assert(signal1.aborted);
133+
assert(signal2.aborted);
134+
assert(signal3.aborted);
135+
eventFired = true;
136+
});
137+
138+
controller.abort();
139+
assert(eventFired, 'event fired');
140+
});
141+
142+
// Refs: https://github.com/web-platform-tests/wpt/pull/47653
143+
it('aborts dependent signals correctly for reentrant aborts', () => {
144+
const controller1 = new AbortController();
145+
const controller2 = new AbortController();
146+
const signal = AbortSignal.any([controller1.signal, controller2.signal]);
147+
let count = 0;
148+
149+
controller1.signal.addEventListener('abort', () => {
150+
controller2.abort('reason 2');
151+
});
152+
153+
signal.addEventListener('abort', () => {
154+
count++;
155+
});
156+
157+
controller1.abort('reason 1');
158+
assert.strictEqual(count, 1);
159+
assert(signal.aborted);
160+
assert.strictEqual(signal.reason, 'reason 1');
161+
});
121162
});

0 commit comments

Comments
 (0)