diff --git a/gulpfile.js b/gulpfile.js index 19b85a249..c384fb36c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -10,10 +10,8 @@ var gulp = require('gulp'); var rollup = require('gulp-rollup'); var rename = require("gulp-rename"); -var rename = require('gulp-rename'); var uglify = require('gulp-uglify'); var pump = require('pump'); -var uglify = require('gulp-uglify'); var path = require('path'); var spawn = require('child_process').spawn; diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 5b2e82fbb..50bd721a4 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -118,11 +118,16 @@ const EVENT_TASKS = zoneSymbol('eventTasks'); const ADD_EVENT_LISTENER = 'addEventListener'; const REMOVE_EVENT_LISTENER = 'removeEventListener'; +interface NestedEventListener { + listener?: EventListenerOrEventListenerObject; +} + +declare type NestedEventListenerOrEventListenerObject = NestedEventListener | EventListener | EventListenerObject; interface ListenerTaskMeta extends TaskData { useCapturing: boolean; eventName: string; - handler: EventListenerOrEventListenerObject; + handler: NestedEventListenerOrEventListenerObject; target: any; name: string; } @@ -134,7 +139,8 @@ function findExistingRegisteredTask( for (let i = 0; i < eventTasks.length; i++) { const eventTask = eventTasks[i]; const data = eventTask.data; - if (data.handler === handler && data.useCapturing === capture && data.eventName === name) { + const listener = data.handler; + if ((data.handler === handler || listener.listener === handler) && data.useCapturing === capture && data.eventName === name) { if (remove) { eventTasks.splice(i, 1); } @@ -145,25 +151,47 @@ function findExistingRegisteredTask( return null; } +function findAllExistingRegisteredTasks(target: any, name: string, capture: boolean, remove: boolean): Task[] { + const eventTasks: Task[] = target[EVENT_TASKS]; + if (eventTasks) { + const result = []; + for (var i = eventTasks.length - 1; i >= 0; i --) { + const eventTask = eventTasks[i]; + const data = eventTask.data; + if (data.eventName === name && data.useCapturing === capture) { + result.push(eventTask); + if (remove) { + eventTasks.splice(i, 1); + } + } + } + return result; + } + return null; +} -function attachRegisteredEvent(target: any, eventTask: Task): void { +function attachRegisteredEvent(target: any, eventTask: Task, isPrepend: boolean): void { let eventTasks: Task[] = target[EVENT_TASKS]; if (!eventTasks) { eventTasks = target[EVENT_TASKS] = []; } - eventTasks.push(eventTask); + if (isPrepend) { + eventTasks.unshift(eventTask); + } else { + eventTasks.push(eventTask); + } } export function makeZoneAwareAddListener( addFnName: string, removeFnName: string, useCapturingParam: boolean = true, - allowDuplicates: boolean = false) { + allowDuplicates: boolean = false, isPrepend: boolean = false) { const addFnSymbol = zoneSymbol(addFnName); const removeFnSymbol = zoneSymbol(removeFnName); const defaultUseCapturing = useCapturingParam ? false : undefined; function scheduleEventListener(eventTask: Task): any { const meta = eventTask.data; - attachRegisteredEvent(meta.target, eventTask); + attachRegisteredEvent(meta.target, eventTask, isPrepend); return meta.target[addFnSymbol](meta.eventName, eventTask.invoke, meta.useCapturing); } @@ -247,6 +275,25 @@ export function makeZoneAwareRemoveListener(fnName: string, useCapturingParam: b }; } +export function makeZoneAwareRemoveAllListeners(fnName: string, useCapturingParam: boolean = true) { + const symbol = zoneSymbol(fnName); + const defaultUseCapturing = useCapturingParam ? false : undefined; + + return function zoneAwareRemoveAllListener(self: any, args: any[]) { + var eventName = args[0]; + var useCapturing = args[1] || defaultUseCapturing; + var target = self || _global; + var eventTasks = findAllExistingRegisteredTasks(target, eventName, useCapturing, true); + if (eventTasks) { + for (var i = 0; i < eventTasks.length; i ++) { + var eventTask = eventTasks[i]; + eventTask.zone.cancelTask(eventTask); + } + } + target[symbol](eventName, useCapturing); + } +} + export function makeZoneAwareListeners(fnName: string) { const symbol = zoneSymbol(fnName); @@ -371,4 +418,4 @@ export function patchMethod( proto[name] = createNamedFn(name, patchFn(delegate, delegateName, name)); } return delegate; -} +} \ No newline at end of file diff --git a/lib/node/events.ts b/lib/node/events.ts index a798e9e10..3190e1c95 100644 --- a/lib/node/events.ts +++ b/lib/node/events.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {makeZoneAwareAddListener, makeZoneAwareListeners, makeZoneAwareRemoveListener, patchMethod} from '../common/utils'; +import {makeZoneAwareAddListener, makeZoneAwareListeners, makeZoneAwareRemoveListener, makeZoneAwareRemoveAllListeners, patchMethod} from '../common/utils'; const callAndReturnFirstParam = (fn: (self: any, args: any[]) => any) => { return (self: any, args: any[]) => { @@ -19,12 +19,14 @@ const callAndReturnFirstParam = (fn: (self: any, args: any[]) => any) => { const EE_ADD_LISTENER = 'addListener'; const EE_PREPEND_LISTENER = 'prependListener'; const EE_REMOVE_LISTENER = 'removeListener'; +const EE_REMOVE_ALL_LISTENER = 'removeAllListeners'; const EE_LISTENERS = 'listeners'; const EE_ON = 'on'; -const zoneAwareAddListener = callAndReturnFirstParam(makeZoneAwareAddListener(EE_ADD_LISTENER, EE_REMOVE_LISTENER, false, true)); -const zoneAwarePrependListener = callAndReturnFirstParam(makeZoneAwareAddListener(EE_PREPEND_LISTENER, EE_REMOVE_LISTENER, false, true)); +const zoneAwareAddListener = callAndReturnFirstParam(makeZoneAwareAddListener(EE_ADD_LISTENER, EE_REMOVE_LISTENER, false, true, false)); +const zoneAwarePrependListener = callAndReturnFirstParam(makeZoneAwareAddListener(EE_PREPEND_LISTENER, EE_REMOVE_LISTENER, false, true, true)); const zoneAwareRemoveListener = callAndReturnFirstParam(makeZoneAwareRemoveListener(EE_REMOVE_LISTENER, false)); +const zoneAwareRemoveAllListeners = callAndReturnFirstParam(makeZoneAwareRemoveAllListeners(EE_REMOVE_ALL_LISTENER, false)); const zoneAwareListeners = makeZoneAwareListeners(EE_LISTENERS); export function patchEventEmitterMethods(obj: any): boolean { @@ -32,6 +34,7 @@ export function patchEventEmitterMethods(obj: any): boolean { patchMethod(obj, EE_ADD_LISTENER, () => zoneAwareAddListener); patchMethod(obj, EE_PREPEND_LISTENER, () => zoneAwarePrependListener); patchMethod(obj, EE_REMOVE_LISTENER, () => zoneAwareRemoveListener); + patchMethod(obj, EE_REMOVE_ALL_LISTENER, () => zoneAwareRemoveAllListeners); patchMethod(obj, EE_LISTENERS, () => zoneAwareListeners); obj[EE_ON] = obj[EE_ADD_LISTENER]; return true; diff --git a/package.json b/package.json index 160ab5ede..7c88da39a 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,6 @@ "ts-loader": "^0.6.0", "tslint": "^3.15.1", "typescript": "^2.0.2", - "whatwg-fetch": "^1.0.0" + "whatwg-fetch": "https://github.com/jimmywarting/fetch.git" } } diff --git a/test/node/events.spec.ts b/test/node/events.spec.ts index 5a3fa9906..270131683 100644 --- a/test/node/events.spec.ts +++ b/test/node/events.spec.ts @@ -9,7 +9,7 @@ import {EventEmitter} from 'events'; describe('nodejs EventEmitter', () => { - let zone, zoneA, zoneB, emitter, expectZoneACount; + let zone, zoneA, zoneB, emitter, expectZoneACount, zoneResults; beforeEach(() => { zone = Zone.current; zoneA = zone.fork({name: 'A'}); @@ -17,6 +17,8 @@ describe('nodejs EventEmitter', () => { emitter = new EventEmitter(); expectZoneACount = 0; + + zoneResults = []; }); function expectZoneA(value) { @@ -25,6 +27,14 @@ describe('nodejs EventEmitter', () => { expect(value).toBe('test value'); } + function listenerA() { + zoneResults.push('A'); + } + + function listenerB() { + zoneResults.push('B'); + } + function shouldNotRun() { fail('this listener should not run'); } @@ -68,5 +78,45 @@ describe('nodejs EventEmitter', () => { zoneA.run(() => { expect(emitter.listeners('test')).toEqual([]); }); - }) + }); + it ('should prepend listener by order', () => { + zoneA.run(() => { + emitter.on('test', listenerA); + emitter.on('test', listenerB); + expect(emitter.listeners('test')).toEqual([listenerA, listenerB]); + emitter.emit('test'); + expect(zoneResults).toEqual(['A', 'B']); + zoneResults = []; + + emitter.removeAllListeners('test'); + + emitter.on('test', listenerA); + emitter.prependListener('test', listenerB); + expect(emitter.listeners('test')).toEqual([listenerB, listenerA]); + emitter.emit('test'); + expect(zoneResults).toEqual(['B', 'A']); + }); + }); + it ('should remove All listeners properly', () => { + zoneA.run(() => { + emitter.on('test', expectZoneA); + emitter.on('test', expectZoneA); + emitter.removeAllListeners('test'); + expect(emitter.listeners('test').length).toEqual(0); + }); + }); + it ('should remove once listener after emit', () => { + zoneA.run(() => { + emitter.once('test', expectZoneA); + emitter.emit('test', 'test value'); + expect(emitter.listeners('test').length).toEqual(0); + }); + }); + it ('should remove once listener properly before listener triggered', () => { + zoneA.run(() => { + emitter.once('test', shouldNotRun); + emitter.removeListener('test', shouldNotRun); + emitter.emit('test'); + }); + }); }); \ No newline at end of file