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 db355d1

Browse filesBrowse files
atlowChemiruyadorno
authored andcommitted
lib: add option to force handling stopped events
PR-URL: #48301 Backport-PR-URL: #49587 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
1 parent 56249b0 commit db355d1
Copy full SHA for db355d1

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
@@ -59,6 +59,7 @@ const {
5959
} = require('internal/util/inspect');
6060

6161
let spliceOne;
62+
let kResistStopPropagation;
6263

6364
const {
6465
AbortError,
@@ -981,7 +982,10 @@ async function once(emitter, name, options = kEmptyObject) {
981982
}
982983
resolve(args);
983984
};
984-
eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });
985+
986+
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
987+
const opts = { __proto__: null, once: true, [kResistStopPropagation]: true };
988+
eventTargetAgnosticAddListener(emitter, name, resolver, opts);
985989
if (name !== 'error' && typeof emitter.once === 'function') {
986990
// EventTarget does not have `error` event semantics like Node
987991
// EventEmitters, we listen to `error` events only on EventEmitters.
@@ -994,7 +998,7 @@ async function once(emitter, name, options = kEmptyObject) {
994998
}
995999
if (signal != null) {
9961000
eventTargetAgnosticAddListener(
997-
signal, 'abort', abortListener, { once: true });
1001+
signal, 'abort', abortListener, { __proto__: null, once: true, [kResistStopPropagation]: true });
9981002
}
9991003
});
10001004
}
@@ -1119,11 +1123,12 @@ function on(emitter, event, options = kEmptyObject) {
11191123
}
11201124

11211125
if (signal) {
1126+
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
11221127
eventTargetAgnosticAddListener(
11231128
signal,
11241129
'abort',
11251130
abortListener,
1126-
{ once: true });
1131+
{ __proto__: null, once: true, [kResistStopPropagation]: true });
11271132
}
11281133

11291134
function abortListener() {
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');
@@ -403,6 +404,7 @@ const kFlagPassive = 1 << 2;
403404
const kFlagNodeStyle = 1 << 3;
404405
const kFlagWeak = 1 << 4;
405406
const kFlagRemoved = 1 << 5;
407+
const kFlagResistStopPropagation = 1 << 6;
406408

407409
// The listeners for an EventTarget are maintained as a linked list.
408410
// Unfortunately, the way EventTarget is defined, listeners are accounted
@@ -413,7 +415,7 @@ const kFlagRemoved = 1 << 5;
413415
// slower.
414416
class Listener {
415417
constructor(previous, listener, once, capture, passive,
416-
isNodeStyleListener, weak) {
418+
isNodeStyleListener, weak, resistStopPropagation) {
417419
this.next = undefined;
418420
if (previous !== undefined)
419421
previous.next = this;
@@ -431,6 +433,8 @@ class Listener {
431433
flags |= kFlagNodeStyle;
432434
if (weak)
433435
flags |= kFlagWeak;
436+
if (resistStopPropagation)
437+
flags |= kFlagResistStopPropagation;
434438
this.flags = flags;
435439

436440
this.removed = false;
@@ -468,6 +472,9 @@ class Listener {
468472
get weak() {
469473
return Boolean(this.flags & kFlagWeak);
470474
}
475+
get resistStopPropagation() {
476+
return Boolean(this.flags & kFlagResistStopPropagation);
477+
}
471478
get removed() {
472479
return Boolean(this.flags & kFlagRemoved);
473480
}
@@ -564,6 +571,7 @@ class EventTarget {
564571
signal,
565572
isNodeStyleListener,
566573
weak,
574+
resistStopPropagation,
567575
} = validateEventListenerOptions(options);
568576

569577
if (!validateEventListener(listener)) {
@@ -588,16 +596,16 @@ class EventTarget {
588596
// not prevent the event target from GC.
589597
signal.addEventListener('abort', () => {
590598
this.removeEventListener(type, listener, options);
591-
}, { once: true, [kWeakHandler]: this });
599+
}, { __proto__: null, once: true, [kWeakHandler]: this, [kResistStopPropagation]: true });
592600
}
593601

594602
let root = this[kEvents].get(type);
595603

596604
if (root === undefined) {
597-
root = { size: 1, next: undefined };
605+
root = { size: 1, next: undefined, resistStopPropagation: Boolean(resistStopPropagation) };
598606
// This is the first handler in our linked list.
599607
new Listener(root, listener, once, capture, passive,
600-
isNodeStyleListener, weak);
608+
isNodeStyleListener, weak, resistStopPropagation);
601609
this[kNewListener](
602610
root.size,
603611
type,
@@ -624,8 +632,9 @@ class EventTarget {
624632
}
625633

626634
new Listener(previous, listener, once, capture, passive,
627-
isNodeStyleListener, weak);
635+
isNodeStyleListener, weak, resistStopPropagation);
628636
root.size++;
637+
root.resistStopPropagation ||= Boolean(resistStopPropagation);
629638
this[kNewListener](root.size, type, listener, once, capture, passive, weak);
630639
}
631640

@@ -709,14 +718,21 @@ class EventTarget {
709718
let handler = root.next;
710719
let next;
711720

712-
while (handler !== undefined &&
713-
(handler.passive || event?.[kStop] !== true)) {
721+
const iterationCondition = () => {
722+
if (handler === undefined) {
723+
return false;
724+
}
725+
return root.resistStopPropagation || handler.passive || event?.[kStop] !== true;
726+
};
727+
while (iterationCondition()) {
714728
// Cache the next item in case this iteration removes the current one
715729
next = handler.next;
716730

717-
if (handler.removed) {
731+
if (handler.removed || (event?.[kStop] === true && !handler.resistStopPropagation)) {
718732
// Deal with the case an event is removed while event handlers are
719733
// Being processed (removeEventListener called from a listener)
734+
// And the case of event.stopImmediatePropagation() being called
735+
// For events not flagged as resistStopPropagation
720736
handler = next;
721737
continue;
722738
}
@@ -984,6 +1000,7 @@ function validateEventListenerOptions(options) {
9841000
passive: Boolean(options.passive),
9851001
signal: options.signal,
9861002
weak: options[kWeakHandler],
1003+
resistStopPropagation: options[kResistStopPropagation] ?? false,
9871004
isNodeStyleListener: Boolean(options[kIsNodeStyleListener]),
9881005
};
9891006
}
@@ -1099,5 +1116,6 @@ module.exports = {
10991116
kRemoveListener,
11001117
kEvents,
11011118
kWeakHandler,
1119+
kResistStopPropagation,
11021120
isEventTarget,
11031121
};
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
@@ -717,3 +717,15 @@ let asyncTest = Promise.resolve();
717717
et.removeEventListener(Symbol('symbol'), () => {});
718718
}, TypeError);
719719
}
720+
721+
{
722+
// Test that event listeners are removed by signal even when
723+
// signal's abort event propagation stopped
724+
const controller = new AbortController();
725+
const { signal } = controller;
726+
signal.addEventListener('abort', (e) => e.stopImmediatePropagation(), { once: true });
727+
const et = new EventTarget();
728+
et.addEventListener('foo', common.mustNotCall(), { signal });
729+
controller.abort();
730+
et.dispatchEvent(new Event('foo'));
731+
}

0 commit comments

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