Skip to content

Commit be7dd22

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

File tree

2 files changed

+77
-4
lines changed

2 files changed

+77
-4
lines changed

lib/internal/abort_controller.js

+36-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,48 @@ 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+
// Equivalent to Set.prototype.forEach
391+
SetPrototypeForEach(signal[kDependantSignals], (s) => {
392+
const dependentSignal = s.deref();
393+
// 1. If dependentSignal is not aborted, then:
394+
if (dependentSignal && !dependentSignal[kAborted]) {
395+
// 1. Set dependentSignal's abort reason to signal's abort reason.
396+
dependentSignal[kReason] = reason;
397+
dependentSignal[kAborted] = true;
398+
// 2. Append dependentSignal to dependentSignalsToAbort.
399+
ArrayPrototypePush(dependentSignalsToAbort, dependentSignal);
400+
}
401+
});
402+
}
403+
// 5. Run the abort steps for signal
404+
runAbort(signal);
405+
// 6. For each dependentSignal of dependentSignalsToAbort,
406+
// run the abort steps for dependentSignal.
407+
for (let i = 0; i < dependentSignalsToAbort.length; i++) {
408+
const dependentSignal = dependentSignalsToAbort[i];
409+
runAbort(dependentSignal);
410+
};
411+
}
412+
413+
// To run the abort steps for an AbortSignal signal
414+
function runAbort(signal) {
379415
const event = new Event('abort', {
380416
[kTrustEvent]: true,
381417
});
382418
signal.dispatchEvent(event);
383-
signal[kDependantSignals]?.forEach((s) => {
384-
const signalRef = s.deref();
385-
if (signalRef) abortSignal(signalRef, reason);
386-
});
387419
}
388420

389421
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)