@@ -15,11 +15,12 @@ const {
15
15
ReflectApply,
16
16
SafeArrayIterator,
17
17
SafeMap,
18
+ SafeWeakMap,
19
+ SafeWeakSet,
18
20
String,
19
21
Symbol,
20
22
SymbolFor,
21
23
SymbolToStringTag,
22
- SafeWeakSet,
23
24
} = primordials ;
24
25
25
26
const {
@@ -47,6 +48,7 @@ const kEvents = Symbol('kEvents');
47
48
const kStop = Symbol ( 'kStop' ) ;
48
49
const kTarget = Symbol ( 'kTarget' ) ;
49
50
const kHandlers = Symbol ( 'khandlers' ) ;
51
+ const kWeakHandler = Symbol ( 'kWeak' ) ;
50
52
51
53
const kHybridDispatch = SymbolFor ( 'nodejs.internal.kHybridDispatch' ) ;
52
54
const kCreateEvent = Symbol ( 'kCreateEvent' ) ;
@@ -190,6 +192,21 @@ class NodeCustomEvent extends Event {
190
192
}
191
193
}
192
194
}
195
+
196
+ // Weak listener cleanup
197
+ // This has to be lazy for snapshots to work
198
+ let weakListenersState = null ;
199
+ // The resource needs to retain the callback so that it doesn't
200
+ // get garbage collected now that it's weak.
201
+ let objectToWeakListenerMap = null ;
202
+ function weakListeners ( ) {
203
+ weakListenersState ??= new globalThis . FinalizationRegistry (
204
+ ( listener ) => listener . remove ( )
205
+ ) ;
206
+ objectToWeakListenerMap ??= new SafeWeakMap ( ) ;
207
+ return { registry : weakListenersState , map : objectToWeakListenerMap } ;
208
+ }
209
+
193
210
// The listeners for an EventTarget are maintained as a linked list.
194
211
// Unfortunately, the way EventTarget is defined, listeners are accounted
195
212
// using the tuple [handler,capture], and even if we don't actually make
@@ -198,7 +215,8 @@ class NodeCustomEvent extends Event {
198
215
// the linked list makes dispatching faster, even if adding/removing is
199
216
// slower.
200
217
class Listener {
201
- constructor ( previous , listener , once , capture , passive , isNodeStyleListener ) {
218
+ constructor ( previous , listener , once , capture , passive ,
219
+ isNodeStyleListener , weak ) {
202
220
this . next = undefined ;
203
221
if ( previous !== undefined )
204
222
previous . next = this ;
@@ -210,15 +228,26 @@ class Listener {
210
228
this . passive = passive ;
211
229
this . isNodeStyleListener = isNodeStyleListener ;
212
230
this . removed = false ;
213
-
214
- this . callback =
215
- typeof listener === 'function' ?
216
- listener :
217
- FunctionPrototypeBind ( listener . handleEvent , listener ) ;
231
+ this . weak = Boolean ( weak ) ; // Don't retain the object
232
+
233
+ if ( this . weak ) {
234
+ this . callback = new globalThis . WeakRef ( listener ) ;
235
+ weakListeners ( ) . registry . register ( listener , this , this ) ;
236
+ // Make the retainer retain the listener in a WeakMap
237
+ weakListeners ( ) . map . set ( weak , listener ) ;
238
+ this . listener = this . callback ;
239
+ } else if ( typeof listener === 'function' ) {
240
+ this . callback = listener ;
241
+ this . listener = listener ;
242
+ } else {
243
+ this . callback = FunctionPrototypeBind ( listener . handleEvent , listener ) ;
244
+ this . listener = listener ;
245
+ }
218
246
}
219
247
220
248
same ( listener , capture ) {
221
- return this . listener === listener && this . capture === capture ;
249
+ const myListener = this . weak ? this . listener . deref ( ) : this . listener ;
250
+ return myListener === listener && this . capture === capture ;
222
251
}
223
252
224
253
remove ( ) {
@@ -227,6 +256,8 @@ class Listener {
227
256
if ( this . next !== undefined )
228
257
this . next . previous = this . previous ;
229
258
this . removed = true ;
259
+ if ( this . weak )
260
+ weakListeners ( ) . registry . unregister ( this ) ;
230
261
}
231
262
}
232
263
@@ -277,7 +308,8 @@ class EventTarget {
277
308
capture,
278
309
passive,
279
310
signal,
280
- isNodeStyleListener
311
+ isNodeStyleListener,
312
+ weak,
281
313
} = validateEventListenerOptions ( options ) ;
282
314
283
315
if ( ! shouldAddListener ( listener ) ) {
@@ -302,15 +334,16 @@ class EventTarget {
302
334
// not prevent the event target from GC.
303
335
signal . addEventListener ( 'abort' , ( ) => {
304
336
this . removeEventListener ( type , listener , options ) ;
305
- } , { once : true } ) ;
337
+ } , { once : true , [ kWeakHandler ] : this } ) ;
306
338
}
307
339
308
340
let root = this [ kEvents ] . get ( type ) ;
309
341
310
342
if ( root === undefined ) {
311
343
root = { size : 1 , next : undefined } ;
312
344
// This is the first handler in our linked list.
313
- new Listener ( root , listener , once , capture , passive , isNodeStyleListener ) ;
345
+ new Listener ( root , listener , once , capture , passive ,
346
+ isNodeStyleListener , weak ) ;
314
347
this [ kNewListener ] ( root . size , type , listener , once , capture , passive ) ;
315
348
this [ kEvents ] . set ( type , root ) ;
316
349
return ;
@@ -330,7 +363,7 @@ class EventTarget {
330
363
}
331
364
332
365
new Listener ( previous , listener , once , capture , passive ,
333
- isNodeStyleListener ) ;
366
+ isNodeStyleListener , weak ) ;
334
367
root . size ++ ;
335
368
this [ kNewListener ] ( root . size , type , listener , once , capture , passive ) ;
336
369
}
@@ -418,7 +451,12 @@ class EventTarget {
418
451
} else {
419
452
arg = createEvent ( ) ;
420
453
}
421
- const result = FunctionPrototypeCall ( handler . callback , this , arg ) ;
454
+ const callback = handler . weak ?
455
+ handler . callback . deref ( ) : handler . callback ;
456
+ let result ;
457
+ if ( callback ) {
458
+ result = FunctionPrototypeCall ( callback , this , arg ) ;
459
+ }
422
460
if ( result !== undefined && result !== null )
423
461
addCatch ( this , result , createEvent ( ) ) ;
424
462
} catch ( err ) {
@@ -569,6 +607,7 @@ function validateEventListenerOptions(options) {
569
607
capture : Boolean ( options . capture ) ,
570
608
passive : Boolean ( options . passive ) ,
571
609
signal : options . signal ,
610
+ weak : options [ kWeakHandler ] ,
572
611
isNodeStyleListener : Boolean ( options [ kIsNodeStyleListener ] )
573
612
} ;
574
613
}
@@ -671,5 +710,6 @@ module.exports = {
671
710
kTrustEvent,
672
711
kRemoveListener,
673
712
kEvents,
713
+ kWeakHandler,
674
714
isEventTarget,
675
715
} ;
0 commit comments