From 7c4b784b459f46e91119023b1f4bc4058046ec6f Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Sun, 26 Aug 2018 19:51:34 +0900 Subject: [PATCH] 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..65dc1fb3c 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": 42250 } ] -} \ 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); + }); + }); + })); }); });