diff --git a/karma-build-jasmine-scope.conf.js b/karma-build-jasmine-scope.conf.js new file mode 100644 index 000000000..8c73ce9c2 --- /dev/null +++ b/karma-build-jasmine-scope.conf.js @@ -0,0 +1,15 @@ +/** + * @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) { + config.client.preload = 'build/test/browser-scope-zone-preload.js'; + require('./karma-build-jasmine.conf.js')(config); + config.client.entrypoint = 'browser_scope_zone_entry_point'; + config.client.setup = 'browser-scope-zone-setup'; + config.client.notPatchTestFramework = true; +}; 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'); 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/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/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/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/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 aa1f44757..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,9 +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, 'Promise', NativePromise); const symbolThenPatched = __symbol__('thenPatched'); @@ -469,6 +477,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 77554e524..dd31ef36d 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); } } } @@ -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) { @@ -315,7 +327,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 +348,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 +423,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 +495,9 @@ 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; + (Zone as any).__register_patched_delegate(patchedTarget, prop, original); } let isDetectedIEOrEdge = false; diff --git a/lib/zone.ts b/lib/zone.ts index 74fb76536..ed829ff78 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; @@ -329,6 +335,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 */ @@ -487,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; } @@ -512,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'; + /** */ @@ -636,12 +656,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 @@ -691,6 +714,14 @@ const Zone: ZoneType = (function(global: any) { return _currentTask; } + static get mode(): ZoneLoadMode { + return _mode; + } + + static set mode(newMode: ZoneLoadMode) { + _mode = newMode; + } + static __load_patch(name: string, fn: _PatchFn): void { if (patches.hasOwnProperty(name)) { throw Error('Already loaded patch: ' + name); @@ -702,6 +733,55 @@ const Zone: ZoneType = (function(global: any) { } } + 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 (monkeyPatched) { + return; + } + delegates.forEach(delegate => { + if (delegate.isPropertyDesc) { + Object.defineProperty(delegate.proto, delegate.property, delegate.patched); + } else { + delegate.proto[delegate.property] = delegate.patched; + } + }); + monkeyPatched = true; + } + + static __unloadAll() { + if (!monkeyPatched) { + return; + } + delegates.forEach(delegate => { + if (delegate.isPropertyDesc) { + Object.defineProperty(delegate.proto, delegate.property, delegate.origin); + } else { + delegate.proto[delegate.property] = delegate.origin; + } + }); + monkeyPatched = false; + } + public get parent(): AmbientZone|null { return this._parent; } @@ -759,11 +839,17 @@ 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 === 'scope') { + Zone.__reloadAll(); + } _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; try { return this._zoneDelegate.invoke(this, callback, applyThis, applyArgs, source); } finally { _currentZoneFrame = _currentZoneFrame.parent!; + if (_mode === 'scope' && _currentZoneFrame.zone && !_currentZoneFrame.zone.parent) { + Zone.__unloadAll(); + } } } @@ -771,6 +857,9 @@ const Zone: ZoneType = (function(global: any) { public runGuarded( callback: (...args: any[]) => T, applyThis: any = null, applyArgs?: any[], source?: string) { + if (!monkeyPatched && _mode === 'scope') { + Zone.__reloadAll(); + } _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; try { try { @@ -782,6 +871,9 @@ const Zone: ZoneType = (function(global: any) { } } finally { _currentZoneFrame = _currentZoneFrame.parent!; + if (_mode === 'scope' && _currentZoneFrame.zone && !_currentZoneFrame.zone.parent) { + Zone.__unloadAll(); + } } } @@ -805,6 +897,9 @@ const Zone: ZoneType = (function(global: any) { task.runCount++; const previousTask = _currentTask; _currentTask = task; + if (!monkeyPatched && _mode === 'scope') { + Zone.__reloadAll(); + } _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; try { if (task.type == macroTask && task.data && !task.data.isPeriodic) { @@ -832,6 +927,9 @@ const Zone: ZoneType = (function(global: any) { } _currentZoneFrame = _currentZoneFrame.parent!; _currentTask = previousTask; + if (_mode === 'scope' && _currentZoneFrame.zone && !_currentZoneFrame.zone.parent) { + Zone.__unloadAll(); + } } } @@ -1279,6 +1377,8 @@ const Zone: ZoneType = (function(global: any) { if (!nativeMicroTaskQueuePromise) { if (global[symbolPromise]) { nativeMicroTaskQueuePromise = global[symbolPromise].resolve(0); + } else if (_mode === 'scope' && !monkeyPatched) { + nativeMicroTaskQueuePromise = global['Promise'].resolve(0); } } if (nativeMicroTaskQueuePromise) { @@ -1331,6 +1431,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, isPropertyDesc: boolean}[] = []; + let monkeyPatched = true; const _api: _ZonePrivate = { symbol: __symbol__, currentZoneFrame: () => _currentZoneFrame, @@ -1351,10 +1454,13 @@ const Zone: ZoneType = (function(global: any) { nativeMicroTaskQueuePromise = NativePromise.resolve(0); } }, + 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; let _numberOfNestedTaskFrames = 0; + let _mode: ZoneLoadMode = global['__zone_symbol__load_mode'] || 'normal'; function noop() {} diff --git a/package.json b/package.json index 479947dae..361c036d7 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: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", @@ -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: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-scope-zone-preload.ts b/test/browser-scope-zone-preload.ts new file mode 100644 index 000000000..991f601c2 --- /dev/null +++ b/test/browser-scope-zone-preload.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'] = '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/performance.spec.ts b/test/browser/performance.spec.ts new file mode 100644 index 000000000..08b9ddfd2 --- /dev/null +++ b/test/browser/performance.spec.ts @@ -0,0 +1,61 @@ +/** + * @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 {ifEnvSupports} from '../test-util'; + +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/browser/scope.spec.ts b/test/browser/scope.spec.ts new file mode 100644 index 000000000..5974e04a9 --- /dev/null +++ b/test/browser/scope.spec.ts @@ -0,0 +1,180 @@ +/** + * @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('scope', function() { + const targets = [ + {proto: 'Window', property: 'Promise'}, + {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: 'Document', 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'} + ]; + + 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)]; + if (isNative) { + expect(proto[property]).toBe(native); + } else { + expect(proto[property]).not.toBe(native); + } + } + }); + } + + it('after unloadAll patches should use native delegate', () => { + (Zone as any).__unloadAll(); + checkDelegates(true); + }); + + it('after reloadAll patches should use patched delegate', () => { + (Zone as any).__reloadAll(); + checkDelegates(false); + }); + + 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); + 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'); + 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); + 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); + done(); + }); + }); + expect(Zone.current.name).toEqual(zone.name); + checkDelegates(false); + }); + }); + 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); + }); +}); 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'; diff --git a/test/browser_scope_zone_entry_point.ts b/test/browser_scope_zone_entry_point.ts new file mode 100644 index 000000000..91ffa10d3 --- /dev/null +++ b/test/browser_scope_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/scope.spec'; diff --git a/test/main.ts b/test/main.ts index 049f9ba6f..67f08b0e8 100644 --- a/test/main.ts +++ b/test/main.ts @@ -15,12 +15,20 @@ declare const __karma__: { __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; } } else if (typeof process !== 'undefined') { (window as any)['__Zone_Error_BlacklistedStackFrames_policy'] = process.env.errorpolicy; @@ -47,11 +55,13 @@ 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(() => { - let testFrameworkPatch = typeof (window as any).Mocha !== 'undefined' ? - '/base/build/lib/mocha/mocha' : - '/base/build/lib/jasmine/jasmine'; - return System.import(testFrameworkPatch); + browserPatchedPromise = System.import('/base/build/test/' + setup).then(() => { + if (patchTestFramework) { + let testFrameworkPatch = typeof (window as any).Mocha !== 'undefined' ? + '/base/build/lib/mocha/mocha' : + '/base/build/lib/jasmine/jasmine'; + return System.import(testFrameworkPatch); + } }); }