Skip to content

Commit

Permalink
fix(zone.js): fix addEventListener behavior when passive option is en…
Browse files Browse the repository at this point in the history
…abled

This fix the inconsistent behavior when multiple identical eventListeners are registered on the same eventTarget with passive enabled

Close angular#45020
  • Loading branch information
Zuckjet committed Feb 10, 2022
1 parent bbababe commit 1a7b875
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 15 deletions.
16 changes: 10 additions & 6 deletions packages/zone.js/lib/browser/event-target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/

import { CAPTURE_STR, PASSVE_STR } from "../common/utils";

export function eventTargetPatch(_global: any, api: _ZonePrivate) {
if ((Zone as any)[api.symbol('patchEventTarget')]) {
// EventTarget is already patched.
Expand All @@ -16,13 +18,15 @@ export function eventTargetPatch(_global: any, api: _ZonePrivate) {
// predefine all __zone_symbol__ + eventName + true/false string
for (let i = 0; i < eventNames.length; i++) {
const eventName = eventNames[i];
const falseEventName = eventName + FALSE_STR;
const trueEventName = eventName + TRUE_STR;
const symbol = ZONE_SYMBOL_PREFIX + falseEventName;
const symbolCapture = ZONE_SYMBOL_PREFIX + trueEventName;
const falseCaptureFalsePassiveName = `${FALSE_STR}${CAPTURE_STR}${FALSE_STR}${PASSVE_STR}`;
const falseCaptureTruePassiveName = `${FALSE_STR}${CAPTURE_STR}${TRUE_STR}${PASSVE_STR}`;
const trueCaptureFalsePassiveName = `${TRUE_STR}${CAPTURE_STR}${FALSE_STR}${PASSVE_STR}`;
const trueCaptureTruePassiveName = `${TRUE_STR}${CAPTURE_STR}${TRUE_STR}${PASSVE_STR}`;
zoneSymbolEventNames[eventName] = {};
zoneSymbolEventNames[eventName][FALSE_STR] = symbol;
zoneSymbolEventNames[eventName][TRUE_STR] = symbolCapture;
zoneSymbolEventNames[eventName][falseCaptureFalsePassiveName] = `${ZONE_SYMBOL_PREFIX}${eventName}${falseCaptureFalsePassiveName}`;
zoneSymbolEventNames[eventName][falseCaptureTruePassiveName] = `${ZONE_SYMBOL_PREFIX}${eventName}${falseCaptureTruePassiveName}`;
zoneSymbolEventNames[eventName][trueCaptureFalsePassiveName] = `${ZONE_SYMBOL_PREFIX}${eventName}${trueCaptureFalsePassiveName}`;
zoneSymbolEventNames[eventName][trueCaptureTruePassiveName] = `${ZONE_SYMBOL_PREFIX}${eventName}${trueCaptureTruePassiveName}`;
}

const EVENT_TARGET = _global['EventTarget'];
Expand Down
73 changes: 64 additions & 9 deletions packages/zone.js/lib/common/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* @suppress {missingRequire}
*/

import {ADD_EVENT_LISTENER_STR, attachOriginToPatched, FALSE_STR, isNode, ObjectGetPrototypeOf, REMOVE_EVENT_LISTENER_STR, TRUE_STR, ZONE_SYMBOL_PREFIX, zoneSymbol} from './utils';
import {ADD_EVENT_LISTENER_STR, attachOriginToPatched, FALSE_STR, isNode, ObjectGetPrototypeOf, REMOVE_EVENT_LISTENER_STR, TRUE_STR, CAPTURE_STR, PASSVE_STR, ZONE_SYMBOL_PREFIX, zoneSymbol} from './utils';


/** @internal **/
Expand Down Expand Up @@ -137,7 +137,7 @@ export function patchEventTarget(
return error;
};

function globalCallback(context: unknown, event: Event, isCapture: boolean) {
function globalCallback(context: unknown, event: Event, options: boolean | AddEventListenerOptions) {
// https://github.com/angular/zone.js/issues/911, in IE, sometimes
// event will be undefined, so we need to use window.event
event = event || _global.event;
Expand All @@ -147,7 +147,23 @@ export function patchEventTarget(
// event.target is needed for Samsung TV and SourceBuffer
// || global is needed https://github.com/angular/zone.js/issues/190
const target: any = context || event.target || _global;
const tasks = target[zoneSymbolEventNames[event.type][isCapture ? TRUE_STR : FALSE_STR]];

let props: string;
if (typeof options === 'boolean') {
if (options) {
props = `${TRUE_STR}${CAPTURE_STR}${FALSE_STR}${PASSVE_STR}`;
} else {
props = `${FALSE_STR}${CAPTURE_STR}${FALSE_STR}${PASSVE_STR}`;
}
} else {
if (options.capture) {
props = `${TRUE_STR}${CAPTURE_STR}${TRUE_STR}${PASSVE_STR}`;
} else {
props = `${FALSE_STR}${CAPTURE_STR}${TRUE_STR}${PASSVE_STR}`;
}
}

const tasks = target[zoneSymbolEventNames[event.type][props]];
if (tasks) {
const errors = [];
// invoke all tasks which attached to current target with given event.type and capture = false
Expand Down Expand Up @@ -183,16 +199,26 @@ export function patchEventTarget(
}
}

// global shared zoneAwareCallback to handle all event callback with capture = false
// global shared zoneAwareCallback to handle all event callback with capture = false and passive = false
const globalZoneAwareCallback = function(this: unknown, event: Event) {
return globalCallback(this, event, false);
};

// global shared zoneAwareCallback to handle all event callback with capture = true
// global shared zoneAwareCallback to handle all event callback with capture = false and passive = true
const globalZoneAwarePassiveCallback = function(this: unknown, event: Event) {
return globalCallback(this, event, {passive: true});
};

// global shared zoneAwareCallback to handle all event callback with capture = true and passive = false
const globalZoneAwareCaptureCallback = function(this: unknown, event: Event) {
return globalCallback(this, event, true);
};

// global shared zoneAwareCallback to handle all event callback with capture = true and passive = true
const globalZoneAwareCapturePassiveCallback = function(this: unknown, event: Event) {
return globalCallback(this, event, {capture: true, passive: true});
};

function patchEventTargetMethods(obj: any, patchOptions?: PatchEventTargetOptions) {
if (!obj) {
return false;
Expand Down Expand Up @@ -275,17 +301,31 @@ export function patchEventTarget(
return {...options, passive: true};
}
return options;
}
}7

const customScheduleGlobal = function(task: Task) {
// if there is already a task for the eventName + capture,
// just return, because we use the shared globalZoneAwareCallback here.
if (taskData.isExisting) {
return;
}
let callback;
if (taskData.capture) {
if (taskData.options && taskData.options.passive) {
callback = globalZoneAwareCapturePassiveCallback;
} else {
callback = globalZoneAwareCaptureCallback;
}
} else {
if (taskData.options && taskData.options.passive) {
callback = globalZoneAwarePassiveCallback;
} else {
callback = globalZoneAwareCallback;
}
}
return nativeAddEventListener.call(
taskData.target, taskData.eventName,
taskData.capture ? globalZoneAwareCaptureCallback : globalZoneAwareCallback,
callback,
taskData.options);
};

Expand Down Expand Up @@ -410,14 +450,29 @@ export function patchEventTarget(

const capture = !options ? false : typeof options === 'boolean' ? true : options.capture;
const once = options && typeof options === 'object' ? options.once : false;

const passiveEnabled = passiveSupported && options && typeof options === 'object' ? options.passive : false;
const zone = Zone.current;
let symbolEventNames = zoneSymbolEventNames[eventName];
if (!symbolEventNames) {
prepareEventNames(eventName, eventNameToString);
symbolEventNames = zoneSymbolEventNames[eventName];
}
const symbolEventName = symbolEventNames[capture ? TRUE_STR : FALSE_STR];

let props;
if (capture) {
if (passiveEnabled) {
props = `${TRUE_STR}${CAPTURE_STR}${TRUE_STR}${PASSVE_STR}`;
} else {
props = `${TRUE_STR}${CAPTURE_STR}${FALSE_STR}${PASSVE_STR}`
}
} else {
if (passiveEnabled) {
props = `${FALSE_STR}${CAPTURE_STR}${TRUE_STR}${PASSVE_STR}`;
} else {
props = `${FALSE_STR}${CAPTURE_STR}${FALSE_STR}${PASSVE_STR}`;
}
}
const symbolEventName = symbolEventNames[props];
let existingTasks = target[symbolEventName];
let isExisting = false;
if (existingTasks) {
Expand Down
4 changes: 4 additions & 0 deletions packages/zone.js/lib/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export const ZONE_SYMBOL_REMOVE_EVENT_LISTENER = Zone.__symbol__(REMOVE_EVENT_LI
export const TRUE_STR = 'true';
/** false string const */
export const FALSE_STR = 'false';
/** capture string const */
export const CAPTURE_STR= 'capture';
/** passive string const */
export const PASSVE_STR= 'passive';
/** Zone symbol prefix string const. */
export const ZONE_SYMBOL_PREFIX = Zone.__symbol__('');

Expand Down

0 comments on commit 1a7b875

Please sign in to comment.