Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

fix(event): in IE, fix #1128, #589, pointer event will be transformed in IE #1129

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions file-size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"path": "dist/zone.min.js",
"checkTarget": true,
"limit": 42050
"limit": 45000
}
]
}
}
71 changes: 59 additions & 12 deletions lib/common/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,26 @@
* @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 {
// use global callback or not
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') {
Expand All @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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 = {};
Expand Down Expand Up @@ -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
Expand All @@ -350,7 +375,6 @@ export function patchEventTarget(
return;
}

const eventName = arguments[0];
const options = arguments[2];

if (blackListedEvents) {
Expand Down Expand Up @@ -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] = {};
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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];
Expand Down
146 changes: 146 additions & 0 deletions test/browser/browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
}));
});
});