From b29fa056da72e006f3dd856135caf7e4db618b3e Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Sun, 4 Nov 2018 17:52:30 +0900 Subject: [PATCH 01/12] staging changes --- lib/common/utils.ts | 12 ++++++------ lib/zone.ts | 9 ++++++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 77554e524..636b14234 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -82,9 +82,9 @@ export function patchPrototype(prototype: any, fnNames: string[]) { const patched: any = function() { return delegate.apply(this, bindArguments(arguments, source + '.' + name)); }; - attachOriginToPatched(patched, delegate); return patched; })(delegate); + attachOriginToPatched(prototype, name, delegate); } } } @@ -315,7 +315,7 @@ export function patchClass(className: string) { }; // attach original delegate to patched function - attachOriginToPatched(_global[className], OriginalClass); + attachOriginToPatched(_global, className, OriginalClass); const instance = new OriginalClass(function() {}); @@ -336,7 +336,7 @@ export function patchClass(className: string) { // keep callback in wrapped function so we can // use it in Function.prototype.toString to return // the native one. - attachOriginToPatched(this[originalInstanceKey][prop], fn); + attachOriginToPatched(this[originalInstanceKey], prop, fn); } else { this[originalInstanceKey][prop] = fn; } @@ -411,7 +411,7 @@ export function patchMethod( proto[name] = function() { return patchDelegate(this, arguments as any); }; - attachOriginToPatched(proto[name], delegate); + attachOriginToPatched(proto, name, delegate); if (shouldCopySymbolProperties) { copySymbolProperties(delegate, proto[name]); } @@ -483,8 +483,8 @@ export function patchMicroTask( }); } -export function attachOriginToPatched(patched: Function, original: any) { - (patched as any)[zoneSymbol('OriginalDelegate')] = original; +export function attachOriginToPatched(patchedTarget: any, prop: string, original: any) { + patchedTarget[prop][zoneSymbol('OriginalDelegate')] = original; } let isDetectedIEOrEdge = false; diff --git a/lib/zone.ts b/lib/zone.ts index 74fb76536..4f76b0167 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -697,7 +697,13 @@ const Zone: ZoneType = (function(global: any) { } else if (!global['__Zone_disable_' + name]) { const perfName = 'Zone:' + name; mark(perfName); - patches[name] = fn(global, Zone, _api); + if (_mode === 'normal') { + patches[name] = fn(global, Zone, _api); + } else { + patches[name] = function () { + fn(global, Zone, _api); + }; + } performanceMeasure(perfName, perfName); } } @@ -1355,6 +1361,7 @@ const Zone: ZoneType = (function(global: any) { let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)}; let _currentTask: Task|null = null; let _numberOfNestedTaskFrames = 0; + let _mode: 'lazy' | 'normal' = 'normal'; function noop() {} From 3aee06f8f8622147df4c77884a12daa6efef81ff Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Sun, 4 Nov 2018 21:19:55 +0900 Subject: [PATCH 02/12] add reload/unload methods --- lib/browser/register-element.ts | 2 +- lib/common/events.ts | 8 +++--- lib/zone.ts | 50 +++++++++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/lib/browser/register-element.ts b/lib/browser/register-element.ts index f590f0c60..ba89fca21 100644 --- a/lib/browser/register-element.ts +++ b/lib/browser/register-element.ts @@ -38,7 +38,7 @@ function patchCallbacks(target: any, targetName: string, method: string, callbac return nativeDelegate.call(target, name, opts, options); }; - attachOriginToPatched(target[method], nativeDelegate); + attachOriginToPatched(target, method, nativeDelegate); } export function registerElementPatch(_global: any) { diff --git a/lib/common/events.ts b/lib/common/events.ts index b2a02f69c..5953edf7b 100644 --- a/lib/common/events.ts +++ b/lib/common/events.ts @@ -621,13 +621,13 @@ export function patchEventTarget( }; // for native toString patch - attachOriginToPatched(proto[ADD_EVENT_LISTENER], nativeAddEventListener); - attachOriginToPatched(proto[REMOVE_EVENT_LISTENER], nativeRemoveEventListener); + attachOriginToPatched(proto, ADD_EVENT_LISTENER, nativeAddEventListener); + attachOriginToPatched(proto, REMOVE_EVENT_LISTENER, nativeRemoveEventListener); if (nativeRemoveAllListeners) { - attachOriginToPatched(proto[REMOVE_ALL_LISTENERS_EVENT_LISTENER], nativeRemoveAllListeners); + attachOriginToPatched(proto, REMOVE_ALL_LISTENERS_EVENT_LISTENER, nativeRemoveAllListeners); } if (nativeListeners) { - attachOriginToPatched(proto[LISTENERS_EVENT_LISTENER], nativeListeners); + attachOriginToPatched(proto, LISTENERS_EVENT_LISTENER, nativeListeners); } return true; } diff --git a/lib/zone.ts b/lib/zone.ts index 4f76b0167..75b8d1af1 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -700,7 +700,7 @@ const Zone: ZoneType = (function(global: any) { if (_mode === 'normal') { patches[name] = fn(global, Zone, _api); } else { - patches[name] = function () { + patches[name] = function() { fn(global, Zone, _api); }; } @@ -708,6 +708,29 @@ const Zone: ZoneType = (function(global: any) { } } + static __load() { + Object.keys(patches).forEach(key => patches[key]()); + patchLoaded = true; + } + + static __register_patched_delegate(proto: any, property: string, origin: any) { + delegates.push({proto: proto, property: property, patched: proto[property], origin: origin}); + } + + static __reloadAll() { + delegates.forEach(delegate => { + delegate.proto[delegate.property] = delegate.patched; + }); + patchLoaded = true; + } + + static __unloadAll() { + delegates.forEach(delegate => { + delegate.proto[delegate.property] = delegate.origin; + }); + patchLoaded = false; + } + public get parent(): AmbientZone|null { return this._parent; } @@ -765,11 +788,18 @@ const Zone: ZoneType = (function(global: any) { public run(callback: Function, applyThis?: any, applyArgs?: any[], source?: string): any; public run( callback: (...args: any[]) => T, applyThis?: any, applyArgs?: any[], source?: string): T { + if (!patchLoaded && _mode === 'lazy') { + Zone.__reloadAll(); + } _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; try { return this._zoneDelegate.invoke(this, callback, applyThis, applyArgs, source); } finally { _currentZoneFrame = _currentZoneFrame.parent!; + if (_mode === 'lazy' && _currentZoneFrame.zone && + _currentZoneFrame.zone.name === '') { + Zone.__unloadAll(); + } } } @@ -777,6 +807,9 @@ const Zone: ZoneType = (function(global: any) { public runGuarded( callback: (...args: any[]) => T, applyThis: any = null, applyArgs?: any[], source?: string) { + if (!patchLoaded && _mode === 'lazy') { + Zone.__reloadAll(); + } _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; try { try { @@ -788,6 +821,10 @@ const Zone: ZoneType = (function(global: any) { } } finally { _currentZoneFrame = _currentZoneFrame.parent!; + if (_mode === 'lazy' && _currentZoneFrame.zone && + _currentZoneFrame.zone.name === '') { + Zone.__unloadAll(); + } } } @@ -811,6 +848,9 @@ const Zone: ZoneType = (function(global: any) { task.runCount++; const previousTask = _currentTask; _currentTask = task; + if (!patchLoaded && _mode === 'lazy') { + Zone.__reloadAll(); + } _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; try { if (task.type == macroTask && task.data && !task.data.isPeriodic) { @@ -838,6 +878,10 @@ const Zone: ZoneType = (function(global: any) { } _currentZoneFrame = _currentZoneFrame.parent!; _currentTask = previousTask; + if (_mode === 'lazy' && _currentZoneFrame.zone && + _currentZoneFrame.zone.name === '') { + Zone.__unloadAll(); + } } } @@ -1337,6 +1381,8 @@ const Zone: ZoneType = (function(global: any) { eventTask: 'eventTask' = 'eventTask'; const patches: {[key: string]: any} = {}; + const delegates: {proto: any, property: string, patched: any, origin: any}[] = []; + let patchLoaded = false; const _api: _ZonePrivate = { symbol: __symbol__, currentZoneFrame: () => _currentZoneFrame, @@ -1361,7 +1407,7 @@ const Zone: ZoneType = (function(global: any) { let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)}; let _currentTask: Task|null = null; let _numberOfNestedTaskFrames = 0; - let _mode: 'lazy' | 'normal' = 'normal'; + let _mode: 'lazy'|'normal' = 'normal'; function noop() {} From 56842fc898140090dffc311895c5cd5c58a8be9b Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Mon, 5 Nov 2018 01:11:08 +0900 Subject: [PATCH 03/12] feat(core): scoped zone --- lib/browser/browser.ts | 3 +- lib/browser/webapis-user-media.ts | 2 ++ lib/browser/websocket.ts | 1 + lib/common/promise.ts | 1 + lib/zone.ts | 2 ++ test/browser/performance.spec.ts | 59 +++++++++++++++++++++++++++++++ test/browser_entry_point.ts | 1 + 7 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 test/browser/performance.spec.ts diff --git a/lib/browser/browser.ts b/lib/browser/browser.ts index 0d7ab2458..a1e22d3fe 100644 --- a/lib/browser/browser.ts +++ b/lib/browser/browser.ts @@ -12,7 +12,7 @@ import {findEventTasks} from '../common/events'; import {patchTimer} from '../common/timers'; -import {bindArguments, patchClass, patchMacroTask, patchMethod, patchOnProperties, patchPrototype, scheduleMacroTaskWithCurrentZone, ZONE_SYMBOL_ADD_EVENT_LISTENER, ZONE_SYMBOL_REMOVE_EVENT_LISTENER, zoneSymbol} from '../common/utils'; +import {attachOriginToPatched, bindArguments, patchClass, patchMacroTask, patchMethod, patchOnProperties, patchPrototype, scheduleMacroTaskWithCurrentZone, ZONE_SYMBOL_ADD_EVENT_LISTENER, ZONE_SYMBOL_REMOVE_EVENT_LISTENER, zoneSymbol} from '../common/utils'; import {propertyPatch} from './define-property'; import {eventTargetPatch, patchEvent} from './event-target'; @@ -23,6 +23,7 @@ Zone.__load_patch('util', (global: any, Zone: ZoneType, api: _ZonePrivate) => { api.patchOnProperties = patchOnProperties; api.patchMethod = patchMethod; api.bindArguments = bindArguments; + api.attachOriginToPatched = attachOriginToPatched; }); Zone.__load_patch('timers', (global: any) => { diff --git a/lib/browser/webapis-user-media.ts b/lib/browser/webapis-user-media.ts index 6d2cf5b98..c99ff054b 100644 --- a/lib/browser/webapis-user-media.ts +++ b/lib/browser/webapis-user-media.ts @@ -15,6 +15,8 @@ Zone.__load_patch('getUserMedia', (global: any, Zone: any, api: _ZonePrivate) => } let navigator = global['navigator']; if (navigator && navigator.getUserMedia) { + const native = navigator.getUserMedia; navigator.getUserMedia = wrapFunctionArgs(navigator.getUserMedia); + api.attachOriginToPatched(navigator, 'getUserMedia', native); } }); diff --git a/lib/browser/websocket.ts b/lib/browser/websocket.ts index ab3cc0d41..e6db663fb 100644 --- a/lib/browser/websocket.ts +++ b/lib/browser/websocket.ts @@ -55,6 +55,7 @@ export function apply(api: _ZonePrivate, _global: any) { }; const globalWebSocket = _global['WebSocket']; + api.attachOriginToPatched(_global, 'WebSocket', WS); for (const prop in WS) { globalWebSocket[prop] = WS[prop]; } diff --git a/lib/common/promise.ts b/lib/common/promise.ts index aa1f44757..6baade78a 100644 --- a/lib/common/promise.ts +++ b/lib/common/promise.ts @@ -445,6 +445,7 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr } global['Promise'] = ZoneAwarePromise; + api.attachOriginToPatched(global, 'Promise', NativePromise); const symbolThenPatched = __symbol__('thenPatched'); diff --git a/lib/zone.ts b/lib/zone.ts index 75b8d1af1..506251efa 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -329,6 +329,7 @@ interface _ZonePrivate { patchFn: (delegate: Function, delegateName: string, name: string) => (self: any, args: any[]) => any) => Function | null; bindArguments: (args: any[], source: string) => any[]; + attachOriginToPatched: (patchedTarget: any, prop: string, original: any) => void; } /** @internal */ @@ -1403,6 +1404,7 @@ const Zone: ZoneType = (function(global: any) { nativeMicroTaskQueuePromise = NativePromise.resolve(0); } }, + attachOriginToPatched: (patchedTarget: any, prop: string, original: any) => noop }; let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)}; let _currentTask: Task|null = null; diff --git a/test/browser/performance.spec.ts b/test/browser/performance.spec.ts new file mode 100644 index 000000000..7a4d018c4 --- /dev/null +++ b/test/browser/performance.spec.ts @@ -0,0 +1,59 @@ +/** + * @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('Performance', () => { + function mark(name: string) { + performance && performance['mark'] && performance['mark'](name); + } + function performanceMeasure(name: string, label: string) { + performance && performance['measure'] && performance['measure'](name, label); + } + function getEntriesByName(name: string) { + return performance && performance['getEntriesByName'] && + performance['getEntriesByName'](name).filter(m => m.entryType === 'measure'); + } + describe('Lazy mode', () => { + const rootZone = Zone.root; + const noop = function() {}; + const normal = function() { + for (let i = 0; i < 10000; i++) { + let j = i * i; + } + }; + const times = 100000; + describe('root zone performance benchmark', () => { + it('noop function', () => { + const normal = 'rootZone normal noop'; + mark(normal); + for (let i = 0; i < times; i++) { + rootZone.run(noop); + } + performanceMeasure(normal, normal); + const normalEntries = getEntriesByName(normal); + const normalDuration = normalEntries[0].duration; + const normalAverage = normalDuration / times; + console.log(`${normal} ${times} times cost: ${normalDuration}, ${normalAverage} average`); + const lazy = 'rootZone lazy noop'; + mark(lazy); + (Zone as any)._mode = 'lazy'; + for (let i = 0; i < 100000; i++) { + rootZone.run(noop); + } + performanceMeasure(lazy, lazy); + const lazyEntries = getEntriesByName(lazy); + const lazyDuration = lazyEntries[0].duration; + const lazyAverage = lazyDuration / times; + console.log(`${lazy} ${times} times cost: ${lazyDuration}, ${lazyAverage}`); + (Zone as any)._mode = 'normal'; + const diff = lazyDuration - normalDuration; + const diffAverage = diff / times; + console.log(`diff running ${times} times is: ${diff}, average is ${diffAverage}`); + }); + }); + }); +}); diff --git a/test/browser_entry_point.ts b/test/browser_entry_point.ts index d594c89fd..d2367ebd3 100644 --- a/test/browser_entry_point.ts +++ b/test/browser_entry_point.ts @@ -27,3 +27,4 @@ import './browser/Worker.spec'; import './mocha-patch.spec'; import './jasmine-patch.spec'; import './extra/cordova.spec'; +import './browser/performance.spec'; From 09e276591c64ea82edae083c0b14182de78a16c0 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Mon, 3 Dec 2018 22:58:23 +0900 Subject: [PATCH 04/12] pass test --- karma-build-jasmine-lazy.conf.js | 13 ++++ lib/browser/browser.ts | 2 +- lib/common/promise.ts | 3 +- lib/common/utils.ts | 17 ++++- lib/zone.ts | 104 +++++++++++++++++++++----- package.json | 2 + test/browser-lazy-zone-setup.ts | 10 +++ test/browser/lazy.spec.ts | 76 +++++++++++++++++++ test/browser/performance.spec.ts | 10 +-- test/browser_lazy_zone_entry_point.ts | 10 +++ test/main.ts | 6 +- 11 files changed, 225 insertions(+), 28 deletions(-) create mode 100644 karma-build-jasmine-lazy.conf.js create mode 100644 test/browser-lazy-zone-setup.ts create mode 100644 test/browser/lazy.spec.ts create mode 100644 test/browser_lazy_zone_entry_point.ts diff --git a/karma-build-jasmine-lazy.conf.js b/karma-build-jasmine-lazy.conf.js new file mode 100644 index 000000000..15d934411 --- /dev/null +++ b/karma-build-jasmine-lazy.conf.js @@ -0,0 +1,13 @@ +/** + * @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 + */ + +module.exports = function(config) { + require('./karma-base-jasmine.conf.js')(config); + config.client.setup = 'browser-lazy-zone-setup'; + config.client.entrypoint = 'browser_lazy_zone_entry_point'; +}; diff --git a/lib/browser/browser.ts b/lib/browser/browser.ts index a1e22d3fe..2f222b82e 100644 --- a/lib/browser/browser.ts +++ b/lib/browser/browser.ts @@ -24,7 +24,7 @@ Zone.__load_patch('util', (global: any, Zone: ZoneType, api: _ZonePrivate) => { api.patchMethod = patchMethod; api.bindArguments = bindArguments; api.attachOriginToPatched = attachOriginToPatched; -}); +}, true); Zone.__load_patch('timers', (global: any) => { const set = 'set'; diff --git a/lib/common/promise.ts b/lib/common/promise.ts index 6baade78a..fb4ad5e28 100644 --- a/lib/common/promise.ts +++ b/lib/common/promise.ts @@ -445,7 +445,7 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr } global['Promise'] = ZoneAwarePromise; - api.attachOriginToPatched(global, 'Promise', NativePromise); + api.attachOriginToPatched(global, ZONE_AWARE_PROMISE, NativePromise); const symbolThenPatched = __symbol__('thenPatched'); @@ -470,6 +470,7 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr return wrapped.then(onResolve, onReject); }; (Ctor as any)[symbolThenPatched] = true; + api.attachOriginToPatched(Ctor.prototype, 'then', originalThen); } api.patchThen = patchThen; diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 636b14234..610931b67 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -159,13 +159,23 @@ const wrapFn = function(event: Event) { }; export function patchProperty(obj: any, prop: string, prototype?: any) { - let desc = ObjectGetOwnPropertyDescriptor(obj, prop); - if (!desc && prototype) { + let desc: PropertyDescriptor | null = null; + const originalDesc = ObjectGetOwnPropertyDescriptor(obj, prop); + if (!originalDesc && prototype) { // when patch window object, use prototype to check prop exist or not const prototypeDesc = ObjectGetOwnPropertyDescriptor(prototype, prop); if (prototypeDesc) { desc = {enumerable: true, configurable: true}; } + } else if (originalDesc) { + desc = { + configurable: originalDesc.configurable, + enumerable: originalDesc.enumerable, + value: originalDesc.value, + writable: originalDesc.writable, + get: originalDesc.get, + set: originalDesc.set + }; } // if the descriptor not exists or is not configurable // just return @@ -262,6 +272,8 @@ export function patchProperty(obj: any, prop: string, prototype?: any) { ObjectDefineProperty(obj, prop, desc); obj[onPropPatchedSymbol] = true; + + (Zone as any).__register_patched_delegate(obj, prop, originalDesc, true); } export function patchOnProperties(obj: any, properties: string[]|null, prototype?: any) { @@ -485,6 +497,7 @@ export function patchMicroTask( export function attachOriginToPatched(patchedTarget: any, prop: string, original: any) { patchedTarget[prop][zoneSymbol('OriginalDelegate')] = original; + (Zone as any).__register_patched_delegate(patchedTarget, prop, original); } let isDetectedIEOrEdge = false; diff --git a/lib/zone.ts b/lib/zone.ts index 506251efa..b2daef101 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -154,6 +154,7 @@ interface Zone { * @returns {any} The value for the key, or `undefined` if not found. */ get(key: string): any; + /** * Returns a Zone which defines a `key`. * @@ -163,6 +164,7 @@ interface Zone { * @returns {Zone} The Zone which defines the `key`, `null` if not found. */ getZoneWith(key: string): Zone|null; + /** * Used to create a child zone. * @@ -170,6 +172,7 @@ interface Zone { * @returns {Zone} A new child zone. */ fork(zoneSpec: ZoneSpec): Zone; + /** * Wraps a callback function in a new function which will properly restore the current zone upon * invocation. @@ -184,6 +187,7 @@ interface Zone { * @returns {function(): *} A function which will invoke the `callback` through [Zone.runGuarded]. */ wrap(callback: F, source: string): F; + /** * Invokes a function in a given zone. * @@ -196,6 +200,7 @@ interface Zone { * @returns {any} Value from the `callback` function. */ run(callback: Function, applyThis?: any, applyArgs?: any[], source?: string): T; + /** * Invokes a function in a given zone and catches any exceptions. * @@ -211,6 +216,7 @@ interface Zone { * @returns {any} Value from the `callback` function. */ runGuarded(callback: Function, applyThis?: any, applyArgs?: any[], source?: string): T; + /** * Execute the Task by restoring the [Zone.currentTask] in the Task's zone. * @@ -303,7 +309,7 @@ interface ZoneType { root: Zone; /** @internal */ - __load_patch(name: string, fn: _PatchFn): void; + __load_patch(name: string, fn: _PatchFn, preload?: boolean): void; /** @internal */ __symbol__(name: string): string; @@ -488,14 +494,22 @@ interface ZoneSpec { */ interface ZoneDelegate { zone: Zone; + fork(targetZone: Zone, zoneSpec: ZoneSpec): Zone; + intercept(targetZone: Zone, callback: Function, source: string): Function; + invoke(targetZone: Zone, callback: Function, applyThis?: any, applyArgs?: any[], source?: string): any; + handleError(targetZone: Zone, error: any): boolean; + scheduleTask(targetZone: Zone, task: Task): Task; + invokeTask(targetZone: Zone, task: Task, applyThis?: any, applyArgs?: any[]): any; + cancelTask(targetZone: Zone, task: Task): any; + hasTask(targetZone: Zone, isEmpty: HasTaskState): void; } @@ -637,12 +651,15 @@ type AmbientZoneDelegate = ZoneDelegate; const Zone: ZoneType = (function(global: any) { const performance: {mark(name: string): void; measure(name: string, label: string): void;} = global['performance']; + function mark(name: string) { performance && performance['mark'] && performance['mark'](name); } + function performanceMeasure(name: string, label: string) { performance && performance['measure'] && performance['measure'](name, label); } + mark('Zone'); if (global['Zone']) { // if global['Zone'] already exists (maybe zone.js was already loaded or @@ -666,7 +683,7 @@ const Zone: ZoneType = (function(global: any) { static __symbol__: (name: string) => string = __symbol__; static assertZonePatched() { - if (global['Promise'] !== patches['ZoneAwarePromise']) { + if (patchLoaded && global['Promise'] !== patchedPromise) { throw new Error( 'Zone.js has detected that ZoneAwarePromise `(window|global).Promise` ' + 'has been overwritten.\n' + @@ -692,17 +709,25 @@ const Zone: ZoneType = (function(global: any) { return _currentTask; } - static __load_patch(name: string, fn: _PatchFn): void { + static get mode(): 'lazy'|'normal' { + return _mode; + } + + static set mode(newMode: 'lazy'|'normal') { + _mode = newMode; + } + + static __load_patch(name: string, fn: _PatchFn, preload = false): void { if (patches.hasOwnProperty(name)) { throw Error('Already loaded patch: ' + name); } else if (!global['__Zone_disable_' + name]) { const perfName = 'Zone:' + name; mark(perfName); - if (_mode === 'normal') { + if (_mode === 'normal' || preload) { patches[name] = fn(global, Zone, _api); } else { patches[name] = function() { - fn(global, Zone, _api); + return fn(global, Zone, _api); }; } performanceMeasure(perfName, perfName); @@ -710,24 +735,66 @@ const Zone: ZoneType = (function(global: any) { } static __load() { - Object.keys(patches).forEach(key => patches[key]()); + if (patchLoaded) { + return; + } + Object.keys(patches).forEach(key => { + const loadPatchFn = patches[key]; + if (typeof loadPatchFn === 'function') { + const r = loadPatchFn(); + if (key === 'ZoneAwarePromise') { + patchedPromise = r; + } + } + }); patchLoaded = true; } - static __register_patched_delegate(proto: any, property: string, origin: any) { - delegates.push({proto: proto, property: property, patched: proto[property], origin: origin}); + static __register_patched_delegate( + proto: any, property: string, origin: any, isPropertyDesc = false) { + if (!isPropertyDesc) { + delegates.push({ + proto: proto, + property: property, + patched: proto[property], + origin: origin, + isPropertyDesc: isPropertyDesc + }); + } else { + delegates.push({ + proto: proto, + property: property, + patched: Object.getOwnPropertyDescriptor(proto, property), + origin: origin, + isPropertyDesc: isPropertyDesc + }); + } } static __reloadAll() { + if (patchLoaded) { + return; + } delegates.forEach(delegate => { - delegate.proto[delegate.property] = delegate.patched; + if (delegate.isPropertyDesc) { + Object.defineProperty(delegate.proto, delegate.property, delegate.patched); + } else { + delegate.proto[delegate.property] = delegate.patched; + } }); patchLoaded = true; } static __unloadAll() { + if (!patchLoaded) { + return; + } delegates.forEach(delegate => { - delegate.proto[delegate.property] = delegate.origin; + if (delegate.isPropertyDesc) { + Object.defineProperty(delegate.proto, delegate.property, delegate.origin); + } else { + delegate.proto[delegate.property] = delegate.origin; + } }); patchLoaded = false; } @@ -797,8 +864,7 @@ const Zone: ZoneType = (function(global: any) { return this._zoneDelegate.invoke(this, callback, applyThis, applyArgs, source); } finally { _currentZoneFrame = _currentZoneFrame.parent!; - if (_mode === 'lazy' && _currentZoneFrame.zone && - _currentZoneFrame.zone.name === '') { + if (_mode === 'lazy' && _currentZoneFrame.zone && !_currentZoneFrame.zone.parent) { Zone.__unloadAll(); } } @@ -822,8 +888,7 @@ const Zone: ZoneType = (function(global: any) { } } finally { _currentZoneFrame = _currentZoneFrame.parent!; - if (_mode === 'lazy' && _currentZoneFrame.zone && - _currentZoneFrame.zone.name === '') { + if (_mode === 'lazy' && _currentZoneFrame.zone && !_currentZoneFrame.zone.parent) { Zone.__unloadAll(); } } @@ -879,8 +944,7 @@ const Zone: ZoneType = (function(global: any) { } _currentZoneFrame = _currentZoneFrame.parent!; _currentTask = previousTask; - if (_mode === 'lazy' && _currentZoneFrame.zone && - _currentZoneFrame.zone.name === '') { + if (_mode === 'lazy' && _currentZoneFrame.zone && !_currentZoneFrame.zone.parent) { Zone.__unloadAll(); } } @@ -1330,6 +1394,8 @@ const Zone: ZoneType = (function(global: any) { if (!nativeMicroTaskQueuePromise) { if (global[symbolPromise]) { nativeMicroTaskQueuePromise = global[symbolPromise].resolve(0); + } else if (_mode === 'lazy' && patchedPromise !== global['Promise']) { + nativeMicroTaskQueuePromise = global['Promise'].resolve(0); } } if (nativeMicroTaskQueuePromise) { @@ -1382,7 +1448,9 @@ const Zone: ZoneType = (function(global: any) { eventTask: 'eventTask' = 'eventTask'; const patches: {[key: string]: any} = {}; - const delegates: {proto: any, property: string, patched: any, origin: any}[] = []; + let patchedPromise: any; + const delegates: + {proto: any, property: string, patched: any, origin: any, isPropertyDesc: boolean}[] = []; let patchLoaded = false; const _api: _ZonePrivate = { symbol: __symbol__, @@ -1409,7 +1477,7 @@ const Zone: ZoneType = (function(global: any) { let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)}; let _currentTask: Task|null = null; let _numberOfNestedTaskFrames = 0; - let _mode: 'lazy'|'normal' = 'normal'; + let _mode: 'lazy'|'normal' = global['__zone_symbol__load_mode'] || 'normal'; function noop() {} diff --git a/package.json b/package.json index 479947dae..550802c09 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "format": "gulp format:enforce", "karma-jasmine": "karma start karma-build-jasmine.conf.js", "karma-jasmine:es6": "karma start karma-build-jasmine.es6.conf.js", + "karma-jasmine:lazy": "karma start karma-build-jasmine-lazy.conf.js", "karma-jasmine:phantomjs": "karma start karma-build-jasmine-phantomjs.conf.js --single-run", "karma-jasmine:single": "karma start karma-build-jasmine.conf.js --single-run", "karma-jasmine:autoclose": "npm run karma-jasmine:single && npm run ws-client", @@ -40,6 +41,7 @@ "tslint": "tslint -c tslint.json 'lib/**/*.ts'", "test": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine\"", "test:es6": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine:es6\"", + "test:lazy": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run karma-jasmine:lazy\"", "test:phantomjs": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine:phantomjs\"", "test:phantomjs-single": "npm run tsc && concurrently \"npm run ws-server\" \"npm run karma-jasmine-phantomjs:autoclose\"", "test:single": "npm run tsc && concurrently \"npm run ws-server\" \"npm run karma-jasmine:autoclose\"", diff --git a/test/browser-lazy-zone-setup.ts b/test/browser-lazy-zone-setup.ts new file mode 100644 index 000000000..0f3b2fbbb --- /dev/null +++ b/test/browser-lazy-zone-setup.ts @@ -0,0 +1,10 @@ +/** + * @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 + */ +if (typeof window !== 'undefined') { + (window as any)['__zone_symbol__load_mode'] = 'lazy'; +} diff --git a/test/browser/lazy.spec.ts b/test/browser/lazy.spec.ts new file mode 100644 index 000000000..ead743d04 --- /dev/null +++ b/test/browser/lazy.spec.ts @@ -0,0 +1,76 @@ +/** + * @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('lazy', function() { + const targets = [ + {proto: 'Window', property: '__zone_symbol__ZoneAwarePromise'}, + {proto: 'Promise', property: 'then'}, + {proto: 'AbortController', property: 'abort'}, + {proto: 'Window', property: 'setTimeout'}, + {proto: 'Window', property: 'clearTimeout'}, + {proto: 'Window', property: 'setInterval'}, + {proto: 'Window', property: 'clearInterval'}, + {proto: 'Window', property: 'requestAnimationFrame'}, + {proto: 'Window', property: 'cancelAnimationFrame'}, + {proto: 'Window', property: 'webkitRequestAnimationFrame'}, + {proto: 'Window', property: 'webkitCancelAnimationFrame'}, + {proto: 'Window', property: 'alert'}, + {proto: 'Window', property: 'prompt'}, + {proto: 'Window', property: 'confirm'}, + {proto: 'Event', property: 'stopImmediatePropagation'}, + {proto: 'EventTarget', property: 'addEventListener'}, + {proto: 'EventTarget', property: 'removeEventListener'}, + {proto: 'Window', property: 'MutationObserver'}, + {proto: 'Window', property: 'WebKitMutationObserver'}, + {proto: 'Window', property: 'IntersectionObserver'}, + {proto: 'Window', property: 'FileReader'}, + {proto: 'HTMLDocument', property: 'registerElement'}, + {proto: 'CustomElementRegistry', property: 'define'}, + {proto: 'HTMLCanvasElement', property: 'toBlob'}, + {proto: 'XMLHttpRequest', property: 'open'}, + {proto: 'XMLHttpRequest', property: 'send'}, + {proto: 'XMLHttpRequest', property: 'abort'}, + {proto: 'Geolocation', property: 'getCurrentPosition'}, + {proto: 'Geolocation', property: 'watchPosition'} + ]; + + it('before patch should use native delegate', (done: DoneFn) => { + const zone = Zone.current.fork({name: 'zone'}); + zone.run(() => { + Promise.resolve().then(() => { + expect(Zone.current.name).not.toEqual(zone.name); + }); + setTimeout(() => { + expect(Zone.current.name).not.toEqual(zone.name); + }); + const interval = setInterval(() => { + expect(Zone.current.name).not.toEqual(zone.name); + clearInterval(interval); + }, 100); + requestAnimationFrame(() => { + expect(Zone.current.name).not.toEqual(zone.name); + }); + const btn = document.createElement('button'); + document.body.appendChild(btn); + btn.addEventListener('click', () => { + expect(Zone.current.name).not.toEqual(zone.name); + }); + const evt = document.createEvent('MouseEvent'); + evt.initEvent('click', true, true); + Zone.root.run(() => { + btn.dispatchEvent(evt); + }); + setTimeout(done, 500); + }); + }); + + it('after load should use patched delegate', + (done: DoneFn) => { + + }); +}); diff --git a/test/browser/performance.spec.ts b/test/browser/performance.spec.ts index 7a4d018c4..27e79bb65 100644 --- a/test/browser/performance.spec.ts +++ b/test/browser/performance.spec.ts @@ -17,7 +17,7 @@ describe('Performance', () => { return performance && performance['getEntriesByName'] && performance['getEntriesByName'](name).filter(m => m.entryType === 'measure'); } - describe('Lazy mode', () => { + fdescribe('Lazy mode', () => { const rootZone = Zone.root; const noop = function() {}; const normal = function() { @@ -25,7 +25,7 @@ describe('Performance', () => { let j = i * i; } }; - const times = 100000; + const times = 10000; describe('root zone performance benchmark', () => { it('noop function', () => { const normal = 'rootZone normal noop'; @@ -40,8 +40,8 @@ describe('Performance', () => { console.log(`${normal} ${times} times cost: ${normalDuration}, ${normalAverage} average`); const lazy = 'rootZone lazy noop'; mark(lazy); - (Zone as any)._mode = 'lazy'; - for (let i = 0; i < 100000; i++) { + (Zone as any).mode = 'lazy'; + for (let i = 0; i < times; i++) { rootZone.run(noop); } performanceMeasure(lazy, lazy); @@ -49,7 +49,7 @@ describe('Performance', () => { const lazyDuration = lazyEntries[0].duration; const lazyAverage = lazyDuration / times; console.log(`${lazy} ${times} times cost: ${lazyDuration}, ${lazyAverage}`); - (Zone as any)._mode = 'normal'; + (Zone as any).mode = 'normal'; const diff = lazyDuration - normalDuration; const diffAverage = diff / times; console.log(`diff running ${times} times is: ${diff}, average is ${diffAverage}`); diff --git a/test/browser_lazy_zone_entry_point.ts b/test/browser_lazy_zone_entry_point.ts new file mode 100644 index 000000000..4072f9889 --- /dev/null +++ b/test/browser_lazy_zone_entry_point.ts @@ -0,0 +1,10 @@ +/** + * @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 + */ + +// List all tests here: +import './browser/lazy.spec'; diff --git a/test/main.ts b/test/main.ts index 049f9ba6f..6a9351be7 100644 --- a/test/main.ts +++ b/test/main.ts @@ -15,6 +15,7 @@ declare const __karma__: { __karma__.loaded = function() {}; let entryPoint = 'browser_entry_point'; +let setup = 'browser-zone-setup'; if (typeof __karma__ !== 'undefined') { (window as any)['__Zone_Error_BlacklistedStackFrames_policy'] = @@ -22,6 +23,9 @@ if (typeof __karma__ !== 'undefined') { if ((__karma__ as any).config.entrypoint) { entryPoint = (__karma__ as any).config.entrypoint; } + if ((__karma__ as any).config.setup) { + setup = (__karma__ as any).config.setup; + } } else if (typeof process !== 'undefined') { (window as any)['__Zone_Error_BlacklistedStackFrames_policy'] = process.env.errorpolicy; if (process.env.entrypoint) { @@ -47,7 +51,7 @@ if ((window as any)[(Zone as any).__symbol__('setTimeout')]) { } else { // this means that Zone has not patched the browser yet, which means we must be running in // build mode and need to load the browser patch. - browserPatchedPromise = System.import('/base/build/test/browser-zone-setup').then(() => { + browserPatchedPromise = System.import('/base/build/test/' + setup).then(() => { let testFrameworkPatch = typeof (window as any).Mocha !== 'undefined' ? '/base/build/lib/mocha/mocha' : '/base/build/lib/jasmine/jasmine'; From 4c8addf8bde4c92e5da4e78f387678dad087ce92 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Wed, 12 Dec 2018 11:20:44 +0900 Subject: [PATCH 05/12] add karma-jasmine config --- karma-build-jasmine-lazy.conf.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/karma-build-jasmine-lazy.conf.js b/karma-build-jasmine-lazy.conf.js index 15d934411..f25516592 100644 --- a/karma-build-jasmine-lazy.conf.js +++ b/karma-build-jasmine-lazy.conf.js @@ -7,7 +7,7 @@ */ module.exports = function(config) { - require('./karma-base-jasmine.conf.js')(config); - config.client.setup = 'browser-lazy-zone-setup'; + config.client.preload = 'build/test/browser-lazy-zone-setup.js'; + require('./karma-build-jasmine.conf.js')(config); config.client.entrypoint = 'browser_lazy_zone_entry_point'; }; From 633a41c92e4080d314cb2672379d2f75177e3fb1 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Wed, 12 Dec 2018 11:21:52 +0900 Subject: [PATCH 06/12] staging --- karma-build.conf.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/karma-build.conf.js b/karma-build.conf.js index aa2d3113c..3d77a1778 100644 --- a/karma-build.conf.js +++ b/karma-build.conf.js @@ -7,7 +7,11 @@ */ module.exports = function(config) { + var preload = config.client.preload; require('./karma-base.conf.js')(config); + if (preload) { + config.files.push(preload); + } config.files.push('build/test/wtf_mock.js'); config.files.push('build/test/test_fake_polyfill.js'); config.files.push('build/lib/zone.js'); From 666a3f551e486fbd3380847a6988f22b020e8a30 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Wed, 12 Dec 2018 16:48:28 +0900 Subject: [PATCH 07/12] remove focus --- lib/common/utils.ts | 2 +- test/browser/performance.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 610931b67..dd31ef36d 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -159,7 +159,7 @@ const wrapFn = function(event: Event) { }; export function patchProperty(obj: any, prop: string, prototype?: any) { - let desc: PropertyDescriptor | null = null; + let desc: PropertyDescriptor|null = null; const originalDesc = ObjectGetOwnPropertyDescriptor(obj, prop); if (!originalDesc && prototype) { // when patch window object, use prototype to check prop exist or not diff --git a/test/browser/performance.spec.ts b/test/browser/performance.spec.ts index 27e79bb65..216e19eeb 100644 --- a/test/browser/performance.spec.ts +++ b/test/browser/performance.spec.ts @@ -17,7 +17,7 @@ describe('Performance', () => { return performance && performance['getEntriesByName'] && performance['getEntriesByName'](name).filter(m => m.entryType === 'measure'); } - fdescribe('Lazy mode', () => { + describe('Lazy mode', () => { const rootZone = Zone.root; const noop = function() {}; const normal = function() { From 9a2ea32ecf7228ef7e7687d71c2437c88e77eee5 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Thu, 13 Dec 2018 00:18:39 +0900 Subject: [PATCH 08/12] update logic and test --- karma-build-jasmine-lazy.conf.js | 1 + lib/browser/browser.ts | 2 +- lib/common/fetch.ts | 3 +- lib/common/promise.ts | 19 ++++-- lib/zone.ts | 47 ++++---------- test/browser/lazy.spec.ts | 69 ++++++++++---------- test/browser/performance.spec.ts | 104 ++++++++++++++++--------------- test/main.ts | 14 +++-- 8 files changed, 129 insertions(+), 130 deletions(-) diff --git a/karma-build-jasmine-lazy.conf.js b/karma-build-jasmine-lazy.conf.js index f25516592..67f8522a4 100644 --- a/karma-build-jasmine-lazy.conf.js +++ b/karma-build-jasmine-lazy.conf.js @@ -10,4 +10,5 @@ module.exports = function(config) { config.client.preload = 'build/test/browser-lazy-zone-setup.js'; require('./karma-build-jasmine.conf.js')(config); config.client.entrypoint = 'browser_lazy_zone_entry_point'; + config.client.notPatchTestFramework = true; }; diff --git a/lib/browser/browser.ts b/lib/browser/browser.ts index 2f222b82e..a1e22d3fe 100644 --- a/lib/browser/browser.ts +++ b/lib/browser/browser.ts @@ -24,7 +24,7 @@ Zone.__load_patch('util', (global: any, Zone: ZoneType, api: _ZonePrivate) => { api.patchMethod = patchMethod; api.bindArguments = bindArguments; api.attachOriginToPatched = attachOriginToPatched; -}, true); +}); Zone.__load_patch('timers', (global: any) => { const set = 'set'; diff --git a/lib/common/fetch.ts b/lib/common/fetch.ts index 5fda7a5ab..790a93ac9 100644 --- a/lib/common/fetch.ts +++ b/lib/common/fetch.ts @@ -25,6 +25,7 @@ Zone.__load_patch('fetch', (global: any, Zone: ZoneType, api: _ZonePrivate) => { signal.abortController = abortController; return abortController; }; + global['AbortController'].prototype = OriginalAbortController.prototype; abortNative = api.patchMethod( OriginalAbortController.prototype, 'abort', (delegate: Function) => (self: any, args: any) => { @@ -97,4 +98,4 @@ Zone.__load_patch('fetch', (global: any, Zone: ZoneType, api: _ZonePrivate) => { } }); }; -}); \ No newline at end of file +}); diff --git a/lib/common/promise.ts b/lib/common/promise.ts index fb4ad5e28..105b37b23 100644 --- a/lib/common/promise.ts +++ b/lib/common/promise.ts @@ -408,12 +408,18 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr const NativePromise = global[symbolPromise] = global['Promise']; const ZONE_AWARE_PROMISE = Zone.__symbol__('ZoneAwarePromise'); - let desc = ObjectGetOwnPropertyDescriptor(global, 'Promise'); - if (!desc || desc.configurable) { - desc && delete desc.writable; - desc && delete desc.value; - if (!desc) { + let desc = null; + const originalDesc = ObjectGetOwnPropertyDescriptor(global, 'Promise'); + if (!originalDesc || originalDesc.configurable) { + if (!originalDesc) { desc = {configurable: true, enumerable: true}; + } else { + desc = { + configurable: originalDesc.configurable, + enumerable: originalDesc.enumerable, + get: originalDesc.get, + set: originalDesc.set + }; } desc.get = function() { // if we already set ZoneAwarePromise, use patched one @@ -442,10 +448,11 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr }; ObjectDefineProperty(global, 'Promise', desc); + (Zone as any).__register_patched_delegate(global, 'Promise', originalDesc, true); } global['Promise'] = ZoneAwarePromise; - api.attachOriginToPatched(global, ZONE_AWARE_PROMISE, NativePromise); + api.attachOriginToPatched(global, 'Promise', NativePromise); const symbolThenPatched = __symbol__('thenPatched'); diff --git a/lib/zone.ts b/lib/zone.ts index b2daef101..205194294 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -683,7 +683,7 @@ const Zone: ZoneType = (function(global: any) { static __symbol__: (name: string) => string = __symbol__; static assertZonePatched() { - if (patchLoaded && global['Promise'] !== patchedPromise) { + if (global['Promise'] !== patchedPromise) { throw new Error( 'Zone.js has detected that ZoneAwarePromise `(window|global).Promise` ' + 'has been overwritten.\n' + @@ -717,39 +717,17 @@ const Zone: ZoneType = (function(global: any) { _mode = newMode; } - static __load_patch(name: string, fn: _PatchFn, preload = false): void { + static __load_patch(name: string, fn: _PatchFn): void { if (patches.hasOwnProperty(name)) { throw Error('Already loaded patch: ' + name); } else if (!global['__Zone_disable_' + name]) { const perfName = 'Zone:' + name; mark(perfName); - if (_mode === 'normal' || preload) { - patches[name] = fn(global, Zone, _api); - } else { - patches[name] = function() { - return fn(global, Zone, _api); - }; - } + patches[name] = fn(global, Zone, _api); performanceMeasure(perfName, perfName); } } - static __load() { - if (patchLoaded) { - return; - } - Object.keys(patches).forEach(key => { - const loadPatchFn = patches[key]; - if (typeof loadPatchFn === 'function') { - const r = loadPatchFn(); - if (key === 'ZoneAwarePromise') { - patchedPromise = r; - } - } - }); - patchLoaded = true; - } - static __register_patched_delegate( proto: any, property: string, origin: any, isPropertyDesc = false) { if (!isPropertyDesc) { @@ -772,7 +750,7 @@ const Zone: ZoneType = (function(global: any) { } static __reloadAll() { - if (patchLoaded) { + if (monkeyPatched) { return; } delegates.forEach(delegate => { @@ -782,11 +760,11 @@ const Zone: ZoneType = (function(global: any) { delegate.proto[delegate.property] = delegate.patched; } }); - patchLoaded = true; + monkeyPatched = true; } static __unloadAll() { - if (!patchLoaded) { + if (!monkeyPatched) { return; } delegates.forEach(delegate => { @@ -796,7 +774,7 @@ const Zone: ZoneType = (function(global: any) { delegate.proto[delegate.property] = delegate.origin; } }); - patchLoaded = false; + monkeyPatched = false; } public get parent(): AmbientZone|null { @@ -856,7 +834,7 @@ const Zone: ZoneType = (function(global: any) { public run(callback: Function, applyThis?: any, applyArgs?: any[], source?: string): any; public run( callback: (...args: any[]) => T, applyThis?: any, applyArgs?: any[], source?: string): T { - if (!patchLoaded && _mode === 'lazy') { + if (!monkeyPatched && _mode === 'lazy') { Zone.__reloadAll(); } _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; @@ -874,7 +852,7 @@ const Zone: ZoneType = (function(global: any) { public runGuarded( callback: (...args: any[]) => T, applyThis: any = null, applyArgs?: any[], source?: string) { - if (!patchLoaded && _mode === 'lazy') { + if (!monkeyPatched && _mode === 'lazy') { Zone.__reloadAll(); } _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; @@ -914,7 +892,7 @@ const Zone: ZoneType = (function(global: any) { task.runCount++; const previousTask = _currentTask; _currentTask = task; - if (!patchLoaded && _mode === 'lazy') { + if (!monkeyPatched && _mode === 'lazy') { Zone.__reloadAll(); } _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; @@ -1451,7 +1429,7 @@ const Zone: ZoneType = (function(global: any) { let patchedPromise: any; const delegates: {proto: any, property: string, patched: any, origin: any, isPropertyDesc: boolean}[] = []; - let patchLoaded = false; + let monkeyPatched = true; const _api: _ZonePrivate = { symbol: __symbol__, currentZoneFrame: () => _currentZoneFrame, @@ -1472,7 +1450,8 @@ const Zone: ZoneType = (function(global: any) { nativeMicroTaskQueuePromise = NativePromise.resolve(0); } }, - attachOriginToPatched: (patchedTarget: any, prop: string, original: any) => noop + attachOriginToPatched: (patchedTarget: any, prop: string, original: any) => + Zone.__register_patched_delegate(patchedTarget, prop, original) }; let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)}; let _currentTask: Task|null = null; diff --git a/test/browser/lazy.spec.ts b/test/browser/lazy.spec.ts index ead743d04..aec2e4848 100644 --- a/test/browser/lazy.spec.ts +++ b/test/browser/lazy.spec.ts @@ -8,7 +8,7 @@ describe('lazy', function() { const targets = [ - {proto: 'Window', property: '__zone_symbol__ZoneAwarePromise'}, + {proto: 'Window', property: 'Promise'}, {proto: 'Promise', property: 'then'}, {proto: 'AbortController', property: 'abort'}, {proto: 'Window', property: 'setTimeout'}, @@ -29,7 +29,7 @@ describe('lazy', function() { {proto: 'Window', property: 'WebKitMutationObserver'}, {proto: 'Window', property: 'IntersectionObserver'}, {proto: 'Window', property: 'FileReader'}, - {proto: 'HTMLDocument', property: 'registerElement'}, + {proto: 'Document', property: 'registerElement'}, {proto: 'CustomElementRegistry', property: 'define'}, {proto: 'HTMLCanvasElement', property: 'toBlob'}, {proto: 'XMLHttpRequest', property: 'open'}, @@ -39,38 +39,41 @@ describe('lazy', function() { {proto: 'Geolocation', property: 'watchPosition'} ]; - it('before patch should use native delegate', (done: DoneFn) => { - const zone = Zone.current.fork({name: 'zone'}); - zone.run(() => { - Promise.resolve().then(() => { - expect(Zone.current.name).not.toEqual(zone.name); - }); - setTimeout(() => { - expect(Zone.current.name).not.toEqual(zone.name); - }); - const interval = setInterval(() => { - expect(Zone.current.name).not.toEqual(zone.name); - clearInterval(interval); - }, 100); - requestAnimationFrame(() => { - expect(Zone.current.name).not.toEqual(zone.name); - }); - const btn = document.createElement('button'); - document.body.appendChild(btn); - btn.addEventListener('click', () => { - expect(Zone.current.name).not.toEqual(zone.name); - }); - const evt = document.createEvent('MouseEvent'); - evt.initEvent('click', true, true); - Zone.root.run(() => { - btn.dispatchEvent(evt); - }); - setTimeout(done, 500); + function checkDelegates(isNative: boolean) { + targets.forEach(t => { + const protoName = t.proto; + const property = t.property; + let proto = null; + if (protoName === 'Window') { + proto = window; + } else if (protoName === 'Document') { + proto = document; + } else if (protoName === 'CustomElementRegistry') { + proto = window['customElements']; + } else { + proto = (window as any)[protoName] && (window as any)[protoName].prototype; + } + if (proto) { + const native = proto[Zone.__symbol__(property)]; + console.log('check target', protoName, property); + if (isNative) { + expect(proto[property]).toBe(native); + } else { + expect(proto[property]).not.toBe(native); + } + } }); - }); + } - it('after load should use patched delegate', - (done: DoneFn) => { + it('before patch should use native delegate', (done: DoneFn) => { + (Zone as any).__unloadAll(); + checkDelegates(true); + done(); + }); - }); + it('after load should use patched delegate', (done: DoneFn) => { + (Zone as any).__reloadAll(); + checkDelegates(false); + done(); + }); }); diff --git a/test/browser/performance.spec.ts b/test/browser/performance.spec.ts index 216e19eeb..08b9ddfd2 100644 --- a/test/browser/performance.spec.ts +++ b/test/browser/performance.spec.ts @@ -5,55 +5,57 @@ * 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 */ +import {ifEnvSupports} from '../test-util'; -describe('Performance', () => { - function mark(name: string) { - performance && performance['mark'] && performance['mark'](name); - } - function performanceMeasure(name: string, label: string) { - performance && performance['measure'] && performance['measure'](name, label); - } - function getEntriesByName(name: string) { - return performance && performance['getEntriesByName'] && - performance['getEntriesByName'](name).filter(m => m.entryType === 'measure'); - } - describe('Lazy mode', () => { - const rootZone = Zone.root; - const noop = function() {}; - const normal = function() { - for (let i = 0; i < 10000; i++) { - let j = i * i; - } - }; - const times = 10000; - describe('root zone performance benchmark', () => { - it('noop function', () => { - const normal = 'rootZone normal noop'; - mark(normal); - for (let i = 0; i < times; i++) { - rootZone.run(noop); - } - performanceMeasure(normal, normal); - const normalEntries = getEntriesByName(normal); - const normalDuration = normalEntries[0].duration; - const normalAverage = normalDuration / times; - console.log(`${normal} ${times} times cost: ${normalDuration}, ${normalAverage} average`); - const lazy = 'rootZone lazy noop'; - mark(lazy); - (Zone as any).mode = 'lazy'; - for (let i = 0; i < times; i++) { - rootZone.run(noop); - } - performanceMeasure(lazy, lazy); - const lazyEntries = getEntriesByName(lazy); - const lazyDuration = lazyEntries[0].duration; - const lazyAverage = lazyDuration / times; - console.log(`${lazy} ${times} times cost: ${lazyDuration}, ${lazyAverage}`); - (Zone as any).mode = 'normal'; - const diff = lazyDuration - normalDuration; - const diffAverage = diff / times; - console.log(`diff running ${times} times is: ${diff}, average is ${diffAverage}`); - }); - }); - }); -}); +describe('Performance', ifEnvSupports('performance', () => { + function mark(name: string) { + performance && performance['mark'] && performance['mark'](name); + } + function performanceMeasure(name: string, label: string) { + performance && performance['measure'] && performance['measure'](name, label); + } + function getEntriesByName(name: string) { + return performance && performance['getEntriesByName'] && + performance['getEntriesByName'](name).filter(m => m.entryType === 'measure'); + } + describe('Lazy mode', () => { + const rootZone = Zone.root; + const noop = function() {}; + const normal = function() { + for (let i = 0; i < 10000; i++) { + let j = i * i; + } + }; + const times = 10000; + describe('root zone performance benchmark', () => { + it('noop function', () => { + const normal = 'rootZone normal noop'; + mark(normal); + for (let i = 0; i < times; i++) { + rootZone.run(noop); + } + performanceMeasure(normal, normal); + const normalEntries = getEntriesByName(normal); + const normalDuration = normalEntries[0].duration; + const normalAverage = normalDuration / times; + console.log( + `${normal} ${times} times cost: ${normalDuration}, ${normalAverage} average`); + const lazy = 'rootZone lazy noop'; + mark(lazy); + (Zone as any).mode = 'lazy'; + for (let i = 0; i < times; i++) { + rootZone.run(noop); + } + performanceMeasure(lazy, lazy); + const lazyEntries = getEntriesByName(lazy); + const lazyDuration = lazyEntries[0].duration; + const lazyAverage = lazyDuration / times; + console.log(`${lazy} ${times} times cost: ${lazyDuration}, ${lazyAverage}`); + (Zone as any).mode = 'normal'; + const diff = lazyDuration - normalDuration; + const diffAverage = diff / times; + console.log(`diff running ${times} times is: ${diff}, average is ${diffAverage}`); + }); + }); + }); + })); diff --git a/test/main.ts b/test/main.ts index 6a9351be7..67f08b0e8 100644 --- a/test/main.ts +++ b/test/main.ts @@ -16,12 +16,16 @@ __karma__.loaded = function() {}; let entryPoint = 'browser_entry_point'; let setup = 'browser-zone-setup'; +let patchTestFramework = true; if (typeof __karma__ !== 'undefined') { (window as any)['__Zone_Error_BlacklistedStackFrames_policy'] = (__karma__ as any).config.errorpolicy; if ((__karma__ as any).config.entrypoint) { entryPoint = (__karma__ as any).config.entrypoint; + if ((__karma__ as any).config.notPatchTestFramework) { + patchTestFramework = false; + } } if ((__karma__ as any).config.setup) { setup = (__karma__ as any).config.setup; @@ -52,10 +56,12 @@ if ((window as any)[(Zone as any).__symbol__('setTimeout')]) { // this means that Zone has not patched the browser yet, which means we must be running in // build mode and need to load the browser patch. browserPatchedPromise = System.import('/base/build/test/' + setup).then(() => { - let testFrameworkPatch = typeof (window as any).Mocha !== 'undefined' ? - '/base/build/lib/mocha/mocha' : - '/base/build/lib/jasmine/jasmine'; - return System.import(testFrameworkPatch); + if (patchTestFramework) { + let testFrameworkPatch = typeof (window as any).Mocha !== 'undefined' ? + '/base/build/lib/mocha/mocha' : + '/base/build/lib/jasmine/jasmine'; + return System.import(testFrameworkPatch); + } }); } From 5eccff60eef374a832c0a7e0bf8fc049e205993e Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Thu, 13 Dec 2018 09:14:46 +0900 Subject: [PATCH 09/12] revert promise check --- lib/zone.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/zone.ts b/lib/zone.ts index 205194294..6dd8d2c3e 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -683,7 +683,7 @@ const Zone: ZoneType = (function(global: any) { static __symbol__: (name: string) => string = __symbol__; static assertZonePatched() { - if (global['Promise'] !== patchedPromise) { + if (global['Promise'] !== patches['ZoneAwarePromise']) { throw new Error( 'Zone.js has detected that ZoneAwarePromise `(window|global).Promise` ' + 'has been overwritten.\n' + @@ -1372,7 +1372,7 @@ const Zone: ZoneType = (function(global: any) { if (!nativeMicroTaskQueuePromise) { if (global[symbolPromise]) { nativeMicroTaskQueuePromise = global[symbolPromise].resolve(0); - } else if (_mode === 'lazy' && patchedPromise !== global['Promise']) { + } else if (_mode === 'lazy' && !monkeyPatched) { nativeMicroTaskQueuePromise = global['Promise'].resolve(0); } } @@ -1426,7 +1426,6 @@ const Zone: ZoneType = (function(global: any) { eventTask: 'eventTask' = 'eventTask'; const patches: {[key: string]: any} = {}; - let patchedPromise: any; const delegates: {proto: any, property: string, patched: any, origin: any, isPropertyDesc: boolean}[] = []; let monkeyPatched = true; From 7798b649478bc8483ce6d190cdc9557bece5c83a Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Mon, 24 Dec 2018 09:29:36 +0800 Subject: [PATCH 10/12] add test cases --- ...nf.js => karma-build-jasmine-scope.conf.js | 5 +- lib/zone.ts | 25 +++++---- package.json | 4 +- ...setup.ts => browser-scope-zone-preload.ts} | 2 +- test/browser-scope-zone-setup.ts | 14 +++++ test/browser/{lazy.spec.ts => scope.spec.ts} | 55 +++++++++++++++++-- ...t.ts => browser_scope_zone_entry_point.ts} | 2 +- 7 files changed, 85 insertions(+), 22 deletions(-) rename karma-build-jasmine-lazy.conf.js => karma-build-jasmine-scope.conf.js (64%) rename test/{browser-lazy-zone-setup.ts => browser-scope-zone-preload.ts} (80%) create mode 100644 test/browser-scope-zone-setup.ts rename test/browser/{lazy.spec.ts => scope.spec.ts} (60%) rename test/{browser_lazy_zone_entry_point.ts => browser_scope_zone_entry_point.ts} (88%) diff --git a/karma-build-jasmine-lazy.conf.js b/karma-build-jasmine-scope.conf.js similarity index 64% rename from karma-build-jasmine-lazy.conf.js rename to karma-build-jasmine-scope.conf.js index 67f8522a4..8c73ce9c2 100644 --- a/karma-build-jasmine-lazy.conf.js +++ b/karma-build-jasmine-scope.conf.js @@ -7,8 +7,9 @@ */ module.exports = function(config) { - config.client.preload = 'build/test/browser-lazy-zone-setup.js'; + config.client.preload = 'build/test/browser-scope-zone-preload.js'; require('./karma-build-jasmine.conf.js')(config); - config.client.entrypoint = 'browser_lazy_zone_entry_point'; + config.client.entrypoint = 'browser_scope_zone_entry_point'; + config.client.setup = 'browser-scope-zone-setup'; config.client.notPatchTestFramework = true; }; diff --git a/lib/zone.ts b/lib/zone.ts index 6dd8d2c3e..ed829ff78 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -527,6 +527,11 @@ type TaskType = 'microTask'|'macroTask'|'eventTask'; */ type TaskState = 'notScheduled'|'scheduling'|'scheduled'|'running'|'canceling'|'unknown'; +/** + * Zone Load Mode: `normal`, `scope` + */ +type ZoneLoadMode = 'normal'|'scope'; + /** */ @@ -709,11 +714,11 @@ const Zone: ZoneType = (function(global: any) { return _currentTask; } - static get mode(): 'lazy'|'normal' { + static get mode(): ZoneLoadMode { return _mode; } - static set mode(newMode: 'lazy'|'normal') { + static set mode(newMode: ZoneLoadMode) { _mode = newMode; } @@ -834,7 +839,7 @@ const Zone: ZoneType = (function(global: any) { public run(callback: Function, applyThis?: any, applyArgs?: any[], source?: string): any; public run( callback: (...args: any[]) => T, applyThis?: any, applyArgs?: any[], source?: string): T { - if (!monkeyPatched && _mode === 'lazy') { + if (!monkeyPatched && _mode === 'scope') { Zone.__reloadAll(); } _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; @@ -842,7 +847,7 @@ const Zone: ZoneType = (function(global: any) { return this._zoneDelegate.invoke(this, callback, applyThis, applyArgs, source); } finally { _currentZoneFrame = _currentZoneFrame.parent!; - if (_mode === 'lazy' && _currentZoneFrame.zone && !_currentZoneFrame.zone.parent) { + if (_mode === 'scope' && _currentZoneFrame.zone && !_currentZoneFrame.zone.parent) { Zone.__unloadAll(); } } @@ -852,7 +857,7 @@ const Zone: ZoneType = (function(global: any) { public runGuarded( callback: (...args: any[]) => T, applyThis: any = null, applyArgs?: any[], source?: string) { - if (!monkeyPatched && _mode === 'lazy') { + if (!monkeyPatched && _mode === 'scope') { Zone.__reloadAll(); } _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; @@ -866,7 +871,7 @@ const Zone: ZoneType = (function(global: any) { } } finally { _currentZoneFrame = _currentZoneFrame.parent!; - if (_mode === 'lazy' && _currentZoneFrame.zone && !_currentZoneFrame.zone.parent) { + if (_mode === 'scope' && _currentZoneFrame.zone && !_currentZoneFrame.zone.parent) { Zone.__unloadAll(); } } @@ -892,7 +897,7 @@ const Zone: ZoneType = (function(global: any) { task.runCount++; const previousTask = _currentTask; _currentTask = task; - if (!monkeyPatched && _mode === 'lazy') { + if (!monkeyPatched && _mode === 'scope') { Zone.__reloadAll(); } _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; @@ -922,7 +927,7 @@ const Zone: ZoneType = (function(global: any) { } _currentZoneFrame = _currentZoneFrame.parent!; _currentTask = previousTask; - if (_mode === 'lazy' && _currentZoneFrame.zone && !_currentZoneFrame.zone.parent) { + if (_mode === 'scope' && _currentZoneFrame.zone && !_currentZoneFrame.zone.parent) { Zone.__unloadAll(); } } @@ -1372,7 +1377,7 @@ const Zone: ZoneType = (function(global: any) { if (!nativeMicroTaskQueuePromise) { if (global[symbolPromise]) { nativeMicroTaskQueuePromise = global[symbolPromise].resolve(0); - } else if (_mode === 'lazy' && !monkeyPatched) { + } else if (_mode === 'scope' && !monkeyPatched) { nativeMicroTaskQueuePromise = global['Promise'].resolve(0); } } @@ -1455,7 +1460,7 @@ const Zone: ZoneType = (function(global: any) { let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)}; let _currentTask: Task|null = null; let _numberOfNestedTaskFrames = 0; - let _mode: 'lazy'|'normal' = global['__zone_symbol__load_mode'] || 'normal'; + let _mode: ZoneLoadMode = global['__zone_symbol__load_mode'] || 'normal'; function noop() {} diff --git a/package.json b/package.json index 550802c09..361c036d7 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "format": "gulp format:enforce", "karma-jasmine": "karma start karma-build-jasmine.conf.js", "karma-jasmine:es6": "karma start karma-build-jasmine.es6.conf.js", - "karma-jasmine:lazy": "karma start karma-build-jasmine-lazy.conf.js", + "karma-jasmine:scope": "karma start karma-build-jasmine-scope.conf.js", "karma-jasmine:phantomjs": "karma start karma-build-jasmine-phantomjs.conf.js --single-run", "karma-jasmine:single": "karma start karma-build-jasmine.conf.js --single-run", "karma-jasmine:autoclose": "npm run karma-jasmine:single && npm run ws-client", @@ -41,7 +41,7 @@ "tslint": "tslint -c tslint.json 'lib/**/*.ts'", "test": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine\"", "test:es6": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine:es6\"", - "test:lazy": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run karma-jasmine:lazy\"", + "test:scope": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run karma-jasmine:scope\"", "test:phantomjs": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine:phantomjs\"", "test:phantomjs-single": "npm run tsc && concurrently \"npm run ws-server\" \"npm run karma-jasmine-phantomjs:autoclose\"", "test:single": "npm run tsc && concurrently \"npm run ws-server\" \"npm run karma-jasmine:autoclose\"", diff --git a/test/browser-lazy-zone-setup.ts b/test/browser-scope-zone-preload.ts similarity index 80% rename from test/browser-lazy-zone-setup.ts rename to test/browser-scope-zone-preload.ts index 0f3b2fbbb..991f601c2 100644 --- a/test/browser-lazy-zone-setup.ts +++ b/test/browser-scope-zone-preload.ts @@ -6,5 +6,5 @@ * found in the LICENSE file at https://angular.io/license */ if (typeof window !== 'undefined') { - (window as any)['__zone_symbol__load_mode'] = 'lazy'; + (window as any)['__zone_symbol__load_mode'] = 'scope'; } diff --git a/test/browser-scope-zone-setup.ts b/test/browser-scope-zone-setup.ts new file mode 100644 index 000000000..8e34233e9 --- /dev/null +++ b/test/browser-scope-zone-setup.ts @@ -0,0 +1,14 @@ +/** + * @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 + */ +import '../lib/common/to-string'; +import '../lib/browser/browser'; +import '../lib/common/fetch'; +import '../lib/browser/webapis-user-media'; +import '../lib/browser/webapis-media-query'; +import '../lib/extra/cordova'; +import '../lib/browser/webapis-resize-observer'; diff --git a/test/browser/lazy.spec.ts b/test/browser/scope.spec.ts similarity index 60% rename from test/browser/lazy.spec.ts rename to test/browser/scope.spec.ts index aec2e4848..8132041ff 100644 --- a/test/browser/lazy.spec.ts +++ b/test/browser/scope.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -describe('lazy', function() { +describe('scope', function() { const targets = [ {proto: 'Window', property: 'Promise'}, {proto: 'Promise', property: 'then'}, @@ -55,7 +55,6 @@ describe('lazy', function() { } if (proto) { const native = proto[Zone.__symbol__(property)]; - console.log('check target', protoName, property); if (isNative) { expect(proto[property]).toBe(native); } else { @@ -65,15 +64,59 @@ describe('lazy', function() { }); } - it('before patch should use native delegate', (done: DoneFn) => { + it('after unloadAll patches should use native delegate', () => { (Zone as any).__unloadAll(); checkDelegates(true); - done(); }); - it('after load should use patched delegate', (done: DoneFn) => { + it('after reloadAll patches should use patched delegate', () => { (Zone as any).__reloadAll(); checkDelegates(false); - done(); }); + + it('after unload, function inside zone.run should still use patched delegates', () => { + (Zone as any).__unloadAll(); + Zone.current.fork({name: 'zone'}).run(() => { + checkDelegates(false); + }); + }); + + it('after unload, async function inside zone.run should still use patched delegates', + (done: DoneFn) => { + (Zone as any).__unloadAll(); + Zone.current.fork({name: 'zone'}).run(() => { + checkDelegates(false); + setTimeout(() => { + checkDelegates(false); + expect(Zone.current.name).toEqual('zone'); + done(); + }); + }); + checkDelegates(true); + }); + + it('after unload, async function inside nested zone.run should still use patched delegates', + (done: DoneFn) => { + (Zone as any).__unloadAll(); + const zone = Zone.current.fork({name: 'zone'}); + const childZone = Zone.current.fork({name: 'childZone'}); + zone.run(() => { + setTimeout(() => { + checkDelegates(false); + expect(Zone.current.name).toEqual(zone.name); + childZone.run(() => { + expect(Zone.current.name).toEqual(childZone.name); + checkDelegates(false); + setTimeout(() => { + expect(Zone.current.name).toEqual(childZone.name); + checkDelegates(false); + done(); + }); + }); + expect(Zone.current.name).toEqual(zone.name); + checkDelegates(false); + }); + }); + checkDelegates(true); + }); }); diff --git a/test/browser_lazy_zone_entry_point.ts b/test/browser_scope_zone_entry_point.ts similarity index 88% rename from test/browser_lazy_zone_entry_point.ts rename to test/browser_scope_zone_entry_point.ts index 4072f9889..91ffa10d3 100644 --- a/test/browser_lazy_zone_entry_point.ts +++ b/test/browser_scope_zone_entry_point.ts @@ -7,4 +7,4 @@ */ // List all tests here: -import './browser/lazy.spec'; +import './browser/scope.spec'; From 28b75dc66c0cf872d5b50267acbc5e69e30e3a8c Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Mon, 24 Dec 2018 09:38:10 +0800 Subject: [PATCH 11/12] add cases for promise --- test/browser/scope.spec.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/browser/scope.spec.ts b/test/browser/scope.spec.ts index 8132041ff..85d5321bd 100644 --- a/test/browser/scope.spec.ts +++ b/test/browser/scope.spec.ts @@ -86,6 +86,16 @@ describe('scope', function() { (Zone as any).__unloadAll(); Zone.current.fork({name: 'zone'}).run(() => { checkDelegates(false); + new Promise(res => { + setTimeout(() => { + expect(Zone.current.name).toEqual('zone'); + checkDelegates(false); + res(); + }); + }).then(() => { + expect(Zone.current.name).toEqual('zone'); + checkDelegates(false); + }); setTimeout(() => { checkDelegates(false); expect(Zone.current.name).toEqual('zone'); @@ -107,6 +117,16 @@ describe('scope', function() { childZone.run(() => { expect(Zone.current.name).toEqual(childZone.name); checkDelegates(false); + new Promise(res => { + setTimeout(() => { + expect(Zone.current.name).toEqual(childZone.name); + checkDelegates(false); + res(); + }); + }).then(() => { + expect(Zone.current.name).toEqual(childZone.name); + checkDelegates(false); + }); setTimeout(() => { expect(Zone.current.name).toEqual(childZone.name); checkDelegates(false); From 834240d52dabaab00c2c1521cddec58f4f53b1b1 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Wed, 26 Dec 2018 12:08:58 +0800 Subject: [PATCH 12/12] add test cases --- test/browser/scope.spec.ts | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/browser/scope.spec.ts b/test/browser/scope.spec.ts index 85d5321bd..5974e04a9 100644 --- a/test/browser/scope.spec.ts +++ b/test/browser/scope.spec.ts @@ -139,4 +139,42 @@ describe('scope', function() { }); checkDelegates(true); }); + + it('after unload, nested async function inside zone.run should still use patched delegates', + (done: DoneFn) => { + (Zone as any).__unloadAll(); + Zone.current.fork({name: 'zone'}).run(() => { + checkDelegates(false); + new Promise(res => { + setTimeout(() => { + expect(Zone.current.name).toEqual('zone'); + checkDelegates(false); + res(new Promise(res1 => { + setTimeout(() => { + expect(Zone.current.name).toEqual('zone'); + checkDelegates(false); + setTimeout(() => { + expect(Zone.current.name).toEqual('zone'); + checkDelegates(false); + res1(); + }); + }); + })); + }); + }).then(() => { + expect(Zone.current.name).toEqual('zone'); + checkDelegates(false); + }); + setTimeout(() => { + checkDelegates(false); + expect(Zone.current.name).toEqual('zone'); + setTimeout(() => { + checkDelegates(false); + expect(Zone.current.name).toEqual('zone'); + }); + }); + }); + checkDelegates(true); + setTimeout(done, 500); + }); });