@@ -45,6 +45,7 @@ const kEvents = Symbol('kEvents');
45
45
const kStop = Symbol ( 'kStop' ) ;
46
46
const kTarget = Symbol ( 'kTarget' ) ;
47
47
const kHandlers = Symbol ( 'khandlers' ) ;
48
+ const kWeakHandler = Symbol ( 'kWeak' ) ;
48
49
49
50
const kHybridDispatch = SymbolFor ( 'nodejs.internal.kHybridDispatch' ) ;
50
51
const kCreateEvent = Symbol ( 'kCreateEvent' ) ;
@@ -188,6 +189,19 @@ class NodeCustomEvent extends Event {
188
189
}
189
190
}
190
191
}
192
+
193
+ // Weak listener cleanup
194
+ // This has to be lazy for snapshots to work
195
+ let weakListenersState = null ;
196
+ function weakListeners ( ) {
197
+ if ( ! weakListenersState ) {
198
+ weakListenersState = new FinalizationRegistry (
199
+ ( listener ) => listener . remove ( )
200
+ ) ;
201
+ }
202
+ return weakListenersState ;
203
+ }
204
+
191
205
// The listeners for an EventTarget are maintained as a linked list.
192
206
// Unfortunately, the way EventTarget is defined, listeners are accounted
193
207
// using the tuple [handler,capture], and even if we don't actually make
@@ -196,27 +210,36 @@ class NodeCustomEvent extends Event {
196
210
// the linked list makes dispatching faster, even if adding/removing is
197
211
// slower.
198
212
class Listener {
199
- constructor ( previous , listener , once , capture , passive , isNodeStyleListener ) {
213
+ constructor ( previous , listener , once , capture , passive ,
214
+ isNodeStyleListener , weak ) {
200
215
this . next = undefined ;
201
216
if ( previous !== undefined )
202
217
previous . next = this ;
203
218
this . previous = previous ;
204
- this . listener = listener ;
205
219
// TODO(benjamingr) these 4 can be 'flags' to save 3 slots
206
220
this . once = once ;
207
221
this . capture = capture ;
208
222
this . passive = passive ;
209
223
this . isNodeStyleListener = isNodeStyleListener ;
210
224
this . removed = false ;
211
-
212
- this . callback =
213
- typeof listener === 'function' ?
214
- listener :
215
- listener . handleEvent . bind ( listener ) ;
225
+ this . weak = weak ;
226
+
227
+ if ( this . weak ) {
228
+ this . callback = new WeakRef ( listener ) ;
229
+ weakListeners ( ) . register ( listener , this , this ) ;
230
+ this . listener = this . callback ;
231
+ } else if ( typeof listener === 'function' ) {
232
+ this . callback = listener ;
233
+ this . listener = listener ;
234
+ } else {
235
+ this . callback = listener . handleEvent . bind ( listener ) ;
236
+ this . listener = listener ;
237
+ }
216
238
}
217
239
218
240
same ( listener , capture ) {
219
- return this . listener === listener && this . capture === capture ;
241
+ const myListener = this . weak ? this . listener . deref ( ) : this . listener ;
242
+ return myListener === listener && this . capture === capture ;
220
243
}
221
244
222
245
remove ( ) {
@@ -225,6 +248,8 @@ class Listener {
225
248
if ( this . next !== undefined )
226
249
this . next . previous = this . previous ;
227
250
this . removed = true ;
251
+ if ( this . weak )
252
+ weakListeners ( ) . unregister ( this ) ;
228
253
}
229
254
}
230
255
@@ -275,7 +300,8 @@ class EventTarget {
275
300
capture,
276
301
passive,
277
302
signal,
278
- isNodeStyleListener
303
+ isNodeStyleListener,
304
+ weak,
279
305
} = validateEventListenerOptions ( options ) ;
280
306
281
307
if ( ! shouldAddListener ( listener ) ) {
@@ -296,19 +322,18 @@ class EventTarget {
296
322
if ( signal . aborted ) {
297
323
return false ;
298
324
}
299
- // TODO(benjamingr) make this weak somehow? ideally the signal would
300
- // not prevent the event target from GC.
301
325
signal . addEventListener ( 'abort' , ( ) => {
302
326
this . removeEventListener ( type , listener , options ) ;
303
- } , { once : true } ) ;
327
+ } , { once : true , [ kWeakHandler ] : true } ) ;
304
328
}
305
329
306
330
let root = this [ kEvents ] . get ( type ) ;
307
331
308
332
if ( root === undefined ) {
309
333
root = { size : 1 , next : undefined } ;
310
334
// This is the first handler in our linked list.
311
- new Listener ( root , listener , once , capture , passive , isNodeStyleListener ) ;
335
+ new Listener ( root , listener , once , capture , passive ,
336
+ isNodeStyleListener , weak ) ;
312
337
this [ kNewListener ] ( root . size , type , listener , once , capture , passive ) ;
313
338
this [ kEvents ] . set ( type , root ) ;
314
339
return ;
@@ -328,7 +353,7 @@ class EventTarget {
328
353
}
329
354
330
355
new Listener ( previous , listener , once , capture , passive ,
331
- isNodeStyleListener ) ;
356
+ isNodeStyleListener , weak ) ;
332
357
root . size ++ ;
333
358
this [ kNewListener ] ( root . size , type , listener , once , capture , passive ) ;
334
359
}
@@ -419,7 +444,9 @@ class EventTarget {
419
444
} else {
420
445
arg = createEvent ( ) ;
421
446
}
422
- const result = handler . callback . call ( this , arg ) ;
447
+ const callback = handler . weak ?
448
+ handler . callback . deref ( ) : handler . callback ;
449
+ const result = callback ?. call ( this , arg ) ;
423
450
if ( result !== undefined && result !== null )
424
451
addCatch ( this , result , createEvent ( ) ) ;
425
452
} catch ( err ) {
@@ -573,6 +600,7 @@ function validateEventListenerOptions(options) {
573
600
once : Boolean ( options . once ) ,
574
601
capture : Boolean ( options . capture ) ,
575
602
passive : Boolean ( options . passive ) ,
603
+ weak : Boolean ( options [ kWeakHandler ] ) ,
576
604
signal : options . signal ,
577
605
isNodeStyleListener : Boolean ( options [ kIsNodeStyleListener ] )
578
606
} ;
@@ -675,5 +703,6 @@ module.exports = {
675
703
kTrustEvent,
676
704
kRemoveListener,
677
705
kEvents,
706
+ kWeakHandler,
678
707
isEventTarget,
679
708
} ;
0 commit comments