@@ -43,7 +43,24 @@ export interface PatchEventTargetOptions {
43
43
}
44
44
45
45
export 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
+
47
64
const invokeTask = function ( task : any , target : any , event : Event ) {
48
65
// for better performance, check isRemoved which is set
49
66
// by removeEventListener
@@ -52,12 +69,20 @@ export function patchEventTarget(
52
69
}
53
70
const delegate = task . callback ;
54
71
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
56
73
task . callback = ( event : Event ) => delegate . handleEvent ( event ) ;
57
74
task . originalDelegate = delegate ;
58
75
}
59
76
// invoke static task.invoke
60
77
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
+ }
61
86
} ;
62
87
63
88
// global shared zoneAwareCallback to handle all event callback with capture = false
@@ -67,6 +92,7 @@ export function patchEventTarget(
67
92
const tasks = target [ zoneSymbolEventNames [ event . type ] [ FALSE_STR ] ] ;
68
93
if ( tasks ) {
69
94
// 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
70
96
if ( tasks . length === 1 ) {
71
97
invokeTask ( tasks [ 0 ] , target , event ) ;
72
98
} else {
@@ -88,6 +114,7 @@ export function patchEventTarget(
88
114
const tasks = target [ zoneSymbolEventNames [ event . type ] [ TRUE_STR ] ] ;
89
115
if ( tasks ) {
90
116
// 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
91
118
if ( tasks . length === 1 ) {
92
119
invokeTask ( tasks [ 0 ] , target , event ) ;
93
120
} else {
@@ -106,22 +133,6 @@ export function patchEventTarget(
106
133
if ( ! obj ) {
107
134
return false ;
108
135
}
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 + ':' ;
125
136
126
137
let useGlobalCallback = true ;
127
138
if ( patchOptions && patchOptions . useGlobalCallback !== undefined ) {
@@ -188,6 +199,34 @@ export function patchEventTarget(
188
199
} ;
189
200
190
201
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
+ }
191
230
// if all tasks for the eventName + capture have gone,
192
231
// we will really remove the global event callback,
193
232
// if not, return
@@ -262,6 +301,7 @@ export function patchEventTarget(
262
301
const options = arguments [ 2 ] ;
263
302
264
303
let capture ;
304
+ let once = false ;
265
305
if ( options === undefined ) {
266
306
capture = false ;
267
307
} else if ( options === true ) {
@@ -270,6 +310,7 @@ export function patchEventTarget(
270
310
capture = false ;
271
311
} else {
272
312
capture = options ? ! ! options . capture : false ;
313
+ once = options ? ! ! options . once : false ;
273
314
}
274
315
275
316
const zone = Zone . current ;
@@ -316,6 +357,12 @@ export function patchEventTarget(
316
357
// do not create a new object as task.data to pass those things
317
358
// just use the global shared one
318
359
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
+ }
319
366
taskData . target = target ;
320
367
taskData . capture = capture ;
321
368
taskData . eventName = eventName ;
@@ -327,6 +374,9 @@ export function patchEventTarget(
327
374
328
375
// have to save those information to task in case
329
376
// application may call task.zone.cancelTask() directly
377
+ if ( once ) {
378
+ options . once = true ;
379
+ }
330
380
task . options = options ;
331
381
task . target = target ;
332
382
task . capture = capture ;
@@ -434,10 +484,15 @@ export function patchEventTarget(
434
484
const prop = keys [ i ] ;
435
485
const match = EVENT_NAME_SYMBOL_REGX . exec ( prop ) ;
436
486
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
437
491
if ( evtName && evtName !== 'removeListener' ) {
438
492
this [ REMOVE_ALL_LISTENERS_EVENT_LISTENER ] . apply ( this , [ evtName ] ) ;
439
493
}
440
494
}
495
+ // remove removeListener listener finally
441
496
this [ REMOVE_ALL_LISTENERS_EVENT_LISTENER ] . apply ( this , [ 'removeListener' ] ) ;
442
497
} else {
443
498
const symbolEventNames = zoneSymbolEventNames [ eventName ] ;
@@ -486,7 +541,6 @@ export function patchEventTarget(
486
541
results [ i ] = patchEventTargetMethods ( apis [ i ] , patchOptions ) ;
487
542
}
488
543
489
- api . patchEventTargetMethods = patchEventTargetMethods ;
490
544
return results ;
491
545
}
492
546
0 commit comments