@@ -15,11 +15,12 @@ const {
1515 ReflectApply,
1616 SafeArrayIterator,
1717 SafeMap,
18+ SafeWeakMap,
19+ SafeWeakSet,
1820 String,
1921 Symbol,
2022 SymbolFor,
2123 SymbolToStringTag,
22- SafeWeakSet,
2324} = primordials ;
2425
2526const {
@@ -47,6 +48,7 @@ const kEvents = Symbol('kEvents');
4748const kStop = Symbol ( 'kStop' ) ;
4849const kTarget = Symbol ( 'kTarget' ) ;
4950const kHandlers = Symbol ( 'khandlers' ) ;
51+ const kWeakHandler = Symbol ( 'kWeak' ) ;
5052
5153const kHybridDispatch = SymbolFor ( 'nodejs.internal.kHybridDispatch' ) ;
5254const kCreateEvent = Symbol ( 'kCreateEvent' ) ;
@@ -190,6 +192,21 @@ class NodeCustomEvent extends Event {
190192 }
191193 }
192194}
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+
193210// The listeners for an EventTarget are maintained as a linked list.
194211// Unfortunately, the way EventTarget is defined, listeners are accounted
195212// using the tuple [handler,capture], and even if we don't actually make
@@ -198,7 +215,8 @@ class NodeCustomEvent extends Event {
198215// the linked list makes dispatching faster, even if adding/removing is
199216// slower.
200217class Listener {
201- constructor ( previous , listener , once , capture , passive , isNodeStyleListener ) {
218+ constructor ( previous , listener , once , capture , passive ,
219+ isNodeStyleListener , weak ) {
202220 this . next = undefined ;
203221 if ( previous !== undefined )
204222 previous . next = this ;
@@ -210,15 +228,26 @@ class Listener {
210228 this . passive = passive ;
211229 this . isNodeStyleListener = isNodeStyleListener ;
212230 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+ }
218246 }
219247
220248 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 ;
222251 }
223252
224253 remove ( ) {
@@ -227,6 +256,8 @@ class Listener {
227256 if ( this . next !== undefined )
228257 this . next . previous = this . previous ;
229258 this . removed = true ;
259+ if ( this . weak )
260+ weakListeners ( ) . registry . unregister ( this ) ;
230261 }
231262}
232263
@@ -277,7 +308,8 @@ class EventTarget {
277308 capture,
278309 passive,
279310 signal,
280- isNodeStyleListener
311+ isNodeStyleListener,
312+ weak,
281313 } = validateEventListenerOptions ( options ) ;
282314
283315 if ( ! shouldAddListener ( listener ) ) {
@@ -302,15 +334,16 @@ class EventTarget {
302334 // not prevent the event target from GC.
303335 signal . addEventListener ( 'abort' , ( ) => {
304336 this . removeEventListener ( type , listener , options ) ;
305- } , { once : true } ) ;
337+ } , { once : true , [ kWeakHandler ] : this } ) ;
306338 }
307339
308340 let root = this [ kEvents ] . get ( type ) ;
309341
310342 if ( root === undefined ) {
311343 root = { size : 1 , next : undefined } ;
312344 // 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 ) ;
314347 this [ kNewListener ] ( root . size , type , listener , once , capture , passive ) ;
315348 this [ kEvents ] . set ( type , root ) ;
316349 return ;
@@ -330,7 +363,7 @@ class EventTarget {
330363 }
331364
332365 new Listener ( previous , listener , once , capture , passive ,
333- isNodeStyleListener ) ;
366+ isNodeStyleListener , weak ) ;
334367 root . size ++ ;
335368 this [ kNewListener ] ( root . size , type , listener , once , capture , passive ) ;
336369 }
@@ -418,7 +451,12 @@ class EventTarget {
418451 } else {
419452 arg = createEvent ( ) ;
420453 }
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+ }
422460 if ( result !== undefined && result !== null )
423461 addCatch ( this , result , createEvent ( ) ) ;
424462 } catch ( err ) {
@@ -569,6 +607,7 @@ function validateEventListenerOptions(options) {
569607 capture : Boolean ( options . capture ) ,
570608 passive : Boolean ( options . passive ) ,
571609 signal : options . signal ,
610+ weak : options [ kWeakHandler ] ,
572611 isNodeStyleListener : Boolean ( options [ kIsNodeStyleListener ] )
573612 } ;
574613}
@@ -671,5 +710,6 @@ module.exports = {
671710 kTrustEvent,
672711 kRemoveListener,
673712 kEvents,
713+ kWeakHandler,
674714 isEventTarget,
675715} ;
0 commit comments