From 1a7b875e94646eb11488ca773b804e43ed8f1d04 Mon Sep 17 00:00:00 2001 From: zuckjet Date: Thu, 10 Feb 2022 20:55:58 +0800 Subject: [PATCH] fix(zone.js): fix addEventListener behavior when passive option is enabled This fix the inconsistent behavior when multiple identical eventListeners are registered on the same eventTarget with passive enabled Close #45020 --- packages/zone.js/lib/browser/event-target.ts | 16 +++-- packages/zone.js/lib/common/events.ts | 73 +++++++++++++++++--- packages/zone.js/lib/common/utils.ts | 4 ++ 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/packages/zone.js/lib/browser/event-target.ts b/packages/zone.js/lib/browser/event-target.ts index 215ac3d13ef038..0eb6b9894d44ea 100644 --- a/packages/zone.js/lib/browser/event-target.ts +++ b/packages/zone.js/lib/browser/event-target.ts @@ -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. @@ -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']; diff --git a/packages/zone.js/lib/common/events.ts b/packages/zone.js/lib/common/events.ts index ceed7d23dc2da3..1ace543f9e9847 100644 --- a/packages/zone.js/lib/common/events.ts +++ b/packages/zone.js/lib/common/events.ts @@ -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 **/ @@ -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; @@ -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 @@ -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; @@ -275,7 +301,7 @@ 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, @@ -283,9 +309,23 @@ export function patchEventTarget( 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); }; @@ -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) { diff --git a/packages/zone.js/lib/common/utils.ts b/packages/zone.js/lib/common/utils.ts index 28a58f0ff1693d..555eb051d36261 100644 --- a/packages/zone.js/lib/common/utils.ts +++ b/packages/zone.js/lib/common/utils.ts @@ -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__('');