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

fix(event): should pass boolean to addEventListener if not support passive #1053

Merged
merged 2 commits into from
Jun 18, 2018
Merged
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
41 changes: 39 additions & 2 deletions lib/common/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ interface EventTaskData extends TaskData {
readonly useG?: boolean;
}

let passiveSupported = false;

if (typeof window !== 'undefined') {
try {
const options = Object.defineProperty({}, 'passive', {
get: function() {
passiveSupported = true;
}
});

window.addEventListener('test', options, options);
window.removeEventListener('test', options, options);
} catch (err) {
passiveSupported = false;
}
}

// an identifier to tell ZoneTask do not create a new invoke closure
const OPTIMIZED_ZONE_EVENT_TASK_DATA: EventTaskData = {
useG: true
Expand Down Expand Up @@ -50,6 +67,8 @@ export interface PatchEventTargetOptions {
rt?: boolean;
// event compare handler
diff?: (task: any, delegate: any) => boolean;
// support passive or not
supportPassive?: boolean;
}

export function patchEventTarget(
Expand Down Expand Up @@ -212,12 +231,25 @@ export function patchEventTarget(
proto[patchOptions.prepend];
}

const customScheduleGlobal = function() {
function checkIsPassive(task: Task) {
if (!passiveSupported && typeof taskData.options !== 'boolean' &&
typeof taskData.options !== 'undefined' && taskData.options !== null) {
// options is a non-null non-undefined object
// passive is not supported
// don't pass options as object
// just pass capture as a boolean
(task as any).options = !!taskData.options.capture;
taskData.options = (task as any).options;
}
}

const customScheduleGlobal = function(task: Task) {
// if there is already a task for the eventName + capture,
// just return, because we use the shared globalZoneAwareCallback here.
if (taskData.isExisting) {
return;
}
checkIsPassive(task);
return nativeAddEventListener.call(
taskData.target, taskData.eventName,
taskData.capture ? globalZoneAwareCaptureCallback : globalZoneAwareCallback,
Expand Down Expand Up @@ -265,6 +297,7 @@ export function patchEventTarget(
};

const customScheduleNonGlobal = function(task: Task) {
checkIsPassive(task);
return nativeAddEventListener.call(
taskData.target, taskData.eventName, task.invoke, taskData.options);
};
Expand Down Expand Up @@ -421,7 +454,11 @@ export function patchEventTarget(
if (once) {
options.once = true;
}
task.options = options;
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
task.options = options;
}
task.target = target;
task.capture = capture;
task.eventName = eventName;
Expand Down
52 changes: 50 additions & 2 deletions test/browser/browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ try {
supportsPassive = true;
}
});
window.addEventListener('test', null as any, opts);
window.removeEventListener('test', null as any, opts);
window.addEventListener('test', opts as any, opts);
window.removeEventListener('test', opts as any, opts);
} catch (e) {
}

Expand All @@ -75,6 +75,14 @@ function ieOrEdge() {

(ieOrEdge as any).message = 'IE/Edge Test';

class TestEventListener {
logs: string[] = [];
addEventListener(eventName: string, listener: any, options: any) {
this.logs.push(options);
}
removeEventListener(eventName: string, listener: any, options: any) {}
}

describe('Zone', function() {
const rootZone = Zone.current;
(Zone as any)[zoneSymbol('ignoreConsoleErrorUncaughtError')] = true;
Expand Down Expand Up @@ -996,6 +1004,46 @@ describe('Zone', function() {
expect(logs).toEqual(['click']);
}));

it('should change options to boolean if not support passive', () => {
patchEventTarget(window, [TestEventListener.prototype]);
const testEventListener = new TestEventListener();

const listener = function() {};
testEventListener.addEventListener('test', listener, {passive: true});
testEventListener.addEventListener('test1', listener, {once: true});
testEventListener.addEventListener('test2', listener, {capture: true});
testEventListener.addEventListener('test3', listener, {passive: false});
testEventListener.addEventListener('test4', listener, {once: false});
testEventListener.addEventListener('test5', listener, {capture: false});
if (!supportsPassive) {
expect(testEventListener.logs).toEqual([false, false, true, false, false, false]);
} else {
expect(testEventListener.logs).toEqual([
{passive: true}, {once: true}, {capture: true}, {passive: false}, {once: false},
{capture: false}
]);
}
});

it('should change options to boolean if not support passive on HTMLElement', () => {
const logs: string[] = [];
const listener = (e: Event) => {
logs.push('clicked');
};

(button as any).addEventListener('click', listener, {once: true});
button.dispatchEvent(clickEvent);
expect(logs).toEqual(['clicked']);
button.dispatchEvent(clickEvent);
if (supportsPassive) {
expect(logs).toEqual(['clicked']);
} else {
expect(logs).toEqual(['clicked', 'clicked']);
}

button.removeEventListener('click', listener);
});

it('should support addEventListener with AddEventListenerOptions passive setting',
ifEnvSupports(supportEventListenerOptions, function() {
const hookSpy = jasmine.createSpy('hook');
Expand Down