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..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, 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 { @@ -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') { @@ -43,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 @@ -69,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( @@ -212,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 = {}; @@ -323,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 @@ -350,7 +375,6 @@ export function patchEventTarget( return; } - const eventName = arguments[0]; const options = arguments[2]; if (blackListedEvents) { @@ -380,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] = {}; @@ -393,6 +419,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; @@ -414,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 @@ -427,7 +461,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 +524,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 +570,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) { @@ -549,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]; 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); + }); + }); + })); }); });