From 0591f16a972951d70f6683eb81ec2ea8522c8f4a Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Tue, 20 Mar 2018 23:30:24 +0900 Subject: [PATCH] fix(event): should pass boolean to addEventListener if not support passive --- lib/common/events.ts | 41 +++++++++- test/browser/browser.spec.ts | 148 +++++++++++++++++++++++------------ 2 files changed, 136 insertions(+), 53 deletions(-) diff --git a/lib/common/events.ts b/lib/common/events.ts index ecc80122f..ea6cdd86a 100644 --- a/lib/common/events.ts +++ b/lib/common/events.ts @@ -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 @@ -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( @@ -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, @@ -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); }; @@ -422,7 +455,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; diff --git a/test/browser/browser.spec.ts b/test/browser/browser.spec.ts index 9b013d70c..d39294209 100644 --- a/test/browser/browser.spec.ts +++ b/test/browser/browser.spec.ts @@ -47,8 +47,8 @@ try { supportsPassive = true; } }); - window.addEventListener('test', null, opts); - window.removeEventListener('test', null, opts); + window.addEventListener('test', opts, opts); + window.removeEventListener('test', opts, opts); } catch (e) { } @@ -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; @@ -984,6 +992,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'); @@ -2472,61 +2520,59 @@ describe('Zone', function() { }); })); - describe('ResizeObserver', ifEnvSupports('ResizeObserver', () => { - it('ResizeObserver callback should be in zone', (done) => { - const ResizeObserver = (window as any)['ResizeObserver']; - const div = document.createElement('div'); - const zone = Zone.current.fork({ - name: 'observer' - }); - const observer = new ResizeObserver((entries: any, ob: any) => { - expect(Zone.current.name).toEqual(zone.name); - - expect(entries.length).toBe(1); - expect(entries[0].target).toBe(div); - done(); - }); - - zone.run(() => { - observer.observe(div); - }); + describe( + 'ResizeObserver', ifEnvSupports('ResizeObserver', () => { + it('ResizeObserver callback should be in zone', (done) => { + const ResizeObserver = (window as any)['ResizeObserver']; + const div = document.createElement('div'); + const zone = Zone.current.fork({name: 'observer'}); + const observer = new ResizeObserver((entries: any, ob: any) => { + expect(Zone.current.name).toEqual(zone.name); - document.body.appendChild(div); - }); + expect(entries.length).toBe(1); + expect(entries[0].target).toBe(div); + done(); + }); - it('ResizeObserver callback should be able to in different zones which when they were observed', (done) => { - const ResizeObserver = (window as any)['ResizeObserver']; - const div1 = document.createElement('div'); - const div2 = document.createElement('div'); - const zone = Zone.current.fork({ - name: 'observer' - }); - let count = 0; - const observer = new ResizeObserver((entries: any, ob: any) => { - entries.forEach((entry: any) => { - if (entry.target === div1) { - expect(Zone.current.name).toEqual(zone.name); - } else { - expect(Zone.current.name).toEqual(''); - } + zone.run(() => { + observer.observe(div); }); - count ++; - if (count === 2) { - done(); - } - }); - zone.run(() => { - observer.observe(div1); - }); - Zone.root.run(() => { - observer.observe(div2); + document.body.appendChild(div); }); - document.body.appendChild(div1); - document.body.appendChild(div2); - }); - })); + it('ResizeObserver callback should be able to in different zones which when they were observed', + (done) => { + const ResizeObserver = (window as any)['ResizeObserver']; + const div1 = document.createElement('div'); + const div2 = document.createElement('div'); + const zone = Zone.current.fork({name: 'observer'}); + let count = 0; + const observer = new ResizeObserver((entries: any, ob: any) => { + entries.forEach((entry: any) => { + if (entry.target === div1) { + expect(Zone.current.name).toEqual(zone.name); + } else { + expect(Zone.current.name).toEqual(''); + } + }); + count++; + if (count === 2) { + done(); + } + }); + + zone.run(() => { + observer.observe(div1); + }); + Zone.root.run(() => { + observer.observe(div2); + }); + + document.body.appendChild(div1); + document.body.appendChild(div2); + }); + })); xdescribe('getUserMedia', () => { it('navigator.mediaDevices.getUserMedia should in zone',