Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 97b00c3

Browse filesBrowse files
atlowChemiRafaelGSS
authored andcommitted
lib: add option to force handling stopped events
PR-URL: #48301 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
1 parent 7d01c88 commit 97b00c3
Copy full SHA for 97b00c3

File tree

Expand file treeCollapse file tree

4 files changed

+59
-11
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+59
-11
lines changed
Open diff view settings
Collapse file

‎lib/events.js‎

Copy file name to clipboardExpand all lines: lib/events.js
+8-3Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const {
6161
let spliceOne;
6262
let FixedQueue;
6363
let kFirstEventParam;
64+
let kResistStopPropagation;
6465

6566
const {
6667
AbortError,
@@ -978,7 +979,10 @@ async function once(emitter, name, options = kEmptyObject) {
978979
}
979980
resolve(args);
980981
};
981-
eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });
982+
983+
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
984+
const opts = { __proto__: null, once: true, [kResistStopPropagation]: true };
985+
eventTargetAgnosticAddListener(emitter, name, resolver, opts);
982986
if (name !== 'error' && typeof emitter.once === 'function') {
983987
// EventTarget does not have `error` event semantics like Node
984988
// EventEmitters, we listen to `error` events only on EventEmitters.
@@ -991,7 +995,7 @@ async function once(emitter, name, options = kEmptyObject) {
991995
}
992996
if (signal != null) {
993997
eventTargetAgnosticAddListener(
994-
signal, 'abort', abortListener, { once: true });
998+
signal, 'abort', abortListener, { __proto__: null, once: true, [kResistStopPropagation]: true });
995999
}
9961000
});
9971001
}
@@ -1149,11 +1153,12 @@ function on(emitter, event, options = kEmptyObject) {
11491153
}
11501154
}
11511155
if (signal) {
1156+
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
11521157
eventTargetAgnosticAddListener(
11531158
signal,
11541159
'abort',
11551160
abortListener,
1156-
{ once: true });
1161+
{ __proto__: null, once: true, [kResistStopPropagation]: true });
11571162
}
11581163

11591164
return iterator;
Collapse file

‎lib/internal/event_target.js‎

Copy file name to clipboardExpand all lines: lib/internal/event_target.js
+26-8Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const kStop = Symbol('kStop');
5959
const kTarget = Symbol('kTarget');
6060
const kHandlers = Symbol('kHandlers');
6161
const kWeakHandler = Symbol('kWeak');
62+
const kResistStopPropagation = Symbol('kResistStopPropagation');
6263

6364
const kHybridDispatch = SymbolFor('nodejs.internal.kHybridDispatch');
6465
const kCreateEvent = Symbol('kCreateEvent');
@@ -421,6 +422,7 @@ const kFlagPassive = 1 << 2;
421422
const kFlagNodeStyle = 1 << 3;
422423
const kFlagWeak = 1 << 4;
423424
const kFlagRemoved = 1 << 5;
425+
const kFlagResistStopPropagation = 1 << 6;
424426

