From 767f5ef2dd17d9725510d48df8f2167620c9d350 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 17 Aug 2016 09:03:46 +0900 Subject: [PATCH 01/24] fix long-stack-trace zone will not render correctly when reject a promise --- dist/long-stack-trace-zone.js | 2 +- dist/long-stack-trace-zone.min.js | 2 +- lib/zone-spec/long-stack-trace.ts | 2 +- test/zone-spec/long-stack-trace-zone.spec.ts | 25 ++++++++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/dist/long-stack-trace-zone.js b/dist/long-stack-trace-zone.js index e4b2886a3..69a76721d 100644 --- a/dist/long-stack-trace-zone.js +++ b/dist/long-stack-trace-zone.js @@ -118,7 +118,7 @@ return parentZoneDelegate.scheduleTask(targetZone, task); }, onHandleError: function (parentZoneDelegate, currentZone, targetZone, error) { - var parentTask = Zone.currentTask; + var parentTask = Zone.currentTask || error.task; if (error instanceof Error && parentTask) { var descriptor = Object.getOwnPropertyDescriptor(error, 'stack'); if (descriptor) { diff --git a/dist/long-stack-trace-zone.min.js b/dist/long-stack-trace-zone.min.js index 3da92814b..e3400056a 100644 --- a/dist/long-stack-trace-zone.min.js +++ b/dist/long-stack-trace-zone.min.js @@ -1 +1 @@ -!function(t){function r(n){if(e[n])return e[n].exports;var a=e[n]={exports:{},id:n,loaded:!1};return t[n].call(a.exports,a,a.exports,r),a.loaded=!0,a.exports}var e={};return r.m=t,r.c=e,r.p="",r(0)}([function(t,exports){"use strict";!function(){function t(){return new Error("STACKTRACE TRACKING")}function r(){try{throw t()}catch(r){return r}}function e(t){return t.stack?t.stack.split(i):[]}function n(t,r){for(var n=e(r),a=0;a0&&(t.push(e((new l).error)),c(t,r-1))}function o(){var t=[];c(t,2);for(var r=t[0],e=t[1],n=0;nthis.longStackTraceLimit&&(c.length=this.longStackTraceLimit),n.data||(n.data={}),n.data[f]=c,t.scheduleTask(e,n)},onHandleError:function(t,r,e,n){var c=Zone.currentTask;if(n instanceof Error&&c){var o=Object.getOwnPropertyDescriptor(n,"stack");if(o){var i=o.get,s=o.value;o={get:function(){return a(c.data&&c.data[f],i?i.apply(this):s)}},Object.defineProperty(n,"stack",o)}else n.stack=a(c.data&&c.data[f],n.stack)}return t.handleError(e,n)}},o()}()}]); \ No newline at end of file +!function(t){function r(n){if(e[n])return e[n].exports;var a=e[n]={exports:{},id:n,loaded:!1};return t[n].call(a.exports,a,a.exports,r),a.loaded=!0,a.exports}var e={};return r.m=t,r.c=e,r.p="",r(0)}([function(t,exports){"use strict";!function(){function t(){return new Error("STACKTRACE TRACKING")}function r(){try{throw t()}catch(r){return r}}function e(t){return t.stack?t.stack.split(i):[]}function n(t,r){for(var n=e(r),a=0;a0&&(t.push(e((new l).error)),c(t,r-1))}function o(){var t=[];c(t,2);for(var r=t[0],e=t[1],n=0;nthis.longStackTraceLimit&&(c.length=this.longStackTraceLimit),n.data||(n.data={}),n.data[f]=c,t.scheduleTask(e,n)},onHandleError:function(t,r,e,n){var c=Zone.currentTask||n.task;if(n instanceof Error&&c){var o=Object.getOwnPropertyDescriptor(n,"stack");if(o){var i=o.get,s=o.value;o={get:function(){return a(c.data&&c.data[f],i?i.apply(this):s)}},Object.defineProperty(n,"stack",o)}else n.stack=a(c.data&&c.data[f],n.stack)}return t.handleError(e,n)}},o()}()}]); \ No newline at end of file diff --git a/lib/zone-spec/long-stack-trace.ts b/lib/zone-spec/long-stack-trace.ts index 67e14ec20..3c0a336ca 100644 --- a/lib/zone-spec/long-stack-trace.ts +++ b/lib/zone-spec/long-stack-trace.ts @@ -85,7 +85,7 @@ onHandleError: function(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: any): any { - const parentTask = Zone.currentTask; + const parentTask = Zone.currentTask || error.task; if (error instanceof Error && parentTask) { let descriptor = Object.getOwnPropertyDescriptor(error, 'stack'); if (descriptor) { diff --git a/test/zone-spec/long-stack-trace-zone.spec.ts b/test/zone-spec/long-stack-trace-zone.spec.ts index 646c0a8d1..dae8e0182 100644 --- a/test/zone-spec/long-stack-trace-zone.spec.ts +++ b/test/zone-spec/long-stack-trace-zone.spec.ts @@ -32,6 +32,31 @@ describe('longStackTraceZone', function () { }, 0); }); }); + + it('should produce long stack traces when reject in promise', function(done) { + lstz.runGuarded(function () { + setTimeout(function () { + setTimeout(function () { + let promise = new Promise(function (resolve, reject) { + setTimeout(function (){ + reject(new Error('Hello Promise')); + }, 0); + }); + promise.then(function() { + fail('should not get here'); + }); + setTimeout(function () { + try { + expect(log[0].split('Elapsed: ').length).toBe(5); + done(); + } catch (e) { + expect(e).toBe(null); + } + }, 0); + }, 0); + }, 0); + }); + }); }); export var __something__; From da482b97019c07f3d2351e6dc5226410ecbe83ff Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 10 Nov 2016 02:40:53 +0900 Subject: [PATCH 02/24] fix issue #484 and #491, patch EventEmitter.once and removeAllListeners --- gulpfile.js | 2 -- lib/common/utils.ts | 49 +++++++++++++++++++++++++++++++++++++--- lib/node/events.ts | 5 +++- test/node/events.spec.ts | 10 +++++++- 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 7836f78c5..85054fac5 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..496d0bbff 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,6 +151,24 @@ 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 { let eventTasks: Task[] = target[EVENT_TASKS]; @@ -247,6 +271,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 +414,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..3e6b19499 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 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/test/node/events.spec.ts b/test/node/events.spec.ts index 5a3fa9906..f010a9da2 100644 --- a/test/node/events.spec.ts +++ b/test/node/events.spec.ts @@ -68,5 +68,13 @@ describe('nodejs EventEmitter', () => { zoneA.run(() => { expect(emitter.listeners('test')).toEqual([]); }); - }) + }); + 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); + }); + }); }); \ No newline at end of file From 4dabc6fecd93487787fa70af43d17d27acddbcd2 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 10 Nov 2016 02:50:49 +0900 Subject: [PATCH 03/24] add event emitter once test case --- test/node/events.spec.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/node/events.spec.ts b/test/node/events.spec.ts index f010a9da2..69d22f151 100644 --- a/test/node/events.spec.ts +++ b/test/node/events.spec.ts @@ -77,4 +77,11 @@ describe('nodejs EventEmitter', () => { expect(emitter.listeners('test').length).toEqual(0); }); }); + it ('should remove once listener properly', () => { + zoneA.run(() => { + emitter.once('test', shouldNotRun); + emitter.removeListener('test', shouldNotRun); + emitter.emit('test'); + }); + }); }); \ No newline at end of file From 46533fa66621892a321ce9ce830def5d02266795 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 16 Nov 2016 22:29:28 +0900 Subject: [PATCH 04/24] prependlistener should add listener at the beginning of the listener array. add test cases for prepend listener and once. --- lib/common/utils.ts | 12 ++++++++---- lib/node/events.ts | 4 ++-- test/node/events.spec.ts | 39 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 496d0bbff..50bd721a4 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -170,24 +170,28 @@ function findAllExistingRegisteredTasks(target: any, name: string, capture: bool 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); } diff --git a/lib/node/events.ts b/lib/node/events.ts index 3e6b19499..3190e1c95 100644 --- a/lib/node/events.ts +++ b/lib/node/events.ts @@ -23,8 +23,8 @@ 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); diff --git a/test/node/events.spec.ts b/test/node/events.spec.ts index 69d22f151..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'); } @@ -69,6 +79,24 @@ describe('nodejs EventEmitter', () => { 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); @@ -77,7 +105,14 @@ describe('nodejs EventEmitter', () => { expect(emitter.listeners('test').length).toEqual(0); }); }); - it ('should remove once listener properly', () => { + 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); From 2fcaeef22955b91095d1b55bdf2344fe74078b75 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 17 Nov 2016 01:08:27 +0900 Subject: [PATCH 05/24] use newest fetch to test --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" } } From c5486f8f1c05bcf34f2451b284526b9103d6e2c3 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Tue, 22 Nov 2016 03:52:12 +0800 Subject: [PATCH 06/24] patch process.nextTick --- lib/node/node.ts | 7 +++++++ test/node/process.spec.ts | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 test/node/process.spec.ts diff --git a/lib/node/node.ts b/lib/node/node.ts index 92f69b6d0..4584fb721 100644 --- a/lib/node/node.ts +++ b/lib/node/node.ts @@ -30,6 +30,13 @@ if (shouldPatchGlobalTimers) { patchTimer(_global, set, clear, 'Immediate'); } +// patch process.nextTick +var nativeNextTick = process.nextTick; +process.nextTick = function() { + var args = arguments; + args[0] = Zone.current.wrap(args[0], 'process.nextTick'); + nativeNextTick.apply(this, args); +} // Crypto let crypto; diff --git a/test/node/process.spec.ts b/test/node/process.spec.ts new file mode 100644 index 000000000..017d59379 --- /dev/null +++ b/test/node/process.spec.ts @@ -0,0 +1,23 @@ +/** + * @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 + */ + +describe('process related test', () => { + let zone, zoneA; + beforeEach(() => { + zone = Zone.current; + zoneA = zone.fork({name: 'A'}); + }); + + it('process.nextTick callback should in zone', function() { + zoneA.run(function() { + process.nextTick(function() { + expect(Zone.current).toEqual(zoneA); + }); + }); + }); +}); \ No newline at end of file From 508b3cfcb97be8ba2f6fa4c56721488c0c4ea582 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 23 Nov 2016 10:59:52 +0800 Subject: [PATCH 07/24] restore ZoneAwareError captureStackTrace function --- lib/zone.ts | 8 ++++++++ test/common/Error.spec.ts | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/lib/zone.ts b/lib/zone.ts index 07eb7171a..14ccf4423 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -1337,6 +1337,14 @@ const Zone: ZoneType = (function(global: any) { }); } + if (NativeError.captureStackTrace) { + Object.defineProperty(ZoneAwareError, 'captureStackTrace', { + value: function(targetObject: Object, constructorOpt?: Function) { + NativeError.captureStackTrace(targetObject, constructorOpt); + } + }); + } + // Now we need to populet the `blacklistedStackFrames` as well as find the // run/runGuraded/runTask frames. This is done by creating a detect zone and then threading // the execution through all of the above methods so that we can look at the stack trace and diff --git a/test/common/Error.spec.ts b/test/common/Error.spec.ts index 9e1e741b5..64ed2d07a 100644 --- a/test/common/Error.spec.ts +++ b/test/common/Error.spec.ts @@ -58,6 +58,11 @@ describe('ZoneAwareError', () => { expect(insideFrames[2]).toMatch(/testFn.*[]]/); } }); + it ('should have all properties from NativeError', () => { + let obj: any = new Object(); + Error.captureStackTrace(obj); + expect(obj.stack).not.toBeUndefined(); + }); }); function getRootZone() { From 7c2a6162556da70c3c6ee6ddb655055969a3a0fc Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 23 Nov 2016 11:20:10 +0800 Subject: [PATCH 08/24] move captureStackTrace test into node --- test/common/Error.spec.ts | 5 ----- test/node/Error.spec.ts | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 test/node/Error.spec.ts diff --git a/test/common/Error.spec.ts b/test/common/Error.spec.ts index 64ed2d07a..9e1e741b5 100644 --- a/test/common/Error.spec.ts +++ b/test/common/Error.spec.ts @@ -58,11 +58,6 @@ describe('ZoneAwareError', () => { expect(insideFrames[2]).toMatch(/testFn.*[]]/); } }); - it ('should have all properties from NativeError', () => { - let obj: any = new Object(); - Error.captureStackTrace(obj); - expect(obj.stack).not.toBeUndefined(); - }); }); function getRootZone() { diff --git a/test/node/Error.spec.ts b/test/node/Error.spec.ts new file mode 100644 index 000000000..e35915915 --- /dev/null +++ b/test/node/Error.spec.ts @@ -0,0 +1,20 @@ +/** + * @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 + */ + +describe('ZoneAwareError', () => { + // If the environment does not supports stack rewrites, then these tests will fail + // and there is no point in running them. + if (!Error['stackRewrite']) return; + + it ('should have all properties from NativeError', () => { + let obj: any = new Object(); + Error.captureStackTrace(obj); + expect(obj.stack).not.toBeUndefined(); + }); +}); + From 39644e5264c50a8decced214c4167290f5b6139f Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 23 Nov 2016 11:31:32 +0800 Subject: [PATCH 09/24] use hasOwnProperty for check captureStackTrace exist or not --- lib/zone.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zone.ts b/lib/zone.ts index 14ccf4423..ffda1aa45 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -1337,7 +1337,7 @@ const Zone: ZoneType = (function(global: any) { }); } - if (NativeError.captureStackTrace) { + if (NativeError.hasOwnProperty('captureStackTrace')) { Object.defineProperty(ZoneAwareError, 'captureStackTrace', { value: function(targetObject: Object, constructorOpt?: Function) { NativeError.captureStackTrace(targetObject, constructorOpt); From 058c6a72fe90d23d4d73a15c4da6a648a5f191c8 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 23 Nov 2016 12:49:24 +0800 Subject: [PATCH 10/24] change var to const --- lib/node/node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/node/node.ts b/lib/node/node.ts index 4584fb721..0054a83db 100644 --- a/lib/node/node.ts +++ b/lib/node/node.ts @@ -31,7 +31,7 @@ if (shouldPatchGlobalTimers) { } // patch process.nextTick -var nativeNextTick = process.nextTick; +const nativeNextTick = process.nextTick; process.nextTick = function() { var args = arguments; args[0] = Zone.current.wrap(args[0], 'process.nextTick'); From 3f3335faa5f630da85763e69f01a678b08ceb747 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 23 Nov 2016 21:27:37 +0900 Subject: [PATCH 11/24] add process.spec.ts into node_tests.ts target --- test/node_tests.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/node_tests.ts b/test/node_tests.ts index f8baf9fc8..08a0ad00e 100644 --- a/test/node_tests.ts +++ b/test/node_tests.ts @@ -7,4 +7,5 @@ */ import './node/events.spec'; -import './node/fs.spec'; \ No newline at end of file +import './node/fs.spec'; +import './node/process.spec'; From 69382146a525ab2c869dd77540b48dba5469402b Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 23 Nov 2016 21:54:18 +0900 Subject: [PATCH 12/24] add done in process.spec.ts --- test/node/process.spec.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/test/node/process.spec.ts b/test/node/process.spec.ts index 017d59379..211f44103 100644 --- a/test/node/process.spec.ts +++ b/test/node/process.spec.ts @@ -7,16 +7,11 @@ */ describe('process related test', () => { - let zone, zoneA; - beforeEach(() => { - zone = Zone.current; - zoneA = zone.fork({name: 'A'}); - }); - - it('process.nextTick callback should in zone', function() { - zoneA.run(function() { + it('process.nextTick callback should in zone', function(done) { + Zone.current.fork({name: 'zoneA'}).run(function() { process.nextTick(function() { - expect(Zone.current).toEqual(zoneA); + expect(Zone.current.name).toEqual('zoneA'); + done(); }); }); }); From 8046b677272aaad75e1ce3c122bee9920a73e427 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 23 Nov 2016 21:54:50 +0900 Subject: [PATCH 13/24] change var to let --- lib/node/node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/node/node.ts b/lib/node/node.ts index 0054a83db..af1349585 100644 --- a/lib/node/node.ts +++ b/lib/node/node.ts @@ -33,7 +33,7 @@ if (shouldPatchGlobalTimers) { // patch process.nextTick const nativeNextTick = process.nextTick; process.nextTick = function() { - var args = arguments; + let args = arguments; args[0] = Zone.current.wrap(args[0], 'process.nextTick'); nativeNextTick.apply(this, args); } From 285e4c0751706c7614b864048e7d316c7d05bb14 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 23 Nov 2016 22:14:19 +0900 Subject: [PATCH 14/24] add nexttick order case --- test/node/process.spec.ts | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/test/node/process.spec.ts b/test/node/process.spec.ts index 211f44103..f967f00a0 100644 --- a/test/node/process.spec.ts +++ b/test/node/process.spec.ts @@ -7,12 +7,31 @@ */ describe('process related test', () => { - it('process.nextTick callback should in zone', function(done) { - Zone.current.fork({name: 'zoneA'}).run(function() { - process.nextTick(function() { + let zoneA, result; + beforeEach(() => { + zoneA = Zone.current.fork({name: 'zoneA'}); + result = []; + }); + it('process.nextTick callback should in zone', (done) => { + zoneA.run(function() { + process.nextTick(() => { expect(Zone.current.name).toEqual('zoneA'); done(); }); }); }); + it('process.nextTick should be treated as microTask', (done) => { + zoneA.run(function() { + setTimeout(() => { + result.push('timeout'); + }, 0); + process.nextTick(() => { + result.push('tick'); + }); + setTimeout(() => { + expect(result).toEqual(['tick', 'timeout']); + done(); + }, 0); + }); + }); }); \ No newline at end of file From bedb76810b5970131d7f7cfe45b4045826228dae Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 24 Nov 2016 01:23:56 +0900 Subject: [PATCH 15/24] add prepareStackTrace callback to ZoneAwareError --- lib/zone.ts | 13 ++++++++++--- test/node/Error.spec.ts | 9 +++++++++ test/node/process.spec.ts | 2 +- test/node_tests.ts | 1 + 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/zone.ts b/lib/zone.ts index ffda1aa45..e093d7f88 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -1264,7 +1264,7 @@ const Zone: ZoneType = (function(global: any) { /// Skip this frame when printing out stack blackList, /// This frame marks zone transition - trasition + transition }; const NativeError = global[__symbol__('Error')] = global.Error; // Store the frames which should be removed from the stack frames @@ -1304,7 +1304,7 @@ const Zone: ZoneType = (function(global: any) { if (frameType === FrameType.blackList) { frames.splice(i, 1); i--; - } else if (frameType === FrameType.trasition) { + } else if (frameType === FrameType.transition) { if (zoneFrame.parent) { // This is the special frame where zone changed. Print and process it accordingly frames[i] += ` [${zoneFrame.parent.zone.name} => ${zoneFrame.zone.name}]`; @@ -1345,6 +1345,13 @@ const Zone: ZoneType = (function(global: any) { }); } + Object.defineProperty(ZoneAwareError, 'prepareStackTrace', { + get: function() { return NativeError.prepareStackTrace; }, + set: function(value) { return NativeError.prepareStackTrace = value; } + }); + + // Now we need to populet the `blacklistedStackFrames` as well as find the + // Now we need to populet the `blacklistedStackFrames` as well as find the // run/runGuraded/runTask frames. This is done by creating a detect zone and then threading // the execution through all of the above methods so that we can look at the stack trace and @@ -1372,7 +1379,7 @@ const Zone: ZoneType = (function(global: any) { // FireFox: Zone.prototype.run@http://localhost:9876/base/build/lib/zone.js:101:24 // Safari: run@http://localhost:9876/base/build/lib/zone.js:101:24 let fnName: string = frame.split('(')[0].split('@')[0]; - let frameType = FrameType.trasition; + let frameType = FrameType.transition; if (fnName.indexOf('ZoneAwareError') !== -1) { zoneAwareFrame = frame; } diff --git a/test/node/Error.spec.ts b/test/node/Error.spec.ts index e35915915..410acdf6a 100644 --- a/test/node/Error.spec.ts +++ b/test/node/Error.spec.ts @@ -16,5 +16,14 @@ describe('ZoneAwareError', () => { Error.captureStackTrace(obj); expect(obj.stack).not.toBeUndefined(); }); + + it ('should support prepareStackTrace', () => { + (Error).prepareStackTrace = function(error, stack) { + return stack; + } + let obj: any = new Object(); + Error.captureStackTrace(obj); + expect(obj.stack[0].getFileName()).not.toBeUndefined(); + }); }); diff --git a/test/node/process.spec.ts b/test/node/process.spec.ts index f967f00a0..2e064b794 100644 --- a/test/node/process.spec.ts +++ b/test/node/process.spec.ts @@ -26,7 +26,7 @@ describe('process related test', () => { result.push('timeout'); }, 0); process.nextTick(() => { - result.push('tick'); + result.push('tick'); }); setTimeout(() => { expect(result).toEqual(['tick', 'timeout']); diff --git a/test/node_tests.ts b/test/node_tests.ts index 08a0ad00e..a70c1c118 100644 --- a/test/node_tests.ts +++ b/test/node_tests.ts @@ -9,3 +9,4 @@ import './node/events.spec'; import './node/fs.spec'; import './node/process.spec'; +import './node/Error.spec'; From 1d3e3d40c6a052fe9819dd21fc464c4c3454150e Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Fri, 25 Nov 2016 00:21:00 +0900 Subject: [PATCH 16/24] fix when EventEmitter removeAllListeners has no event type parameter, should remove all listeners --- lib/common/utils.ts | 8 ++++++- test/node/events.spec.ts | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 50bd721a4..c649ba221 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -280,9 +280,15 @@ export function makeZoneAwareRemoveAllListeners(fnName: string, useCapturingPara const defaultUseCapturing = useCapturingParam ? false : undefined; return function zoneAwareRemoveAllListener(self: any, args: any[]) { + var target = self || _global; + if (args.length === 0) { + // remove all listeners without eventName + target[EVENT_TASKS] = []; + target[symbol](); + return; + } 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 ++) { diff --git a/test/node/events.spec.ts b/test/node/events.spec.ts index 270131683..e066287eb 100644 --- a/test/node/events.spec.ts +++ b/test/node/events.spec.ts @@ -105,6 +105,15 @@ describe('nodejs EventEmitter', () => { expect(emitter.listeners('test').length).toEqual(0); }); }); + it ('should remove All listeners properly even without a type parameter', () => { + zoneA.run(() => { + emitter.on('test', shouldNotRun); + emitter.on('test1', shouldNotRun); + emitter.removeAllListeners(); + expect(emitter.listeners('test').length).toEqual(0); + expect(emitter.listeners('test1').length).toEqual(0); + }); + }); it ('should remove once listener after emit', () => { zoneA.run(() => { emitter.once('test', expectZoneA); @@ -119,4 +128,41 @@ describe('nodejs EventEmitter', () => { emitter.emit('test'); }); }); + it ('should trigger removeListener when remove listener', () => { + zoneA.run(() => { + emitter.on('removeListener', function(type, handler) { + zoneResults.push('remove' + type); + }); + emitter.on('newListener', function(type, handler) { + zoneResults.push('new' + type); + }); + emitter.on('test', shouldNotRun); + emitter.removeListener('test', shouldNotRun); + expect(zoneResults).toEqual(['newtest', 'removetest']); + }); + }); + it ('should trigger removeListener when remove all listeners with eventname ', () => { + zoneA.run(() => { + emitter.on('removeListener', function(type, handler) { + zoneResults.push('remove' + type); + }); + emitter.on('test', shouldNotRun); + emitter.on('test1', expectZoneA); + emitter.removeAllListeners('test'); + expect(zoneResults).toEqual(['removetest']); + expect(emitter.listeners('removeListener').length).toBe(1); + }); + }); + it ('should trigger removeListener when remove all listeners without eventname', () => { + zoneA.run(() => { + emitter.on('removeListener', function(type, handler) { + zoneResults.push('remove' + type); + }); + emitter.on('test', shouldNotRun); + emitter.on('test1', expectZoneA); + emitter.removeAllListeners(); + expect(zoneResults).toEqual(['removetest', 'removetest1']); + expect(emitter.listeners('removeListener').length).toBe(0); + }); + }); }); \ No newline at end of file From e011fd3c6997dd16ae2fa7070985716fbec3fc0d Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Fri, 25 Nov 2016 09:25:20 +0900 Subject: [PATCH 17/24] change some var to let/const, remove unnecessary cancelTask call --- lib/common/utils.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/common/utils.ts b/lib/common/utils.ts index c649ba221..2d3f40545 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -155,7 +155,7 @@ function findAllExistingRegisteredTasks(target: any, name: string, capture: bool const eventTasks: Task[] = target[EVENT_TASKS]; if (eventTasks) { const result = []; - for (var i = eventTasks.length - 1; i >= 0; i --) { + for (let i = eventTasks.length - 1; i >= 0; i --) { const eventTask = eventTasks[i]; const data = eventTask.data; if (data.eventName === name && data.useCapturing === capture) { @@ -280,22 +280,17 @@ export function makeZoneAwareRemoveAllListeners(fnName: string, useCapturingPara const defaultUseCapturing = useCapturingParam ? false : undefined; return function zoneAwareRemoveAllListener(self: any, args: any[]) { - var target = self || _global; + const target = self || _global; if (args.length === 0) { // remove all listeners without eventName target[EVENT_TASKS] = []; target[symbol](); - return; - } - var eventName = args[0]; - var useCapturing = args[1] || defaultUseCapturing; - 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); - } + return this; } + const eventName = args[0]; + const useCapturing = args[1] || defaultUseCapturing; + // call this function just remove the related eventTask from target[EVENT_TASKS] + findAllExistingRegisteredTasks(target, eventName, useCapturing, true); target[symbol](eventName, useCapturing); } } From 5462fcb95ee26a57bb59fc1c42ff7ce3c3cc2bd4 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Fri, 25 Nov 2016 11:02:33 +0900 Subject: [PATCH 18/24] modify testcases --- test/node/events.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/node/events.spec.ts b/test/node/events.spec.ts index e066287eb..ce8c00de2 100644 --- a/test/node/events.spec.ts +++ b/test/node/events.spec.ts @@ -159,9 +159,11 @@ describe('nodejs EventEmitter', () => { zoneResults.push('remove' + type); }); emitter.on('test', shouldNotRun); - emitter.on('test1', expectZoneA); + emitter.on('test1', shouldNotRun); emitter.removeAllListeners(); expect(zoneResults).toEqual(['removetest', 'removetest1']); + expect(emitter.listeners('test').length).toBe(0); + expect(emitter.listeners('test1').length).toBe(0); expect(emitter.listeners('removeListener').length).toBe(0); }); }); From 44a17fc9fe227277fadc2764a3635602113b0aee Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Fri, 25 Nov 2016 13:57:00 +0900 Subject: [PATCH 19/24] remove typo --- lib/common/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 2d3f40545..7a35c3956 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -285,7 +285,7 @@ export function makeZoneAwareRemoveAllListeners(fnName: string, useCapturingPara // remove all listeners without eventName target[EVENT_TASKS] = []; target[symbol](); - return this; + return; } const eventName = args[0]; const useCapturing = args[1] || defaultUseCapturing; From 300b965756a3c5047ba3e9a0104a3fe0bd33dd95 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Fri, 25 Nov 2016 17:34:37 +0900 Subject: [PATCH 20/24] use zone.scheduleMicrotask to patch process.nextTick --- lib/node/node.ts | 34 +++++++++++++++++++++------ test/node/process.spec.ts | 49 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/lib/node/node.ts b/lib/node/node.ts index af1349585..2183c2747 100644 --- a/lib/node/node.ts +++ b/lib/node/node.ts @@ -11,6 +11,7 @@ import './events'; import './fs'; import {patchTimer} from '../common/timers'; +import {patchMethod} from '../common/utils'; const set = 'set'; const clear = 'clear'; @@ -30,13 +31,7 @@ if (shouldPatchGlobalTimers) { patchTimer(_global, set, clear, 'Immediate'); } -// patch process.nextTick -const nativeNextTick = process.nextTick; -process.nextTick = function() { - let args = arguments; - args[0] = Zone.current.wrap(args[0], 'process.nextTick'); - nativeNextTick.apply(this, args); -} +patchNextTick(); // Crypto let crypto; @@ -90,3 +85,28 @@ if (httpClient && httpClient.ClientRequest) { } }; } + +function patchNextTick() { + var setNative = null; + + function scheduleTask(task: Task) { + const args = task.data; + args[0] = function() { + task.invoke.apply(this, arguments); + }; + setNative.apply(process, args); + return task; + } + + setNative = + patchMethod(process, 'nextTick', (delegate: Function) => function(self: any, args: any[]) { + if (typeof args[0] === 'function') { + var zone = Zone.current; + var task = zone.scheduleMicroTask('nextTick', args[0], args, scheduleTask); + return task; + } else { + // cause an error by calling it directly. + return delegate.apply(process, args); + } + }); +} \ No newline at end of file diff --git a/test/node/process.spec.ts b/test/node/process.spec.ts index 2e064b794..7e0119608 100644 --- a/test/node/process.spec.ts +++ b/test/node/process.spec.ts @@ -20,7 +20,7 @@ describe('process related test', () => { }); }); }); - it('process.nextTick should be treated as microTask', (done) => { + it('process.nextTick should be excuted before macroTask and promise', (done) => { zoneA.run(function() { setTimeout(() => { result.push('timeout'); @@ -31,7 +31,52 @@ describe('process related test', () => { setTimeout(() => { expect(result).toEqual(['tick', 'timeout']); done(); - }, 0); + }); }); }); + it ('process.nextTick should be treated as microTask', (done) => { + let zoneTick = Zone.current.fork({ + name: 'zoneTick', + onScheduleTask: (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task): Task => { + result.push( + { + callback: 'scheduleTask', + targetZone: targetZone.name, + task: task.source + }); + return parentZoneDelegate.scheduleTask(targetZone, task); + }, + onInvokeTask: (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task, applyThis?: any, applyArgs?: any): any => { + result.push( + { + callback: 'invokeTask', + targetZone: targetZone.name, + task: task.source + }); + return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs); + } + }); + zoneTick.run(() => { + process.nextTick(() => { + result.push('tick'); + }); + }); + setTimeout(() => { + expect(result.length).toBe(3); + expect(result[0]).toEqual( + { + callback: 'scheduleTask', + targetZone: 'zoneTick', + task: 'nextTick' + }); + expect(result[1]).toEqual( + { + callback: 'invokeTask', + targetZone: 'zoneTick', + task: 'nextTick' + } + ); + done(); + }); + }) }); \ No newline at end of file From d55139fdef71985bbaf92e460497a619518c0ceb Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Fri, 25 Nov 2016 17:47:53 +0900 Subject: [PATCH 21/24] forget use let/const again --- lib/node/node.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/node/node.ts b/lib/node/node.ts index 2183c2747..1c6d6e5d7 100644 --- a/lib/node/node.ts +++ b/lib/node/node.ts @@ -87,7 +87,7 @@ if (httpClient && httpClient.ClientRequest) { } function patchNextTick() { - var setNative = null; + let setNative = null; function scheduleTask(task: Task) { const args = task.data; @@ -101,8 +101,8 @@ function patchNextTick() { setNative = patchMethod(process, 'nextTick', (delegate: Function) => function(self: any, args: any[]) { if (typeof args[0] === 'function') { - var zone = Zone.current; - var task = zone.scheduleMicroTask('nextTick', args[0], args, scheduleTask); + const zone = Zone.current; + const task = zone.scheduleMicroTask('nextTick', args[0], args, scheduleTask); return task; } else { // cause an error by calling it directly. From 12dd13dfec66a4bc480a3ae67cb0d62e5be891d9 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Sun, 27 Nov 2016 22:09:51 +0900 Subject: [PATCH 22/24] add comment to removeAllListeners patch, and remove useCapturing parameter cause it is not needed --- lib/common/utils.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 7a35c3956..a7b893d09 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -284,6 +284,8 @@ export function makeZoneAwareRemoveAllListeners(fnName: string, useCapturingPara if (args.length === 0) { // remove all listeners without eventName target[EVENT_TASKS] = []; + // we don't cancel Task either, because call native eventEmitter.removeAllListeners will + // will do remove listener(cancelTask) for us target[symbol](); return; } @@ -291,7 +293,11 @@ export function makeZoneAwareRemoveAllListeners(fnName: string, useCapturingPara const useCapturing = args[1] || defaultUseCapturing; // call this function just remove the related eventTask from target[EVENT_TASKS] findAllExistingRegisteredTasks(target, eventName, useCapturing, true); - target[symbol](eventName, useCapturing); + // we don't need useCapturing here because useCapturing is just for DOM, and + // removeAllListeners should only be called by node eventEmitter + // and we don't cancel Task either, because call native eventEmitter.removeAllListeners will + // will do remove listener(cancelTask) for us + target[symbol](eventName); } } From c9a18c581fbfd6e64003847c83489b3bb72216c7 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Mon, 5 Dec 2016 20:06:02 +0900 Subject: [PATCH 23/24] update fetch to 2.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6bd716186..873e8e72e 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": "https://github.com/jimmywarting/fetch.git" + "whatwg-fetch": "^2.0.1" } } From cab6d64ed46505e3bee087e65729aee94f7fc630 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Tue, 6 Dec 2016 23:15:25 +0900 Subject: [PATCH 24/24] fix bug add event listener to xhrhttprequest multiple times #527 --- lib/browser/browser.ts | 12 ++++++++++-- test/browser/XMLHttpRequest.spec.ts | 12 ++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/browser/browser.ts b/lib/browser/browser.ts index 79f297dfb..85e6a1618 100644 --- a/lib/browser/browser.ts +++ b/lib/browser/browser.ts @@ -48,6 +48,7 @@ patchXHR(_global); const XHR_TASK = zoneSymbol('xhrTask'); const XHR_SYNC = zoneSymbol('xhrSync'); +const XHR_LISTENER = zoneSymbol('xhrListener'); interface XHROptions extends TaskData { target: any; @@ -63,13 +64,20 @@ function patchXHR(window: any) { function scheduleTask(task: Task) { var data = task.data; - data.target.addEventListener('readystatechange', () => { + // remove existing event listener + var listener = data.target[XHR_LISTENER]; + if (listener) { + data.target.removeEventListener('readystatechange', listener); + } + var newListener = data.target[XHR_LISTENER] = () => { if (data.target.readyState === data.target.DONE) { if (!data.aborted) { task.invoke(); } } - }); + }; + data.target.addEventListener('readystatechange', newListener); + var storedTask: Task = data.target[XHR_TASK]; if (!storedTask) { data.target[XHR_TASK] = task; diff --git a/test/browser/XMLHttpRequest.spec.ts b/test/browser/XMLHttpRequest.spec.ts index be1c63fd2..8cceb9d43 100644 --- a/test/browser/XMLHttpRequest.spec.ts +++ b/test/browser/XMLHttpRequest.spec.ts @@ -182,4 +182,16 @@ describe('XMLHttpRequest', function() { expect(XMLHttpRequest.LOADING).toEqual(3); expect(XMLHttpRequest.DONE).toEqual(4); }); + + it('should work properly when send request multiple times on single xmlRequest instance', function() { + testZone.run(function() { + var req = new XMLHttpRequest(); + req.open('get', '/', true); + req.send(); + req.onloadend = function() { + req.open('get', '/', true); + req.send(); + } + }); + }) });