@@ -43,7 +43,24 @@ export interface PatchEventTargetOptions {
4343}
4444
4545export function patchEventTarget (
46- api : _ZonePrivate , _global : any , apis : any [ ] , patchOptions ?: PatchEventTargetOptions ) {
46+ _global : any , apis : any [ ] , patchOptions ?: PatchEventTargetOptions ) {
47+ const ADD_EVENT_LISTENER =
48+ ( patchOptions && patchOptions . addEventListenerFnName ) || 'addEventListener' ;
49+ const REMOVE_EVENT_LISTENER =
50+ ( patchOptions && patchOptions . removeEventListenerFnName ) || 'removeEventListener' ;
51+
52+ const LISTENERS_EVENT_LISTENER =
53+ ( patchOptions && patchOptions . listenersFnName ) || 'eventListeners' ;
54+ const REMOVE_ALL_LISTENERS_EVENT_LISTENER =
55+ ( patchOptions && patchOptions . removeAllFnName ) || 'removeAllListeners' ;
56+
57+ const zoneSymbolAddEventListener = zoneSymbol ( ADD_EVENT_LISTENER ) ;
58+
59+ const ADD_EVENT_LISTENER_SOURCE = '.' + ADD_EVENT_LISTENER + ':' ;
60+
61+ const PREPEND_EVENT_LISTENER = 'prependListener' ;
62+ const PREPEND_EVENT_LISTENER_SOURCE = '.' + PREPEND_EVENT_LISTENER + ':' ;
63+
4764 const invokeTask = function ( task : any , target : any , event : Event ) {
4865 // for better performance, check isRemoved which is set
4966 // by removeEventListener
@@ -52,12 +69,20 @@ export function patchEventTarget(
5269 }
5370 const delegate = task . callback ;
5471 if ( typeof delegate === OBJECT_TYPE && delegate . handleEvent ) {
55- // create the bind version of handleEvnet when invoke
72+ // create the bind version of handleEvent when invoke
5673 task . callback = ( event : Event ) => delegate . handleEvent ( event ) ;
5774 task . originalDelegate = delegate ;
5875 }
5976 // invoke static task.invoke
6077 task . invoke ( task , target , [ event ] ) ;
78+ const options = task . options ;
79+ if ( options && typeof options === 'object' && options . once ) {
80+ // if options.once is true, after invoke once remove listener here
81+ // only browser need to do this, nodejs eventEmitter will cal removeListener
82+ // inside EventEmitter.once
83+ const delegate = task . originalDelegate ? task . originalDelegate : task . callback ;
84+ target [ REMOVE_EVENT_LISTENER ] . apply ( target , [ event . type , delegate , options ] ) ;
85+ }
6186 } ;
6287
6388 // global shared zoneAwareCallback to handle all event callback with capture = false
@@ -67,6 +92,7 @@ export function patchEventTarget(
6792 const tasks = target [ zoneSymbolEventNames [ event . type ] [ FALSE_STR ] ] ;
6893 if ( tasks ) {
6994 // invoke all tasks which attached to current target with given event.type and capture = false
95+ // for performance concern, if task.length === 1, just invoke
7096 if ( tasks . length === 1 ) {
7197 invokeTask ( tasks [ 0 ] , target , event ) ;
7298 } else {
@@ -88,6 +114,7 @@ export function patchEventTarget(
88114 const tasks = target [ zoneSymbolEventNames [ event . type ] [ TRUE_STR ] ] ;
89115 if ( tasks ) {
90116 // invoke all tasks which attached to current target with given event.type and capture = false
117+ // for performance concern, if task.length === 1, just invoke
91118 if ( tasks . length === 1 ) {
92119 invokeTask ( tasks [ 0 ] , target , event ) ;
93120 } else {
@@ -106,22 +133,6 @@ export function patchEventTarget(
106133 if ( ! obj ) {
107134 return false ;
108135 }
109- const ADD_EVENT_LISTENER =
110- ( patchOptions && patchOptions . addEventListenerFnName ) || 'addEventListener' ;
111- const REMOVE_EVENT_LISTENER =
112- ( patchOptions && patchOptions . removeEventListenerFnName ) || 'removeEventListener' ;
113-
114- const LISTENERS_EVENT_LISTENER =
115- ( patchOptions && patchOptions . listenersFnName ) || 'eventListeners' ;
116- const REMOVE_ALL_LISTENERS_EVENT_LISTENER =
117- ( patchOptions && patchOptions . removeAllFnName ) || 'removeAllListeners' ;
118-
119- const zoneSymbolAddEventListener = zoneSymbol ( ADD_EVENT_LISTENER ) ;
120-
121- const ADD_EVENT_LISTENER_SOURCE = '.' + ADD_EVENT_LISTENER + ':' ;
122-
123- const PREPEND_EVENT_LISTENER = 'prependListener' ;
124- const PREPEND_EVENT_LISTENER_SOURCE = '.' + PREPEND_EVENT_LISTENER + ':' ;
125136
126137 let useGlobalCallback = true ;
127138 if ( patchOptions && patchOptions . useGlobalCallback !== undefined ) {
@@ -188,6 +199,34 @@ export function patchEventTarget(
188199 } ;
189200
190201 const customCancelGlobal = function ( task : any ) {
202+ // if task is not marked as isRemoved, this call is directly
203+ // from Zone.prototype.cancelTask, we should remove the task
204+ // from tasksList of target first
205+ if ( ! task . isRemoved ) {
206+ const symbolEventNames = zoneSymbolEventNames [ task . eventName ] ;
207+ let symbolEventName ;
208+ if ( symbolEventNames ) {
209+ symbolEventName = symbolEventNames [ task . capture ? TRUE_STR : FALSE_STR ] ;
210+ }
211+ const existingTasks = symbolEventName && task . target [ symbolEventName ] ;
212+ if ( existingTasks ) {
213+ for ( let i = 0 ; i < existingTasks . length ; i ++ ) {
214+ const existingTask = existingTasks [ i ] ;
215+ if ( existingTask === task ) {
216+ existingTasks . splice ( i , 1 ) ;
217+ // set isRemoved to data for faster invokeTask check
218+ task . isRemoved = true ;
219+ if ( existingTasks . length === 0 ) {
220+ // all tasks for the eventName + capture have gone,
221+ // remove globalZoneAwareCallback and remove the task cache from target
222+ task . allRemoved = true ;
223+ task . target [ symbolEventName ] = null ;
224+ }
225+ break ;
226+ }
227+ }
228+ }
229+ }
191230 // if all tasks for the eventName + capture have gone,
192231 // we will really remove the global event callback,
193232 // if not, return
@@ -262,6 +301,7 @@ export function patchEventTarget(
262301 const options = arguments [ 2 ] ;
263302
264303 let capture ;
304+ let once = false ;
265305 if ( options === undefined ) {
266306 capture = false ;
267307 } else if ( options === true ) {
@@ -270,6 +310,7 @@ export function patchEventTarget(
270310 capture = false ;
271311 } else {
272312 capture = options ? ! ! options . capture : false ;
313+ once = options ? ! ! options . once : false ;
273314 }
274315
275316 const zone = Zone . current ;
@@ -316,6 +357,12 @@ export function patchEventTarget(
316357 // do not create a new object as task.data to pass those things
317358 // just use the global shared one
318359 taskData . options = options ;
360+ if ( once ) {
361+ // if addEventListener with once options, we don't pass it to
362+ // native addEventListener, instead we keep the once setting
363+ // and handle ourselves.
364+ taskData . options . once = false ;
365+ }
319366 taskData . target = target ;
320367 taskData . capture = capture ;
321368 taskData . eventName = eventName ;
@@ -327,6 +374,9 @@ export function patchEventTarget(
327374
328375 // have to save those information to task in case
329376 // application may call task.zone.cancelTask() directly
377+ if ( once ) {
378+ options . once = true ;
379+ }
330380 task . options = options ;
331381 task . target = target ;
332382 task . capture = capture ;
@@ -434,10 +484,15 @@ export function patchEventTarget(
434484 const prop = keys [ i ] ;
435485 const match = EVENT_NAME_SYMBOL_REGX . exec ( prop ) ;
436486 let evtName = match && match [ 1 ] ;
487+ // in nodejs EventEmitter, removeListener event is
488+ // used for monitoring the removeListener call,
489+ // so just keep removeListener eventListener until
490+ // all other eventListeners are removed
437491 if ( evtName && evtName !== 'removeListener' ) {
438492 this [ REMOVE_ALL_LISTENERS_EVENT_LISTENER ] . apply ( this , [ evtName ] ) ;
439493 }
440494 }
495+ // remove removeListener listener finally
441496 this [ REMOVE_ALL_LISTENERS_EVENT_LISTENER ] . apply ( this , [ 'removeListener' ] ) ;
442497 } else {
443498 const symbolEventNames = zoneSymbolEventNames [ eventName ] ;
@@ -486,7 +541,6 @@ export function patchEventTarget(
486541 results [ i ] = patchEventTargetMethods ( apis [ i ] , patchOptions ) ;
487542 }
488543
489- api . patchEventTargetMethods = patchEventTargetMethods ;
490544 return results ;
491545}
492546
0 commit comments