425427
// The listeners for an EventTarget are maintained as a linked list.
426428
// Unfortunately, the way EventTarget is defined, listeners are accounted
@@ -431,7 +433,7 @@ const kFlagRemoved = 1 << 5;
431433
// slower.
432434
class Listener {
433435
constructor(previous, listener, once, capture, passive,
434-
isNodeStyleListener, weak) {
436+
isNodeStyleListener, weak, resistStopPropagation) {
435437
this.next = undefined;
436438
if (previous !== undefined)
437439
previous.next = this;
@@ -449,6 +451,8 @@ class Listener {
449451
flags |= kFlagNodeStyle;
450452
if (weak)
451453
flags |= kFlagWeak;
454+
if (resistStopPropagation)
455+
flags |= kFlagResistStopPropagation;
452456
this.flags = flags;
453457

454458
this.removed = false;
@@ -486,6 +490,9 @@ class Listener {
486490
get weak() {
487491
return Boolean(this.flags & kFlagWeak);
488492
}
493+
get resistStopPropagation() {
494+
return Boolean(this.flags & kFlagResistStopPropagation);
495+
}
489496
get removed() {
490497
return Boolean(this.flags & kFlagRemoved);
491498
}
@@ -583,6 +590,7 @@ class EventTarget {
583590
signal,
584591
isNodeStyleListener,
585592
weak,
593+
resistStopPropagation,
586594
} = validateEventListenerOptions(options);
587595

588596
validateAbortSignal(signal, 'options.signal');
@@ -609,16 +617,16 @@ class EventTarget {
609617
// not prevent the event target from GC.
610618
signal.addEventListener('abort', () => {
611619
this.removeEventListener(type, listener, options);
612-
}, { once: true, [kWeakHandler]: this });
620+
}, { __proto__: null, once: true, [kWeakHandler]: this, [kResistStopPropagation]: true });
613621
}
614622

615623
let root = this[kEvents].get(type);
616624

617625
if (root === undefined) {
618-
root = { size: 1, next: undefined };
626+
root = { size: 1, next: undefined, resistStopPropagation: Boolean(resistStopPropagation) };
619627
// This is the first handler in our linked list.
620628
new Listener(root, listener, once, capture, passive,
621-
isNodeStyleListener, weak);
629+
isNodeStyleListener, weak, resistStopPropagation);
622630
this[kNewListener](
623631
root.size,
624632
type,
@@ -645,8 +653,9 @@ class EventTarget {
645653
}
646654

647655
new Listener(previous, listener, once, capture, passive,
648-
isNodeStyleListener, weak);
656+
isNodeStyleListener, weak, resistStopPropagation);
649657
root.size++;
658+
root.resistStopPropagation ||= Boolean(resistStopPropagation);
650659
this[kNewListener](root.size, type, listener, once, capture, passive, weak);
651660
}
652661

@@ -730,14 +739,21 @@ class EventTarget {
730739
let handler = root.next;
731740
let next;
732741

733-
while (handler !== undefined &&
734-
(handler.passive || event?.[kStop] !== true)) {
742+
const iterationCondition = () => {
743+
if (handler === undefined) {
744+
return false;
745+
}
746+
return root.resistStopPropagation || handler.passive || event?.[kStop] !== true;
747+
};
748+
while (iterationCondition()) {
735749
// Cache the next item in case this iteration removes the current one
736750
next = handler.next;
737751

738-
if (handler.removed) {
752+
if (handler.removed || (event?.[kStop] === true && !handler.resistStopPropagation)) {
739753
// Deal with the case an event is removed while event handlers are
740754
// Being processed (removeEventListener called from a listener)
755+
// And the case of event.stopImmediatePropagation() being called
756+
// For events not flagged as resistStopPropagation
741757
handler = next;
742758
continue;
743759
}
@@ -1005,6 +1021,7 @@ function validateEventListenerOptions(options) {
10051021
passive: Boolean(options.passive),
10061022
signal: options.signal,
10071023
weak: options[kWeakHandler],
1024+
resistStopPropagation: options[kResistStopPropagation] ?? false,
10081025
isNodeStyleListener: Boolean(options[kIsNodeStyleListener]),
10091026
};
10101027
}
@@ -1132,5 +1149,6 @@ module.exports = {
11321149
kRemoveListener,
11331150
kEvents,
11341151
kWeakHandler,
1152+
kResistStopPropagation,
11351153
isEventTarget,
11361154
};
Collapse file

‎test/parallel/test-events-once.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-events-once.js
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,18 @@ async function eventTargetAbortSignalBefore() {
233233
});
234234
}
235235

236+
async function eventTargetAbortSignalBeforeEvenWhenSignalPropagationStopped() {
237+
const et = new EventTarget();
238+
const ac = new AbortController();
239+
const { signal } = ac;
240+
signal.addEventListener('abort', (e) => e.stopImmediatePropagation(), { once: true });
241+
242+
process.nextTick(() => ac.abort());
243+
return rejects(once(et, 'foo', { signal }), {
244+
name: 'AbortError',
245+
});
246+
}
247+
236248
async function eventTargetAbortSignalAfter() {
237249
const et = new EventTarget();
238250
const ac = new AbortController();
@@ -270,6 +282,7 @@ Promise.all([
270282
abortSignalAfterEvent(),
271283
abortSignalRemoveListener(),
272284
eventTargetAbortSignalBefore(),
285+
eventTargetAbortSignalBeforeEvenWhenSignalPropagationStopped(),
273286
eventTargetAbortSignalAfter(),
274287
eventTargetAbortSignalAfterEvent(),
275288
]).then(common.mustCall());
Collapse file

‎test/parallel/test-eventtarget.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-eventtarget.js
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,3 +726,15 @@ let asyncTest = Promise.resolve();
726726
et.removeEventListener(Symbol('symbol'), () => {});
727727
}, TypeError);
728728
}
729+
730+
{
731+
// Test that event listeners are removed by signal even when
732+
// signal's abort event propagation stopped
733+
const controller = new AbortController();
734+
const { signal } = controller;
735+
signal.addEventListener('abort', (e) => e.stopImmediatePropagation(), { once: true });
736+
const et = new EventTarget();
737+
et.addEventListener('foo', common.mustNotCall(), { signal });
738+
controller.abort();
739+
et.dispatchEvent(new Event('foo'));
740+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.