diff --git a/modules/angular2/src/core/zone/vm_turn_zone.dart b/modules/angular2/src/core/zone/vm_turn_zone.dart index 7b0d3991b8..a4d4f58695 100644 --- a/modules/angular2/src/core/zone/vm_turn_zone.dart +++ b/modules/angular2/src/core/zone/vm_turn_zone.dart @@ -29,12 +29,10 @@ class VmTurnZone { // onTurnDone hook at the end of the current VM turn. Zone _innerZone; - // Number of microtasks pending from _outerZone (& descendants) + // Number of microtasks pending from _innerZone (& descendants) int _pendingMicrotasks = 0; // Whether some code has been executed in the _innerZone (& descendants) in the current turn bool _hasExecutedCodeInInnerZone = false; - // Whether the onTurnStart hook is executing - bool _inTurnStart = false; // _outerRun() call depth. 0 at the end of a macrotask // zone.run(() => { // top-level call // zone.run(() => {}); // nested call -> in-turn @@ -44,36 +42,26 @@ class VmTurnZone { /** * Associates with this * - * - an "outer" [Zone], which is a child of the one that created this. + * - an "outer" [Zone], which is a the one that created this. * - an "inner" [Zone], which is a child of the outer [Zone]. * * @param {bool} enableLongStackTrace whether to enable long stack trace. They should only be * enabled in development mode as they significantly impact perf. */ VmTurnZone({bool enableLongStackTrace}) { - // The _outerZone captures microtask scheduling so that we can run onTurnDone when the queue - // is exhausted and code has been executed in the _innerZone. + _outerZone = Zone.current; + if (enableLongStackTrace) { - _outerZone = Chain.capture( - () => _createOuterZone(Zone.current), + _innerZone = Chain.capture( + () => _createInnerZone(Zone.current), onError: _onErrorWithLongStackTrace); } else { - _outerZone = _createOuterZone( + _innerZone = _createInnerZone( Zone.current, handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, error, StackTrace trace) => _onErrorWithoutLongStackTrace(error, trace) ); } - - // Instruments the inner [Zone] to detect when code is executed in this (or a descendant) zone. - // Also runs the onTurnStart hook the first time this zone executes some code in each turn. - _innerZone = _outerZone.fork( - specification: new ZoneSpecification( - run: _innerRun, - runUnary: _innerRunUnary, - runBinary: _innerRunBinary - ), - zoneValues: {'_name': 'inner'}); } /** @@ -136,47 +124,28 @@ class VmTurnZone { * ``` */ dynamic runOutsideAngular(fn()) { - return _outerZone.runGuarded(fn); + return _outerZone.run(fn); } - // Executes code in the [_innerZone] & trigger the onTurnStart hook when code is executed for the - // first time in a turn. - dynamic _innerRun(Zone self, ZoneDelegate parent, Zone zone, fn()) { - _maybeStartVmTurn(parent, zone); - return parent.run(zone, fn); - } - - dynamic _innerRunUnary(Zone self, ZoneDelegate parent, Zone zone, fn(arg), arg) { - _maybeStartVmTurn(parent, zone); - return parent.runUnary(zone, fn, arg); - } - - dynamic _innerRunBinary(Zone self, ZoneDelegate parent, Zone zone, fn(arg1, arg2), arg1, arg2) { - _maybeStartVmTurn(parent, zone); - return parent.runBinary(zone, fn, arg1, arg2); - } - - void _maybeStartVmTurn(ZoneDelegate parent, Zone zone) { + void _maybeStartVmTurn(ZoneDelegate parent) { if (!_hasExecutedCodeInInnerZone) { _hasExecutedCodeInInnerZone = true; if (_onTurnStart != null) { - _inTurnStart = true; - parent.run(zone, _onTurnStart); + parent.run(_innerZone, _onTurnStart); } } } - dynamic _outerRun(Zone self, ZoneDelegate parent, Zone zone, fn()) { + dynamic _run(Zone self, ZoneDelegate parent, Zone zone, fn()) { try { _nestedRun++; + _maybeStartVmTurn(parent); return parent.run(zone, fn); } finally { _nestedRun--; - // If there are no more pending microtasks, we are at the end of a VM turn (or in onTurnStart) - // _nestedRun will be 0 at the end of a macrotasks (it could be > 0 when there are nested calls - // to _outerRun()). + // If there are no more pending microtasks and we are not in a recursive call, this is the end of a turn if (_pendingMicrotasks == 0 && _nestedRun == 0) { - if (_onTurnDone != null && !_inTurnStart && _hasExecutedCodeInInnerZone) { + if (_onTurnDone != null && _hasExecutedCodeInInnerZone) { // Trigger onTurnDone at the end of a turn if _innerZone has executed some code try { parent.run(_innerZone, _onTurnDone); @@ -185,15 +154,14 @@ class VmTurnZone { } } } - _inTurnStart = false; } } - dynamic _outerRunUnary(Zone self, ZoneDelegate parent, Zone zone, fn(arg), arg) => - _outerRun(self, parent, zone, () => fn(arg)); + dynamic _runUnary(Zone self, ZoneDelegate parent, Zone zone, fn(arg), arg) => + _run(self, parent, zone, () => fn(arg)); - dynamic _outerRunBinary(Zone self, ZoneDelegate parent, Zone zone, fn(arg1, arg2), arg1, arg2) => - _outerRun(self, parent, zone, () => fn(arg1, arg2)); + dynamic _runBinary(Zone self, ZoneDelegate parent, Zone zone, fn(arg1, arg2), arg1, arg2) => + _run(self, parent, zone, () => fn(arg1, arg2)); void _scheduleMicrotask(Zone self, ZoneDelegate parent, Zone zone, fn) { _pendingMicrotasks++; @@ -226,16 +194,16 @@ class VmTurnZone { } } - Zone _createOuterZone(Zone zone, {handleUncaughtError}) { + Zone _createInnerZone(Zone zone, {handleUncaughtError}) { return zone.fork( specification: new ZoneSpecification( scheduleMicrotask: _scheduleMicrotask, - run: _outerRun, - runUnary: _outerRunUnary, - runBinary: _outerRunBinary, + run: _run, + runUnary: _runUnary, + runBinary: _runBinary, handleUncaughtError: handleUncaughtError ), - zoneValues: {'_name': 'outer'} + zoneValues: {'_innerZone': true} ); } } diff --git a/modules/angular2/src/core/zone/vm_turn_zone.es6 b/modules/angular2/src/core/zone/vm_turn_zone.es6 index 72d2e500f5..5f09271870 100644 --- a/modules/angular2/src/core/zone/vm_turn_zone.es6 +++ b/modules/angular2/src/core/zone/vm_turn_zone.es6 @@ -27,8 +27,6 @@ export class VmTurnZone { _pendingMicrotask: number; // Whether some code has been executed in the _innerZone (& descendants) in the current turn _hasExecutedCodeInInnerZone: boolean; - // Whether the onTurnStart hook is executing - _inTurnStart: boolean; // run() call depth in _outerZone. 0 at the end of a macrotask // zone.run(() => { // top-level call // zone.run(() => {}); // nested call -> in-turn @@ -51,11 +49,10 @@ export class VmTurnZone { this._pendingMicrotasks = 0; this._hasExecutedCodeInInnerZone = false; - this._inTurnStart = false; this._nestedRun = 0; - this._outerZone = this._createOuterZone(global.zone); - this._innerZone = this._createInnerZone(this._outerZone, enableLongStackTrace); + this._outerZone = global.zone; + this._innerZone = this._createInnerZone(this._outerZone, enableLongStackTrace) } /** @@ -109,50 +106,6 @@ export class VmTurnZone { return this._outerZone.run(fn); } - _createOuterZone(zone) { - var vmTurnZone = this; - - return zone.fork({ - _name: 'outer', - '$run': function(parentRun) { - return function() { - try { - vmTurnZone._nestedRun++; - return parentRun.apply(this, arguments); - } finally { - vmTurnZone._nestedRun--; - // If there are no more pending microtasks, we are at the end of a VM turn (or in onTurnStart) - // _nestedRun will be 0 at the end of a macrotasks (it could be > 0 when there are nested calls - // to run()). - if (vmTurnZone._pendingMicrotasks == 0 && vmTurnZone._nestedRun == 0) { - if (vmTurnZone._onTurnDone && !vmTurnZone._inTurnStart && vmTurnZone._hasExecutedCodeInInnerZone) { - try { - parentRun.call(vmTurnZone._innerZone, vmTurnZone._onTurnDone); - } finally { - vmTurnZone._hasExecutedCodeInInnerZone = false; - } - } - } - vmTurnZone._inTurnStart = false; - } - } - }, - '$scheduleMicrotask': function(parentScheduleMicrotask) { - return function(fn) { - vmTurnZone._pendingMicrotasks++; - var microtask = function() { - try { - fn(); - } finally { - vmTurnZone._pendingMicrotasks--; - } - }; - parentScheduleMicrotask.call(this, microtask); - } - } - }); - } - _createInnerZone(zone, enableLongStackTrace) { var vmTurnZone = this; var errorHandling; @@ -174,28 +127,51 @@ export class VmTurnZone { return zone .fork(errorHandling) .fork({ - // Executes code in the _innerZone & trigger the onTurnStart hook when code is executed for the - // first time in a turn. - '$run': function (parentRun) { - return function () { - vmTurnZone._maybeStartVmTurn() - return parentRun.apply(this, arguments) + '$run': function(parentRun) { + return function() { + try { + vmTurnZone._nestedRun++; + if (!vmTurnZone._hasExecutedCodeInInnerZone) { + vmTurnZone._hasExecutedCodeInInnerZone = true; + if (vmTurnZone._onTurnStart) { + parentRun.call(vmTurnZone._innerZone, vmTurnZone._onTurnStart); + } + } + return parentRun.apply(this, arguments); + } finally { + vmTurnZone._nestedRun--; + // If there are no more pending microtasks, we are at the end of a VM turn (or in onTurnStart) + // _nestedRun will be 0 at the end of a macrotasks (it could be > 0 when there are nested calls + // to run()). + if (vmTurnZone._pendingMicrotasks == 0 && vmTurnZone._nestedRun == 0) { + if (vmTurnZone._onTurnDone && vmTurnZone._hasExecutedCodeInInnerZone) { + try { + parentRun.call(vmTurnZone._innerZone, vmTurnZone._onTurnDone); + } finally { + vmTurnZone._hasExecutedCodeInInnerZone = false; + } + } + } + } } }, - _name: 'inner' + '$scheduleMicrotask': function(parentScheduleMicrotask) { + return function(fn) { + vmTurnZone._pendingMicrotasks++; + var microtask = function() { + try { + fn(); + } finally { + vmTurnZone._pendingMicrotasks--; + } + }; + parentScheduleMicrotask.call(this, microtask); + } + }, + _innerZone: true }); } - _maybeStartVmTurn(): void { - if (!this._hasExecutedCodeInInnerZone) { - this._hasExecutedCodeInInnerZone = true; - if (this._onTurnStart) { - this._inTurnStart = true; - this._outerZone.run.call(this._innerZone, this._onTurnStart); - } - } - } - _onError(zone, e): void { if (isPresent(this._onErrorHandler)) { var trace = [normalizeBlank(e.stack)]; diff --git a/modules/angular2/src/test_lib/test_lib.dart b/modules/angular2/src/test_lib/test_lib.dart index 3d652e142d..c0e2466040 100644 --- a/modules/angular2/src/test_lib/test_lib.dart +++ b/modules/angular2/src/test_lib/test_lib.dart @@ -226,4 +226,4 @@ String elementText(n) { return DOM.getText(n); } -String getCurrentZoneName() => Zone.current['_name']; +bool isInInnerZone() => Zone.current['_innerZone'] == true; diff --git a/modules/angular2/src/test_lib/test_lib.es6 b/modules/angular2/src/test_lib/test_lib.es6 index f9b6595a46..553b661b98 100644 --- a/modules/angular2/src/test_lib/test_lib.es6 +++ b/modules/angular2/src/test_lib/test_lib.es6 @@ -354,6 +354,6 @@ function elementText(n) { return DOM.getText(n); } -function getCurrentZoneName(): string { - return global.zone._name; +export function isInInnerZone(): boolean { + return global.zone._innerZone === true; } diff --git a/modules/angular2/test/core/zone/vm_turn_zone_spec.js b/modules/angular2/test/core/zone/vm_turn_zone_spec.js index dd078e20d2..4b34f3a094 100644 --- a/modules/angular2/test/core/zone/vm_turn_zone_spec.js +++ b/modules/angular2/test/core/zone/vm_turn_zone_spec.js @@ -9,7 +9,8 @@ import { it, xdescribe, xit, - Log + Log, + isInInnerZone } from 'angular2/test_lib'; import {PromiseWrapper} from 'angular2/src/facade/async'; @@ -19,10 +20,8 @@ import {BaseException} from 'angular2/src/facade/lang'; import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone'; // Schedules a macrotask (using a timer) -// The code is executed in the outer zone to properly detect VM turns - in Dart VM turns could not be properly detected -// in the root zone because scheduleMicrotask() is not overriden. function macroTask(fn: Function): void { - _zone.runOutsideAngular(() => PromiseWrapper.setTimeout(fn, 0)); + _zone.runOutsideAngular(() => PromiseWrapper.setTimeout(fn, 1)); } // Schedules a microtasks (using a resolved promise .then()) @@ -145,7 +144,16 @@ export function main() { } function commonTests() { - describe("run", () => { + describe('isInInnerZone', () => { + it('should return whether the code executes in the inner zone', () => { + expect(isInInnerZone()).toEqual(false); + _zone.run(() => { + expect(isInInnerZone()).toEqual(true); + }); + }) + }); + + describe('run', () => { it('should return the body return value from run', inject([AsyncTestCompleter], (async) => { macroTask(() => { expect(_zone.run(() => { @@ -175,7 +183,7 @@ function commonTests() { macroTask(() => { _zone.run(() => { _log.add('run start'); - microTask(() => { _log.add('async'); }); + microTask(_log.fn('async')); _log.add('run end'); }); }); @@ -194,7 +202,7 @@ function commonTests() { _log.add('start run'); _zone.run(() => { _log.add('nested run'); - microTask(() => _log.add('nested run microtask')); + microTask(_log.fn('nested run microtask')); }); _log.add('end run'); }); @@ -210,15 +218,14 @@ function commonTests() { inject([AsyncTestCompleter], (async) => { macroTask(() => { _zone.run(_log.fn('run1')); + }); + + macroTask(() => { _zone.run(_log.fn('run2')); }); macroTask(() => { - _zone.run(_log.fn('run3')); - }); - - macroTask(() => { - expect(_log.result()).toEqual('onTurnStart; run1; run2; onTurnDone; onTurnStart; run3; onTurnDone'); + expect(_log.result()).toEqual('onTurnStart; run1; onTurnDone; onTurnStart; run2; onTurnDone'); async.done(); }); })); @@ -229,22 +236,21 @@ function commonTests() { var b; macroTask(() => { - a = PromiseWrapper.completer(); - b = PromiseWrapper.completer(); _zone.run(() => { + a = PromiseWrapper.completer(); + b = PromiseWrapper.completer(); + _log.add('run start'); - a.promise.then((_) => { - return _log.add('a then'); - }); - b.promise.then((_) => { - return _log.add('b then'); - }); + a.promise.then(_log.fn('a then')); + b.promise.then(_log.fn('b then')); }); }); macroTask(() => { - a.resolve('a'); - b.resolve('b'); + _zone.run(() => { + a.resolve('a'); + b.resolve('b'); + }); }); macroTask(() => { @@ -276,9 +282,7 @@ function commonTests() { macroTask(() => { _zone.run(() => { - completer.promise.then((_) => { - _log.add('executedMicrotask'); - }); + completer.promise.then(_log.fn('executedMicrotask')); }); }); @@ -302,34 +306,6 @@ function commonTests() { }); })); - it('should not call onTurnStart and onTurnDone when an outer microtask is scheduled from inside angular', - inject([AsyncTestCompleter], (async) => { - var completer; - - macroTask(() => { - _zone.runOutsideAngular(() => { - completer = PromiseWrapper.completer(); - completer.promise.then((_) => { - _log.add('executedMicrotask'); - }); - }); - }); - - macroTask(() => { - _zone.run(() => { - _log.add('scheduling a microtask'); - completer.resolve(null); - }); - }); - - macroTask(() => { - expect(_log.result()).toEqual( - 'onTurnStart; scheduling a microtask; executedMicrotask; onTurnDone' - ); - async.done(); - }); - })); - it('should call onTurnStart before executing a microtask scheduled in onTurnDone as well as ' + 'onTurnDone after executing the task', inject([AsyncTestCompleter], (async) => { var ran = false; @@ -347,9 +323,7 @@ function commonTests() { }}); macroTask(() => { - _zone.run(() => { - _log.add('run'); - }); + _zone.run(_log.fn('run')); }); macroTask(() => { @@ -383,7 +357,7 @@ function commonTests() { macroTask(() => { _zone.run(() => { _log.add('scheduleMicrotask'); - microTask(() => { _log.add('run(executeMicrotask)'); }); + microTask(_log.fn('run(executeMicrotask)')); }); }); @@ -409,7 +383,7 @@ function commonTests() { _log.add('onTurnStart(begin)'); if (!startPromiseRan) { _log.add('onTurnStart(schedulePromise)'); - microTask(() => { _log.add('onTurnStart(executePromise)'); }); + microTask(_log.fn('onTurnStart(executePromise)')); startPromiseRan = true; } _log.add('onTurnStart(end)'); @@ -418,7 +392,7 @@ function commonTests() { _log.add('onTurnDone(begin)'); if (!donePromiseRan) { _log.add('onTurnDone(schedulePromise)'); - microTask(() => { _log.add('onTurnDone(executePromise)'); }); + microTask(_log.fn('onTurnDone(executePromise)')); donePromiseRan = true; } _log.add('onTurnDone(end)'); @@ -430,12 +404,10 @@ function commonTests() { PromiseWrapper.resolve(null) .then((_) => { _log.add('promise then'); - PromiseWrapper.resolve(null).then((_) => { _log.add('promise foo'); }); + PromiseWrapper.resolve(null).then(_log.fn('promise foo')); return PromiseWrapper.resolve(null); }) - .then((_) => { - _log.add('promise bar'); - }); + .then(_log.fn('promise bar')); _log.add('run end'); }); }); @@ -465,8 +437,8 @@ function commonTests() { _zone.run(() => { completerA = PromiseWrapper.completer(); completerB = PromiseWrapper.completer(); - completerA.promise.then((_) => _log.add('a then')); - completerB.promise.then((_) => _log.add('b then')); + completerA.promise.then(_log.fn('a then')); + completerB.promise.then(_log.fn('b then')); _log.add('run start'); }); }); @@ -503,9 +475,7 @@ function commonTests() { _log.add('run start'); microTask(() => { _log.add('async1'); - microTask(() => { - _log.add('async2'); - }); + microTask(_log.fn('async2')); }); _log.add('run end'); }); @@ -521,26 +491,19 @@ function commonTests() { inject([AsyncTestCompleter], (async) => { var promise; - _zone.initCallbacks({ - onTurnStart: _log.fn('onTurnStart'), - onTurnDone: _log.fn('onTurnDone') - }); - macroTask(() => { _zone.runOutsideAngular(() => { promise = PromiseWrapper.resolve(4).then((x) => PromiseWrapper.resolve(x)); }); _zone.run(() => { - promise.then((_) => { - _log.add('promise then'); - }); + promise.then(_log.fn('promise then')); _log.add('zone run'); }); }); macroTask(() => { - expect(_log.result()).toEqual('onTurnStart; zone run; promise then; onTurnDone'); + expect(_log.result()).toEqual('onTurnStart; zone run; onTurnDone; onTurnStart; promise then; onTurnDone'); async.done(); }); }));