Skip to content

Commit

Permalink
fix(platform-browser): run BLACK_LISTED_EVENTS outside of ngZone
Browse files Browse the repository at this point in the history
  • Loading branch information
JiaLiPassion committed Sep 2, 2017
1 parent 6064d7b commit eaab36c
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 5 deletions.
1 change: 1 addition & 0 deletions karma-js.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module.exports = function(config) {
'node_modules/zone.js/dist/fake-async-test.js',

// Including systemjs because it defines `__eval`, which produces correct stack traces.
'test-events.js',
'shims_for_IE.js',
'node_modules/systemjs/dist/system.src.js',
{pattern: 'node_modules/rxjs/**', included: false, watched: false, served: true},
Expand Down
33 changes: 28 additions & 5 deletions packages/platform-browser/src/dom/events/dom_events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ const ANGULAR = 'ANGULAR';
const NATIVE_ADD_LISTENER = 'addEventListener';
const NATIVE_REMOVE_LISTENER = 'removeEventListener';

const blackListedMap: {[key: string]: string} = Zone && Zone[__symbol__('BLACK_LISTED_EVENTS')];

const isBlackListedEvent = function(eventName: string) {
if (!blackListedMap) {
return false;
}
return blackListedMap.hasOwnProperty(eventName);
};

interface TaskData {
zone: any;
handler: Function;
Expand Down Expand Up @@ -86,20 +95,34 @@ export class DomEventsPlugin extends EventManagerPlugin {
let callback: EventListener = handler as EventListener;
// if zonejs is loaded and current zone is not ngZone
// we keep Zone.current on target for later restoration.
if (zoneJsLoaded && !NgZone.isInAngularZone()) {
if (zoneJsLoaded && (!NgZone.isInAngularZone() || isBlackListedEvent(eventName))) {
let symbolName = symbolNames[eventName];
if (!symbolName) {
symbolName = symbolNames[eventName] = __symbol__(ANGULAR + eventName + FALSE);
}
let taskDatas: TaskData[] = (element as any)[symbolName];
const listenerRegistered = taskDatas && taskDatas.length > 0;
const globalListenerRegistered = taskDatas && taskDatas.length > 0;
if (!taskDatas) {
taskDatas = (element as any)[symbolName] = [];
}
if (taskDatas.filter(taskData => taskData.handler === callback).length === 0) {
taskDatas.push({zone: Zone.current, handler: callback});

const zone = isBlackListedEvent(eventName) ? Zone.root : Zone.current;
if (taskDatas.length === 0) {
taskDatas.push({zone: zone, handler: callback});
} else {
let callbackRegistered = false;
for (let i = 0; i < taskDatas.length; i++) {
if (taskDatas[i].handler === callback) {
callbackRegistered = true;
break;
}
}
if (!callbackRegistered) {
taskDatas.push({zone: zone, handler: callback});
}
}
if (!listenerRegistered) {

if (!globalListenerRegistered) {
element[ADD_EVENT_LISTENER](eventName, globalListener, false);
}
} else {
Expand Down
90 changes: 90 additions & 0 deletions packages/platform-browser/test/dom/events/event_manager_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,96 @@ export function main() {
getDOM().dispatchEvent(element, dispatchedEvent);
expect(receivedEvent).toBe(null);
});

it('should keep zone when addEventListener multiple times', () => {
const Zone = (window as any)['Zone'];

const element = el('<div><div></div></div>');
getDOM().appendChild(doc.body, element);
const dispatchedEvent = getDOM().createMouseEvent('click');
let receivedEvents: any[] /** TODO #9100 */ = [];
let receivedZones: any[] = [];
const handler1 = (e: any /** TODO #9100 */) => {
receivedEvents.push(e);
receivedZones.push(Zone.current.name);
};
const handler2 = (e: any /** TODO #9100 */) => {
receivedEvents.push(e);
receivedZones.push(Zone.current.name);
};
const manager = new EventManager([domEventPlugin], new FakeNgZone());

let remover1 = null;
let remover2 = null;
Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler1); });
Zone.root.fork({name: 'test'}).run(() => {
remover2 = manager.addEventListener(element, 'click', handler2);
});
getDOM().dispatchEvent(element, dispatchedEvent);
expect(receivedEvents).toEqual([dispatchedEvent, dispatchedEvent]);
expect(receivedZones).toEqual([Zone.root.name, 'test']);

receivedEvents = [];
remover1 && remover1();
remover2 && remover2();
getDOM().dispatchEvent(element, dispatchedEvent);
expect(receivedEvents).toEqual([]);
});

it('should only add same callback once when addEventListener', () => {
const Zone = (window as any)['Zone'];

const element = el('<div><div></div></div>');
getDOM().appendChild(doc.body, element);
const dispatchedEvent = getDOM().createMouseEvent('click');
let receivedEvents: any[] /** TODO #9100 */ = [];
let receivedZones: any[] = [];
const handler = (e: any /** TODO #9100 */) => {
receivedEvents.push(e);
receivedZones.push(Zone.current.name);
};
const manager = new EventManager([domEventPlugin], new FakeNgZone());

let remover1 = null;
let remover2 = null;
Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler); });
Zone.root.fork({name: 'test'}).run(() => {
remover2 = manager.addEventListener(element, 'click', handler);
});
getDOM().dispatchEvent(element, dispatchedEvent);
expect(receivedEvents).toEqual([dispatchedEvent]);
expect(receivedZones).toEqual([Zone.root.name]);

receivedEvents = [];
remover1 && remover1();
remover2 && remover2();
getDOM().dispatchEvent(element, dispatchedEvent);
expect(receivedEvents).toEqual([]);
});

it('should run blackListedEvents handler outside of ngZone', () => {
const Zone = (window as any)['Zone'];
const element = el('<div><div></div></div>');
getDOM().appendChild(doc.body, element);
const dispatchedEvent = getDOM().createMouseEvent('scroll');
let receivedEvent: any /** TODO #9100 */ = null;
let receivedZone: any = null;
const handler = (e: any /** TODO #9100 */) => {
receivedEvent = e;
receivedZone = Zone.current;
};
const manager = new EventManager([domEventPlugin], new FakeNgZone());

let remover = manager.addEventListener(element, 'scroll', handler);
getDOM().dispatchEvent(element, dispatchedEvent);
expect(receivedEvent).toBe(dispatchedEvent);
expect(receivedZone.name).toBe(Zone.root.name);

receivedEvent = null;
remover && remover();
getDOM().dispatchEvent(element, dispatchedEvent);
expect(receivedEvent).toBe(null);
});
});
}

Expand Down
11 changes: 11 additions & 0 deletions test-events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

Zone[Zone.__symbol__('BLACK_LISTED_EVENTS')] = {
scroll: 'scroll'
};

0 comments on commit eaab36c

Please sign in to comment.