From 98dd51f2c7ea2b4f95f216a9b859d9203085d467 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Sun, 26 Aug 2018 19:51:34 +0900 Subject: [PATCH 1/2] fix(event): in IE, fix #1128, #589, pointer event will be transformed in IE --- file-size-limit.json | 4 +- lib/common/events.ts | 69 +++++++++++++++-- test/browser/browser.spec.ts | 146 +++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 8 deletions(-) diff --git a/file-size-limit.json b/file-size-limit.json index e5e12f2ef..65440271e 100644 --- a/file-size-limit.json +++ b/file-size-limit.json @@ -3,7 +3,7 @@ { "path": "dist/zone.min.js", "checkTarget": true, - "limit": 42050 + "limit": 45000 } ] -} \ No newline at end of file +} diff --git a/lib/common/events.ts b/lib/common/events.ts index 4d404b427..7fb5ec190 100644 --- a/lib/common/events.ts +++ b/lib/common/events.ts @@ -10,7 +10,7 @@ * @suppress {missingRequire} */ -import {ADD_EVENT_LISTENER_STR, attachOriginToPatched, FALSE_STR, ObjectGetPrototypeOf, REMOVE_EVENT_LISTENER_STR, TRUE_STR, ZONE_SYMBOL_PREFIX, zoneSymbol} from './utils'; +import {ADD_EVENT_LISTENER_STR, attachOriginToPatched, FALSE_STR, isIE, isIEOrEdge, ObjectGetPrototypeOf, REMOVE_EVENT_LISTENER_STR, TRUE_STR, ZONE_SYMBOL_PREFIX, zoneSymbol} from './utils'; /** @internal **/ interface EventTaskData extends TaskData { @@ -18,6 +18,18 @@ interface EventTaskData extends TaskData { readonly useG?: boolean; } +const pointerEventsMap: {[key: string]: string} = { + 'MSPointerCancel': 'pointercancel', + 'MSPointerDown': 'pointerdown', + 'MSPointerEnter': 'pointerenter', + 'MSPointerHover': 'pointerhover', + 'MSPointerLeave': 'pointerleave', + 'MSPointerMove': 'pointermove', + 'MSPointerOut': 'pointerout', + 'MSPointerOver': 'pointerover', + 'MSPointerUp': 'pointerup' +}; + let passiveSupported = false; if (typeof window !== 'undefined') { @@ -87,6 +99,16 @@ export function patchEventTarget( const PREPEND_EVENT_LISTENER = 'prependListener'; const PREPEND_EVENT_LISTENER_SOURCE = '.' + PREPEND_EVENT_LISTENER + ':'; + Object.keys(pointerEventsMap).forEach(msEventName => { + const eventName = pointerEventsMap[msEventName]; + zoneSymbolEventNames[eventName] = {}; + zoneSymbolEventNames[eventName][FALSE_STR] = ZONE_SYMBOL_PREFIX + eventName + FALSE_STR; + zoneSymbolEventNames[eventName][TRUE_STR] = ZONE_SYMBOL_PREFIX + eventName + TRUE_STR; + zoneSymbolEventNames[msEventName] = {}; + zoneSymbolEventNames[msEventName][FALSE_STR] = ZONE_SYMBOL_PREFIX + msEventName + FALSE_STR; + zoneSymbolEventNames[msEventName][TRUE_STR] = ZONE_SYMBOL_PREFIX + msEventName + TRUE_STR; + }); + const invokeTask = function(task: any, target: any, event: Event) { // for better performance, check isRemoved which is set // by removeEventListener @@ -122,7 +144,15 @@ 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 = this || event.target || _global; - const tasks = target[zoneSymbolEventNames[event.type][FALSE_STR]]; + let tasks = target[zoneSymbolEventNames[event.type][FALSE_STR]]; + if (isIEOrEdge) { + const pointerMappedEvent = pointerEventsMap[event.type]; + const msTasks = + pointerMappedEvent && target[zoneSymbolEventNames[pointerMappedEvent]][FALSE_STR]; + if (msTasks) { + tasks = tasks.concat(msTasks); + } + } if (tasks) { // invoke all tasks which attached to current target with given event.type and capture = false // for performance concern, if task.length === 1, just invoke @@ -154,7 +184,15 @@ 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 = this || event.target || _global; - const tasks = target[zoneSymbolEventNames[event.type][TRUE_STR]]; + let tasks = target[zoneSymbolEventNames[event.type][TRUE_STR]]; + if (isIEOrEdge) { + const pointerMappedEvent = pointerEventsMap[event.type]; + const msTasks = + pointerMappedEvent && target[zoneSymbolEventNames[pointerMappedEvent]][FALSE_STR]; + if (msTasks) { + tasks = tasks.concat(msTasks); + } + } if (tasks) { // invoke all tasks which attached to current target with given event.type and capture = false // for performance concern, if task.length === 1, just invoke @@ -350,7 +388,11 @@ export function patchEventTarget( return; } - const eventName = arguments[0]; + let eventName = arguments[0]; + if (isIEOrEdge) { + const msEventName = pointerEventsMap[eventName]; + eventName = msEventName ? msEventName : eventName; + } const options = arguments[2]; if (blackListedEvents) { @@ -393,6 +435,13 @@ export function patchEventTarget( } let existingTasks = target[symbolEventName]; let isExisting = false; + if (isIEOrEdge && !existingTasks) { + const msEventName = pointerEventsMap[eventName]; + if (msEventName) { + existingTasks = + target[zoneSymbolEventNames[msEventName][capture ? TRUE_STR : FALSE_STR]]; + } + } if (existingTasks) { // already have task registered isExisting = true; @@ -427,7 +476,8 @@ export function patchEventTarget( } taskData.target = target; taskData.capture = capture; - taskData.eventName = eventName; + // in pointer event, we need to use original event name here. + taskData.eventName = arguments[0]; taskData.isExisting = isExisting; const data = useGlobalCallback ? OPTIMIZED_ZONE_EVENT_TASK_DATA : undefined; @@ -489,7 +539,11 @@ export function patchEventTarget( proto[REMOVE_EVENT_LISTENER] = function() { const target = this || _global; - const eventName = arguments[0]; + let eventName = arguments[0]; + if (isIEOrEdge) { + const msEventName = pointerEventsMap[eventName]; + eventName = msEventName ? msEventName : eventName; + } const options = arguments[2]; let capture; @@ -531,6 +585,9 @@ export function patchEventTarget( // remove globalZoneAwareCallback and remove the task cache from target (existingTask as any).allRemoved = true; target[symbolEventName] = null; + if (isIEOrEdge) { + existingTask.eventName = arguments[0]; + } } existingTask.zone.cancelTask(existingTask); if (returnTarget) { diff --git a/test/browser/browser.spec.ts b/test/browser/browser.spec.ts index 93c840c4c..6509063af 100644 --- a/test/browser/browser.spec.ts +++ b/test/browser/browser.spec.ts @@ -2662,5 +2662,151 @@ describe('Zone', function() { }); })); }); + + describe( + 'pointer event in IE', + ifEnvSupports( + () => { + return getIEVersion() === 11; + }, + () => { + const pointerEventsMap: {[key: string]: string} = { + 'MSPointerCancel': 'pointercancel', + 'MSPointerDown': 'pointerdown', + 'MSPointerEnter': 'pointerenter', + 'MSPointerHover': 'pointerhover', + 'MSPointerLeave': 'pointerleave', + 'MSPointerMove': 'pointermove', + 'MSPointerOut': 'pointerout', + 'MSPointerOver': 'pointerover', + 'MSPointerUp': 'pointerup' + }; + + let div: HTMLDivElement; + beforeEach(() => { + div = document.createElement('div'); + document.body.appendChild(div); + }); + afterEach(() => { + document.body.removeChild(div); + }); + Object.keys(pointerEventsMap).forEach(key => { + it(`${key} and ${pointerEventsMap[key]} should both be triggered`, + (done: DoneFn) => { + const logs: string[] = []; + div.addEventListener(key, (event: any) => { + expect(event.type).toEqual(pointerEventsMap[key]); + logs.push(`${key} triggered`); + }); + div.addEventListener(pointerEventsMap[key], (event: any) => { + expect(event.type).toEqual(pointerEventsMap[key]); + logs.push(`${pointerEventsMap[key]} triggered`); + }); + const evt1 = document.createEvent('Event'); + evt1.initEvent(key, true, true); + div.dispatchEvent(evt1); + + setTimeout(() => { + expect(logs).toEqual( + [`${key} triggered`, `${pointerEventsMap[key]} triggered`]); + }); + + const evt2 = document.createEvent('Event'); + evt2.initEvent(pointerEventsMap[key], true, true); + div.dispatchEvent(evt2); + + setTimeout(() => { + expect(logs).toEqual( + [`${key} triggered`, `${pointerEventsMap[key]} triggered`]); + }); + + setTimeout(done); + }); + + it(`${key} and ${ + pointerEventsMap[key]} with same listener should not be triggered twice`, + (done: DoneFn) => { + const logs: string[] = []; + const listener = function(event: any) { + expect(event.type).toEqual(pointerEventsMap[key]); + logs.push(`${key} triggered`); + }; + div.addEventListener(key, listener); + div.addEventListener(pointerEventsMap[key], listener); + + const evt1 = document.createEvent('Event'); + evt1.initEvent(key, true, true); + div.dispatchEvent(evt1); + + setTimeout(() => { + expect(logs).toEqual([`${key} triggered`]); + }); + + const evt2 = document.createEvent('Event'); + evt2.initEvent(pointerEventsMap[key], true, true); + div.dispatchEvent(evt2); + + setTimeout(() => { + expect(logs).toEqual([`${pointerEventsMap[key]} triggered`]); + }); + + setTimeout(done); + }); + + it(`${key} and ${ + pointerEventsMap + [key]} should be able to be removed with removeEventListener`, + (done: DoneFn) => { + const logs: string[] = []; + const listener1 = function(event: any) { + logs.push(`${key} triggered`); + }; + const listener2 = function(event: any) { + logs.push(`${pointerEventsMap[key]} triggered`); + }; + div.addEventListener(key, listener1); + div.addEventListener(pointerEventsMap[key], listener2); + + div.removeEventListener(key, listener1); + div.removeEventListener(key, listener2); + + const evt1 = document.createEvent('Event'); + evt1.initEvent(key, true, true); + div.dispatchEvent(evt1); + + setTimeout(() => { + expect(logs).toEqual([]); + }); + + const evt2 = document.createEvent('Event'); + evt2.initEvent(pointerEventsMap[key], true, true); + div.dispatchEvent(evt2); + + setTimeout(() => { + expect(logs).toEqual([]); + }); + + div.addEventListener(key, listener1); + div.addEventListener(pointerEventsMap[key], listener2); + + div.removeEventListener(pointerEventsMap[key], listener1); + div.removeEventListener(pointerEventsMap[key], listener2); + + div.dispatchEvent(evt1); + + setTimeout(() => { + expect(logs).toEqual([]); + }); + + div.dispatchEvent(evt2); + + setTimeout(() => { + expect(logs).toEqual([]); + }); + + setTimeout(done); + }); + }); + })); }); }); From ad2ace640b44711acd3e6b99af8b3ec5e98d01e9 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Sun, 21 Jul 2019 22:49:51 +0900 Subject: [PATCH 2/2] test ie --- lib/common/events.ts | 74 +++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 42 deletions(-) diff --git a/lib/common/events.ts b/lib/common/events.ts index 7fb5ec190..fdb366b91 100644 --- a/lib/common/events.ts +++ b/lib/common/events.ts @@ -10,7 +10,7 @@ * @suppress {missingRequire} */ -import {ADD_EVENT_LISTENER_STR, attachOriginToPatched, FALSE_STR, isIE, isIEOrEdge, ObjectGetPrototypeOf, REMOVE_EVENT_LISTENER_STR, TRUE_STR, ZONE_SYMBOL_PREFIX, zoneSymbol} from './utils'; +import {ADD_EVENT_LISTENER_STR, attachOriginToPatched, FALSE_STR, isIEOrEdge, isNode, ObjectGetPrototypeOf, REMOVE_EVENT_LISTENER_STR, TRUE_STR, ZONE_SYMBOL_PREFIX, zoneSymbol} from './utils'; /** @internal **/ interface EventTaskData extends TaskData { @@ -55,8 +55,8 @@ const OPTIMIZED_ZONE_EVENT_TASK_DATA: EventTaskData = { export const zoneSymbolEventNames: any = {}; export const globalSources: any = {}; -const EVENT_NAME_SYMBOL_REGX = /^__zone_symbol__(\w+)(true|false)$/; -const IMMEDIATE_PROPAGATION_SYMBOL = ('__zone_symbol__propagationStopped'); +const EVENT_NAME_SYMBOL_REGX = new RegExp('^' + ZONE_SYMBOL_PREFIX + '(\\w+)(true|false)$'); +const IMMEDIATE_PROPAGATION_SYMBOL = zoneSymbol('propagationStopped'); export interface PatchEventTargetOptions { // validateHandler @@ -81,6 +81,8 @@ export interface PatchEventTargetOptions { diff?: (task: any, delegate: any) => boolean; // support passive or not supportPassive?: boolean; + // get string from eventName (in nodejs, eventName maybe Symbol) + eventNameToString?: (eventName: any) => string; } export function patchEventTarget( @@ -99,16 +101,6 @@ export function patchEventTarget( const PREPEND_EVENT_LISTENER = 'prependListener'; const PREPEND_EVENT_LISTENER_SOURCE = '.' + PREPEND_EVENT_LISTENER + ':'; - Object.keys(pointerEventsMap).forEach(msEventName => { - const eventName = pointerEventsMap[msEventName]; - zoneSymbolEventNames[eventName] = {}; - zoneSymbolEventNames[eventName][FALSE_STR] = ZONE_SYMBOL_PREFIX + eventName + FALSE_STR; - zoneSymbolEventNames[eventName][TRUE_STR] = ZONE_SYMBOL_PREFIX + eventName + TRUE_STR; - zoneSymbolEventNames[msEventName] = {}; - zoneSymbolEventNames[msEventName][FALSE_STR] = ZONE_SYMBOL_PREFIX + msEventName + FALSE_STR; - zoneSymbolEventNames[msEventName][TRUE_STR] = ZONE_SYMBOL_PREFIX + msEventName + TRUE_STR; - }); - const invokeTask = function(task: any, target: any, event: Event) { // for better performance, check isRemoved which is set // by removeEventListener @@ -144,15 +136,7 @@ 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 = this || event.target || _global; - let tasks = target[zoneSymbolEventNames[event.type][FALSE_STR]]; - if (isIEOrEdge) { - const pointerMappedEvent = pointerEventsMap[event.type]; - const msTasks = - pointerMappedEvent && target[zoneSymbolEventNames[pointerMappedEvent]][FALSE_STR]; - if (msTasks) { - tasks = tasks.concat(msTasks); - } - } + const tasks = target[zoneSymbolEventNames[event.type][FALSE_STR]]; if (tasks) { // invoke all tasks which attached to current target with given event.type and capture = false // for performance concern, if task.length === 1, just invoke @@ -184,15 +168,7 @@ 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 = this || event.target || _global; - let tasks = target[zoneSymbolEventNames[event.type][TRUE_STR]]; - if (isIEOrEdge) { - const pointerMappedEvent = pointerEventsMap[event.type]; - const msTasks = - pointerMappedEvent && target[zoneSymbolEventNames[pointerMappedEvent]][FALSE_STR]; - if (msTasks) { - tasks = tasks.concat(msTasks); - } - } + const tasks = target[zoneSymbolEventNames[event.type][TRUE_STR]]; if (tasks) { // invoke all tasks which attached to current target with given event.type and capture = false // for performance concern, if task.length === 1, just invoke @@ -250,6 +226,8 @@ export function patchEventTarget( return false; } + const eventNameToString = patchOptions && patchOptions.eventNameToString; + // a shared global taskData to pass data for scheduleEventTask // so we do not need to create a new object just for pass some data const taskData: any = {}; @@ -361,17 +339,26 @@ export function patchEventTarget( const compare = (patchOptions && patchOptions.diff) ? patchOptions.diff : compareTaskCallbackVsDelegate; - const blackListedEvents: string[] = (Zone as any)[Zone.__symbol__('BLACK_LISTED_EVENTS')]; + const blackListedEvents: string[] = (Zone as any)[zoneSymbol('BLACK_LISTED_EVENTS')]; const makeAddListener = function( nativeListener: any, addSource: string, customScheduleFn: any, customCancelFn: any, returnTarget = false, prepend = false) { return function() { const target = this || _global; + let eventName = arguments[0]; + if (isIEOrEdge) { + const msEventName = pointerEventsMap[eventName]; + eventName = msEventName ? msEventName : eventName; + } let delegate = arguments[1]; if (!delegate) { return nativeListener.apply(this, arguments); } + if (isNode && eventName === 'uncaughtException') { + // don't patch uncaughtException of nodejs to prevent endless loop + return nativeListener.apply(this, arguments); + } // don't create the bind delegate function for handleEvent // case here to improve addEventListener performance @@ -388,11 +375,6 @@ export function patchEventTarget( return; } - let eventName = arguments[0]; - if (isIEOrEdge) { - const msEventName = pointerEventsMap[eventName]; - eventName = msEventName ? msEventName : eventName; - } const options = arguments[2]; if (blackListedEvents) { @@ -422,8 +404,10 @@ export function patchEventTarget( let symbolEventName; if (!symbolEventNames) { // the code is duplicate, but I just want to get some better performance - const falseEventName = eventName + FALSE_STR; - const trueEventName = eventName + TRUE_STR; + const falseEventName = + (eventNameToString ? eventNameToString(eventName) : eventName) + FALSE_STR; + const trueEventName = + (eventNameToString ? eventNameToString(eventName) : eventName) + TRUE_STR; const symbol = ZONE_SYMBOL_PREFIX + falseEventName; const symbolCapture = ZONE_SYMBOL_PREFIX + trueEventName; zoneSymbolEventNames[eventName] = {}; @@ -463,7 +447,8 @@ export function patchEventTarget( source = targetSource[eventName]; } if (!source) { - source = constructorName + addSource + eventName; + source = constructorName + addSource + + (eventNameToString ? eventNameToString(eventName) : eventName); } // do not create a new object as task.data to pass those things // just use the global shared one @@ -606,10 +591,15 @@ export function patchEventTarget( proto[LISTENERS_EVENT_LISTENER] = function() { const target = this || _global; - const eventName = arguments[0]; + let eventName = arguments[0]; + if (isIEOrEdge) { + const msEventName = pointerEventsMap[eventName]; + eventName = msEventName ? msEventName : eventName; + } const listeners: any[] = []; - const tasks = findEventTasks(target, eventName); + const tasks = + findEventTasks(target, eventNameToString ? eventNameToString(eventName) : eventName); for (let i = 0; i < tasks.length; i++) { const task: any = tasks[i];