Skip to content

Commit d75ff43

Browse files
committed
lib: add option to force handling stopped events
1 parent d4e99b1 commit d75ff43

File tree

4 files changed

+59
-11
lines changed

4 files changed

+59
-11
lines changed

‎lib/events.js‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const{
6161
letspliceOne;
6262
letFixedQueue;
6363
letkFirstEventParam;
64+
letkResistStopPropagation;
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+
constopts={__proto__: null,once: true,[kResistStopPropagation]: true};
985+
eventTargetAgnosticAddListener(emitter,name,resolver,opts);
982986
if(name!=='error'&&typeofemitter.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
returniterator;

‎lib/internal/event_target.js‎

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const kStop = Symbol('kStop');
5959
constkTarget=Symbol('kTarget');
6060
constkHandlers=Symbol('kHandlers');
6161
constkWeakHandler=Symbol('kWeak');
62+
constkResistStopPropagation=Symbol('kResistStopPropagation');
6263

6364
constkHybridDispatch=SymbolFor('nodejs.internal.kHybridDispatch');
6465
constkCreateEvent=Symbol('kCreateEvent');
@@ -421,6 +422,7 @@ const kFlagPassive = 1 << 2;
421422
constkFlagNodeStyle=1<<3;
422423
constkFlagWeak=1<<4;
423424
constkFlagRemoved=1<<5;
425+
constkFlagResistStopPropagation=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
classListener{
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
getweak(){
487491
returnBoolean(this.flags&kFlagWeak);
488492
}
493+
getresistStopPropagation(){
494+
returnBoolean(this.flags&kFlagResistStopPropagation);
495+
}
489496
getremoved(){
490497
returnBoolean(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
letroot=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
newListener(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
newListener(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
lethandler=root.next;
731740
letnext;
732741

733-
while(handler!==undefined&&
734-
(handler.passive||event?.[kStop]!==true)){
742+
constiterationCondition=()=>{
743+
if(root.resistStopPropagation){
744+
returnhandler!==undefined;
745+
}
746+
returnhandler!==undefined&&(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
};

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,18 @@ async function eventTargetAbortSignalBefore(){
233233
});
234234
}
235235

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

‎test/parallel/test-eventtarget.js‎

Lines 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+
constcontroller=newAbortController();
734+
const{ signal }=controller;
735+
signal.addEventListener('abort',(e)=>e.stopImmediatePropagation(),{once: true});
736+
constet=newEventTarget();
737+
et.addEventListener('foo',common.mustNotCall(),{ signal });
738+
controller.abort();
739+
et.dispatchEvent(newEvent('foo'));
740+
}

0 commit comments

Comments
(0)