From d4dc7cb70c9a281b6022b2e6bb584f788dfd1782 Mon Sep 17 00:00:00 2001 From: arturovt Date: Mon, 30 Sep 2024 00:31:30 +0300 Subject: [PATCH] refactor(zone.js): drop passive supported check (#58010) https://caniuse.com/passive-event-listener Passive event listeners are supported in all major evergreen browsers. Thus, we don't need to check whether passives are supported. PR Close #58010 --- packages/zone.js/lib/common/events.ts | 162 ++++++++++++++++---------- 1 file changed, 102 insertions(+), 60 deletions(-) diff --git a/packages/zone.js/lib/common/events.ts b/packages/zone.js/lib/common/events.ts index ac000ede5c2..8753964fb2f 100644 --- a/packages/zone.js/lib/common/events.ts +++ b/packages/zone.js/lib/common/events.ts @@ -69,29 +69,15 @@ interface InternalEventTask extends EventTask { // its invocation if dispatched later. isRemoved?: boolean; allRemoved?: boolean; -} - -// Note that passive event listeners are now supported by most modern browsers, -// including Chrome, Firefox, Safari, and Edge. There's a pending change that -// would remove support for legacy browsers by zone.js. Removing `passiveSupported` -// from the codebase will reduce the final code size for existing apps that still use zone.js. -let passiveSupported = false; - -if (typeof window !== 'undefined') { - try { - const options = Object.defineProperty({}, 'passive', { - get: function () { - passiveSupported = true; - }, - }); - // Note: We pass the `options` object as the event handler too. This is not compatible with the - // signature of `addEventListener` or `removeEventListener` but enables us to remove the handler - // without an actual handler. - window.addEventListener('test', options as any, options); - window.removeEventListener('test', options as any, options); - } catch (err) { - passiveSupported = false; - } + // `originalDelegate` is the actual event listener object passed when + // calling `addEventListener()`, i.e., `{ handleEvent: event => ... }`. + // This object is used to compare event listeners when `addEventListener` + // is called again with the same event listener object reference. + // For example: + // const eventListenerObject = { handleEvent: console.log }; + // document.addEventListener('click', eventListenerObject); + // document.addEventListener('click', eventListenerObject); + originalDelegate?: EventListenerObject; } // an identifier to tell ZoneTask do not create a new invoke closure @@ -116,31 +102,92 @@ function prepareEventNames(eventName: string, eventNameToString?: (eventName: st } export interface PatchEventTargetOptions { - // validateHandler + /** + * Optional validator for the event handler before patching. + * If it returns false, the handler will not be patched. + * + * @param nativeDelegate The native method (e.g., original addEventListener). + * @param delegate The provided handler function. + * @param target The object being patched. + * @param args The arguments passed to the method. + * @returns Whether the handler is valid for patching. + */ vh?: (nativeDelegate: any, delegate: any, target: any, args: any) => boolean; - // addEventListener function name + + /** + * The property name for the method that adds an event listener. + * Typically `addEventListener`. + */ add?: string; - // removeEventListener function name + + /** + * The property name for the method that removes an event listener. + * Typically `removeEventListener`. + */ rm?: string; - // prependEventListener function name + + /** + * The property name for a method that prepends an event listener. + * Used in some Node.js-style APIs. + */ prepend?: string; - // listeners function name + + /** + * The property name for the method that returns the current listeners. + * `eventListeners` is the default. + * + * Example: + * ```js + * const element = document.querySelector(...); + * console.log(element.eventListeners()); + * ``` + */ listeners?: string; - // removeAllListeners function name + + /** + * The property name for the method that removes all listeners for an event. + * `removeAllListeners` is the default. + */ rmAll?: string; - // useGlobalCallback flag + + /** + * Indicates whether a shared global callback should be used for all events + * instead of individual per-event callbacks. + */ useG?: boolean; - // check duplicate flag when addEventListener + + /** + * If true, checks for duplicate listeners before adding a new one. + * Prevents multiple registrations of the same handler. + */ chkDup?: boolean; - // return target flag when addEventListener + + /** + * If true, the patched add method will return the target object + * (matching typical `addEventListener` behavior). + */ rt?: boolean; - // event compare handler + + /** + * Optional function to compare existing tasks with a given delegate. + * Used to match handlers when removing or managing listeners. + * + * @param task The internal Zone.js task object. + * @param delegate The original event handler function. + * @returns Whether the two refer to the same handler. + */ diff?: (task: any, delegate: any) => boolean; - // support passive or not - supportPassive?: boolean; - // get string from eventName (in nodejs, eventName maybe Symbol) + + /** + * Converts an event name to a string. + * Useful when event names are symbols (e.g., in Node.js). + */ eventNameToString?: (eventName: any) => string; - // transfer eventName + + /** + * Transforms or normalizes the event name before use. + * Allows remapping or renaming of event types. + */ transferEventName?: (eventName: string) => string; } @@ -322,13 +369,7 @@ export function patchEventTarget( * to handle all possible input from the user. */ function buildEventListenerOptions(options: any, passive: boolean) { - if (!passiveSupported && typeof options === 'object' && options) { - // doesn't support passive but user want to pass an object as options. - // this will not work on some old browser, so we just pass a boolean - // as useCapture parameter - return !!options.capture; - } - if (!passiveSupported || !passive) { + if (!passive) { return options; } if (typeof options === 'boolean') { @@ -443,8 +484,7 @@ export function patchEventTarget( ); }; - const compare = - patchOptions && patchOptions.diff ? patchOptions.diff : compareTaskCallbackVsDelegate; + const compare = patchOptions?.diff || compareTaskCallbackVsDelegate; const unpatchedEvents: string[] = (Zone as any)[zoneSymbol('UNPATCHED_EVENTS')]; const passiveEvents: string[] = _global[zoneSymbol('PASSIVE_EVENTS')]; @@ -487,7 +527,7 @@ export function patchEventTarget( if (patchOptions && patchOptions.transferEventName) { eventName = patchOptions.transferEventName(eventName); } - let delegate = arguments[1]; + let delegate: EventListenerOrEventListenerObject = arguments[1]; if (!delegate) { return nativeListener.apply(this, arguments); } @@ -496,23 +536,24 @@ export function patchEventTarget( return nativeListener.apply(this, arguments); } - // don't create the bind delegate function for handleEvent - // case here to improve addEventListener performance - // we will create the bind delegate when invoke - let isHandleEvent = false; + // To improve `addEventListener` performance, we will create the callback + // for the task later when the task is invoked. + let isEventListenerObject = false; if (typeof delegate !== 'function') { + // This checks whether the provided listener argument is an object with + // a `handleEvent` method (since we can call `addEventListener` with a + // function `event => ...` or with an object `{ handleEvent: event => ... }`). if (!delegate.handleEvent) { return nativeListener.apply(this, arguments); } - isHandleEvent = true; + isEventListenerObject = true; } if (validateHandler && !validateHandler(nativeListener, delegate, target, arguments)) { return; } - const passive = - passiveSupported && !!passiveEvents && passiveEvents.indexOf(eventName) !== -1; + const passive = !!passiveEvents && passiveEvents.indexOf(eventName) !== -1; const options = copyEventListenerOptions(buildEventListenerOptions(arguments[2], passive)); const signal: AbortSignal | undefined = options?.signal; if (signal?.aborted) { @@ -610,7 +651,7 @@ export function patchEventTarget( // `taskData.options` to pass it to the native `addEventListener`. const task: InternalEventTask = zone.scheduleEventTask( source, - delegate, + delegate, data, customScheduleFn, customCancelFn, @@ -645,17 +686,18 @@ export function patchEventTarget( if (once) { taskData.options.once = true; } - if (!(!passiveSupported && typeof task.options === 'boolean')) { - // if not support passive, and we pass an option object - // to addEventListener, we should save the options to task + if (typeof task.options !== 'boolean') { + // We should save the options on the task (if it's an object) because + // we'll be using `task.options` later when removing the event listener + // and passing it back to `removeEventListener`. task.options = options; } task.target = target; task.capture = capture; task.eventName = eventName; - if (isHandleEvent) { + if (isEventListenerObject) { // save original delegate for compare to check duplicate - (task as any).originalDelegate = delegate; + task.originalDelegate = delegate; } if (!prepend) { existingTasks.push(task);