Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.

Commit a89830d

Browse files
JiaLiPassionmhevery
authored andcommitted
feat(EventListenerOptions): fix #737, add support to EventListenerOptions (#738)
1 parent e9f68be commit a89830d

File tree

3 files changed

+326
-27
lines changed

3 files changed

+326
-27
lines changed

Diff for: lib/common/utils.ts

+45-26
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,18 @@ export interface NestedEventListener { listener?: EventListenerOrEventListenerOb
181181
export declare type NestedEventListenerOrEventListenerObject =
182182
NestedEventListener | EventListener | EventListenerObject;
183183

184+
export interface EventListenerOptions { capture?: boolean; }
185+
186+
export interface AddEventListenerOptions extends EventListenerOptions {
187+
passive?: boolean;
188+
once?: boolean;
189+
}
190+
191+
export declare type EventListenerOptionsOrCapture =
192+
EventListenerOptions | AddEventListenerOptions | boolean;
193+
184194
export interface ListenerTaskMeta extends TaskData {
185-
useCapturing: boolean;
195+
options: EventListenerOptionsOrCapture;
186196
eventName: string;
187197
handler: NestedEventListenerOrEventListenerObject;
188198
target: any;
@@ -193,16 +203,31 @@ export interface ListenerTaskMeta extends TaskData {
193203
(removeFnSymbol: any, delegate: Task|NestedEventListenerOrEventListenerObject) => any;
194204
}
195205

206+
// compare the EventListenerOptionsOrCapture
207+
// 1. if the options is usCapture: boolean, compare the useCpature values directly
208+
// 2. if the options is EventListerOptions, only compare the capture
209+
function compareEventListenerOptions(
210+
left: EventListenerOptionsOrCapture, right: EventListenerOptionsOrCapture): boolean {
211+
const leftCapture: any = (typeof left === 'boolean') ?
212+
left :
213+
((typeof left === 'object') ? (left && left.capture) : false);
214+
const rightCapture: any = (typeof right === 'boolean') ?
215+
right :
216+
((typeof right === 'object') ? (right && right.capture) : false);
217+
return !!leftCapture === !!rightCapture;
218+
}
219+
196220
function findExistingRegisteredTask(
197-
target: any, handler: any, name: string, capture: boolean, remove: boolean): Task {
221+
target: any, handler: any, name: string, options: EventListenerOptionsOrCapture,
222+
remove: boolean): Task {
198223
const eventTasks: Task[] = target[EVENT_TASKS];
199224
if (eventTasks) {
200225
for (let i = 0; i < eventTasks.length; i++) {
201226
const eventTask = eventTasks[i];
202227
const data = <ListenerTaskMeta>eventTask.data;
203228
const listener = <NestedEventListener>data.handler;
204229
if ((data.handler === handler || listener.listener === handler) &&
205-
data.useCapturing === capture && data.eventName === name) {
230+
compareEventListenerOptions(data.options, options) && data.eventName === name) {
206231
if (remove) {
207232
eventTasks.splice(i, 1);
208233
}
@@ -213,15 +238,14 @@ function findExistingRegisteredTask(
213238
return null;
214239
}
215240

216-
function findAllExistingRegisteredTasks(
217-
target: any, name: string, capture: boolean, remove: boolean): Task[] {
241+
function findAllExistingRegisteredTasks(target: any, name: string, remove: boolean): Task[] {
218242
const eventTasks: Task[] = target[EVENT_TASKS];
219243
if (eventTasks) {
220244
const result = [];
221245
for (let i = eventTasks.length - 1; i >= 0; i--) {
222246
const eventTask = eventTasks[i];
223247
const data = <ListenerTaskMeta>eventTask.data;
224-
if (data.eventName === name && data.useCapturing === capture) {
248+
if (data.eventName === name) {
225249
result.push(eventTask);
226250
if (remove) {
227251
eventTasks.splice(i, 1);
@@ -247,7 +271,7 @@ function attachRegisteredEvent(target: any, eventTask: Task, isPrepend: boolean)
247271

248272
const defaultListenerMetaCreator = (self: any, args: any[]) => {
249273
return {
250-
useCapturing: args[2],
274+
options: args[2],
251275
eventName: args[0],
252276
handler: args[1],
253277
target: self || _global,
@@ -259,16 +283,15 @@ const defaultListenerMetaCreator = (self: any, args: any[]) => {
259283
// remove the delegate directly and try catch error
260284
if (!this.crossContext) {
261285
if (delegate && (<Task>delegate).invoke) {
262-
return this.target[addFnSymbol](
263-
this.eventName, (<Task>delegate).invoke, this.useCapturing);
286+
return this.target[addFnSymbol](this.eventName, (<Task>delegate).invoke, this.options);
264287
} else {
265-
return this.target[addFnSymbol](this.eventName, delegate, this.useCapturing);
288+
return this.target[addFnSymbol](this.eventName, delegate, this.options);
266289
}
267290
} else {
268291
// add a if/else branch here for performance concern, for most times
269292
// cross site context is false, so we don't need to try/catch
270293
try {
271-
return this.target[addFnSymbol](this.eventName, delegate, this.useCapturing);
294+
return this.target[addFnSymbol](this.eventName, delegate, this.options);
272295
} catch (err) {
273296
// do nothing here is fine, because objects in a cross-site context are unusable
274297
}
@@ -280,16 +303,15 @@ const defaultListenerMetaCreator = (self: any, args: any[]) => {
280303
// remove the delegate directly and try catch error
281304
if (!this.crossContext) {
282305
if (delegate && (<Task>delegate).invoke) {
283-
return this.target[removeFnSymbol](
284-
this.eventName, (<Task>delegate).invoke, this.useCapturing);
306+
return this.target[removeFnSymbol](this.eventName, (<Task>delegate).invoke, this.options);
285307
} else {
286-
return this.target[removeFnSymbol](this.eventName, delegate, this.useCapturing);
308+
return this.target[removeFnSymbol](this.eventName, delegate, this.options);
287309
}
288310
} else {
289311
// add a if/else branch here for performance concern, for most times
290312
// cross site context is false, so we don't need to try/catch
291313
try {
292-
return this.target[removeFnSymbol](this.eventName, delegate, this.useCapturing);
314+
return this.target[removeFnSymbol](this.eventName, delegate, this.options);
293315
} catch (err) {
294316
// do nothing here is fine, because objects in a cross-site context are unusable
295317
}
@@ -314,15 +336,14 @@ export function makeZoneAwareAddListener(
314336

315337
function cancelEventListener(eventTask: Task): void {
316338
const meta = <ListenerTaskMeta>eventTask.data;
317-
findExistingRegisteredTask(
318-
meta.target, eventTask.invoke, meta.eventName, meta.useCapturing, true);
339+
findExistingRegisteredTask(meta.target, eventTask.invoke, meta.eventName, meta.options, true);
319340
return meta.invokeRemoveFunc(removeFnSymbol, eventTask);
320341
}
321342

322343
return function zoneAwareAddListener(self: any, args: any[]) {
323344
const data: ListenerTaskMeta = metaCreator(self, args);
324345

325-
data.useCapturing = data.useCapturing || defaultUseCapturing;
346+
data.options = data.options || defaultUseCapturing;
326347
// - Inside a Web Worker, `this` is undefined, the context is `global`
327348
// - When `addEventListener` is called on the global context in strict mode, `this` is undefined
328349
// see https://github.com/angular/zone.js/issues/190
@@ -351,7 +372,7 @@ export function makeZoneAwareAddListener(
351372

352373
if (!allowDuplicates) {
353374
const eventTask: Task = findExistingRegisteredTask(
354-
data.target, data.handler, data.eventName, data.useCapturing, false);
375+
data.target, data.handler, data.eventName, data.options, false);
355376
if (eventTask) {
356377
// we already registered, so this will have noop.
357378
return data.invokeAddFunc(addFnSymbol, eventTask);
@@ -374,7 +395,7 @@ export function makeZoneAwareRemoveListener(
374395
return function zoneAwareRemoveListener(self: any, args: any[]) {
375396
const data = metaCreator(self, args);
376397

377-
data.useCapturing = data.useCapturing || defaultUseCapturing;
398+
data.options = data.options || defaultUseCapturing;
378399
// - Inside a Web Worker, `this` is undefined, the context is `global`
379400
// - When `addEventListener` is called on the global context in strict mode, `this` is undefined
380401
// see https://github.com/angular/zone.js/issues/190
@@ -399,8 +420,8 @@ export function makeZoneAwareRemoveListener(
399420
if (!delegate || validZoneHandler) {
400421
return data.invokeRemoveFunc(symbol, data.handler);
401422
}
402-
const eventTask = findExistingRegisteredTask(
403-
data.target, data.handler, data.eventName, data.useCapturing, true);
423+
const eventTask =
424+
findExistingRegisteredTask(data.target, data.handler, data.eventName, data.options, true);
404425
if (eventTask) {
405426
eventTask.zone.cancelTask(eventTask);
406427
} else {
@@ -409,9 +430,8 @@ export function makeZoneAwareRemoveListener(
409430
};
410431
}
411432

412-
export function makeZoneAwareRemoveAllListeners(fnName: string, useCapturingParam: boolean = true) {
433+
export function makeZoneAwareRemoveAllListeners(fnName: string) {
413434
const symbol = zoneSymbol(fnName);
414-
const defaultUseCapturing = useCapturingParam ? false : undefined;
415435

416436
return function zoneAwareRemoveAllListener(self: any, args: any[]) {
417437
const target = self || _global;
@@ -424,13 +444,12 @@ export function makeZoneAwareRemoveAllListeners(fnName: string, useCapturingPara
424444
return;
425445
}
426446
const eventName = args[0];
427-
const useCapturing = args[1] || defaultUseCapturing;
428447
// call this function just remove the related eventTask from target[EVENT_TASKS]
429-
findAllExistingRegisteredTasks(target, eventName, useCapturing, true);
430448
// we don't need useCapturing here because useCapturing is just for DOM, and
431449
// removeAllListeners should only be called by node eventEmitter
432450
// and we don't cancel Task either, because call native eventEmitter.removeAllListeners will
433451
// will do remove listener(cancelTask) for us
452+
findAllExistingRegisteredTasks(target, eventName, true);
434453
target[symbol](eventName);
435454
};
436455
}

Diff for: lib/node/events.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const zoneAwarePrependListener = callAndReturnFirstParam(
3030
const zoneAwareRemoveListener =
3131
callAndReturnFirstParam(makeZoneAwareRemoveListener(EE_REMOVE_LISTENER, false));
3232
const zoneAwareRemoveAllListeners =
33-
callAndReturnFirstParam(makeZoneAwareRemoveAllListeners(EE_REMOVE_ALL_LISTENER, false));
33+
callAndReturnFirstParam(makeZoneAwareRemoveAllListeners(EE_REMOVE_ALL_LISTENER));
3434
const zoneAwareListeners = makeZoneAwareListeners(EE_LISTENERS);
3535

3636
export function patchEventEmitterMethods(obj: any): boolean {

0 commit comments

Comments
 (0)