From df36ffb11df612faa04a56faf1be2420a353c4cd Mon Sep 17 00:00:00 2001 From: vsavkin Date: Fri, 5 Dec 2014 18:30:45 -0800 Subject: [PATCH] feat(zone): add initial implementation of VmTurnZone --- karma-js.conf.js | 1 + modules/core/src/application.js | 1 + modules/core/src/zone/vm_turn_zone.dart | 66 ++++++++++++++++ modules/core/src/zone/vm_turn_zone.es6 | 52 +++++++++++++ modules/core/test/zone/vm_turn_zone_spec.js | 85 +++++++++++++++++++++ modules/facade/src/async.dart | 13 ++++ modules/facade/src/async.es6 | 16 ++++ modules/facade/src/collection.dart | 1 + modules/facade/src/collection.es6 | 5 ++ modules/test_lib/src/test_lib.dart | 2 +- modules/test_lib/src/test_lib.es6 | 1 - modules/test_lib/src/utils.js | 23 ++++++ modules/test_lib/test/test_lib_spec.js | 5 +- package.json | 3 +- 14 files changed, 269 insertions(+), 5 deletions(-) create mode 100644 modules/core/src/zone/vm_turn_zone.dart create mode 100644 modules/core/src/zone/vm_turn_zone.es6 create mode 100644 modules/core/test/zone/vm_turn_zone_spec.js create mode 100644 modules/test_lib/src/utils.js diff --git a/karma-js.conf.js b/karma-js.conf.js index 672bc13265..3c995e1fda 100644 --- a/karma-js.conf.js +++ b/karma-js.conf.js @@ -19,6 +19,7 @@ module.exports = function(config) { // Including systemjs because it defines `__eval`, which produces correct stack traces. 'node_modules/systemjs/dist/system.src.js', 'node_modules/systemjs/lib/extension-register.js', + 'node_modules/zone.js/zone.js', 'tools/build/file2modulename.js', 'test-main.js' diff --git a/modules/core/src/application.js b/modules/core/src/application.js index 2c0ab60192..3fe660d2cd 100644 --- a/modules/core/src/application.js +++ b/modules/core/src/application.js @@ -83,6 +83,7 @@ export function bootstrap(appComponentType: Type, bindings=null) { var appInjector = _rootInjector.createChild(_injectorBindings( appComponentType)); if (isPresent(bindings)) appInjector = appInjector.createChild(bindings); + return appInjector.asyncGet(ChangeDetector).then((cd) => { // TODO(rado): replace with zone. cd.detectChanges(); diff --git a/modules/core/src/zone/vm_turn_zone.dart b/modules/core/src/zone/vm_turn_zone.dart new file mode 100644 index 0000000000..06f63c3b38 --- /dev/null +++ b/modules/core/src/zone/vm_turn_zone.dart @@ -0,0 +1,66 @@ +library angular.zone; + +import 'dart:async' as async; + +class VmTurnZone { + Function _onTurnStart; + Function _onTurnDone; + Function _onScheduleMicrotask; + + async.Zone _outerZone; + async.Zone _innerZone; + + int _nestedRunCounter; + + VmTurnZone() { + _nestedRunCounter = 0; + _outerZone = async.Zone.current; + _innerZone = _outerZone.fork(specification: new async.ZoneSpecification( + run: _onRun, + runUnary: _onRunUnary, + scheduleMicrotask: _onMicrotask + )); + } + + initCallbacks({Function onTurnStart, Function onTurnDone, Function onScheduleMicrotask}) { + this._onTurnStart = onTurnStart; + this._onTurnDone = onTurnDone; + this._onScheduleMicrotask = onScheduleMicrotask; + } + + dynamic run(fn()) => _innerZone.run(fn); + + dynamic runOutsideAngular(fn()) => _outerZone.run(fn); + + + dynamic _onRunBase(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) { + _nestedRunCounter++; + try { + if (_nestedRunCounter == 1 && _onTurnStart != null) delegate.run(zone, _onTurnStart); + + return fn(); + + } finally { + _nestedRunCounter--; + if (_nestedRunCounter == 0 && _onTurnDone != null) _finishTurn(zone, delegate); + } + } + + dynamic _onRun(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) => + _onRunBase(self, delegate, zone, () => delegate.run(zone, fn)); + + dynamic _onRunUnary(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn(args), args) => + _onRunBase(self, delegate, zone, () => delegate.runUnary(zone, fn, args)); + + void _finishTurn(zone, delegate) { + delegate.run(zone, _onTurnDone); + } + + _onMicrotask(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn) { + if (this._onScheduleMicrotask != null) { + this._onScheduleMicrotask(fn); + } else { + delegate.scheduleMicrotask(zone, fn); + } + } +} diff --git a/modules/core/src/zone/vm_turn_zone.es6 b/modules/core/src/zone/vm_turn_zone.es6 new file mode 100644 index 0000000000..4a00dafc66 --- /dev/null +++ b/modules/core/src/zone/vm_turn_zone.es6 @@ -0,0 +1,52 @@ +import {List, ListWrapper} from 'facade/collection'; +import {normalizeBlank} from 'facade/lang'; + +export class VmTurnZone { + _outerZone; + _innerZone; + + _onTurnStart:Function; + _onTurnDone:Function; + + _nestedRunCounter:number; + + constructor() { + this._nestedRunCounter = 0; + this._onTurnStart = null; + this._onTurnDone = null; + + this._outerZone = window.zone; + this._innerZone = this._outerZone.fork({ + beforeTask: () => this._beforeTask(), + afterTask: () => this._afterTask() + }); + } + + initCallbacks({onTurnStart, onTurnDone, onScheduleMicrotask} = {}) { + this._onTurnStart = normalizeBlank(onTurnStart); + this._onTurnDone = normalizeBlank(onTurnDone); + } + + run(fn) { + return this._innerZone.run(fn); + } + + runOutsideAngular(fn) { + return this._outerZone.run(fn); + } + + + _beforeTask(){ + this._nestedRunCounter ++; + if(this._nestedRunCounter === 1 && this._onTurnStart) { + this._onTurnStart(); + } + } + + _afterTask(){ + this._nestedRunCounter --; + if(this._nestedRunCounter === 0 && this._onTurnDone) { + this._onTurnDone(); + } + } +} \ No newline at end of file diff --git a/modules/core/test/zone/vm_turn_zone_spec.js b/modules/core/test/zone/vm_turn_zone_spec.js new file mode 100644 index 0000000000..c5758dbd9b --- /dev/null +++ b/modules/core/test/zone/vm_turn_zone_spec.js @@ -0,0 +1,85 @@ +import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, async, tick} from 'test_lib/test_lib'; +import {Log, once} from 'test_lib/utils'; +import {PromiseWrapper} from 'facade/async'; +import {BaseException} from 'facade/lang'; +import {VmTurnZone} from 'core/zone/vm_turn_zone'; + +export function main() { + describe("VmTurnZone", () => { + var log, zone; + + beforeEach(() => { + log = new Log(); + zone = new VmTurnZone(); + zone.initCallbacks({ + onTurnStart: log.fn('onTurnStart'), + onTurnDone: log.fn('onTurnDone') + }); + }); + + describe("run", () => { + it('should call onTurnStart and onTurnDone', () => { + zone.run(log.fn('run')); + + expect(log.result()).toEqual('onTurnStart; run; onTurnDone'); + }); + + it('should return the body return value from run', () => { + expect(zone.run(() => 6)).toEqual(6); + }); + + it('should not run onTurnStart and onTurnDone for nested Zone.run', () => { + zone.run(() => { + zone.run(log.fn('run')); + }); + expect(log.result()).toEqual('onTurnStart; run; onTurnDone'); + }); + + + it('should call onTurnStart and onTurnDone before and after each top-level run', () => { + zone.run(log.fn('run1')); + zone.run(log.fn('run2')); + + expect(log.result()).toEqual('onTurnStart; run1; onTurnDone; onTurnStart; run2; onTurnDone'); + }); + + + it('should call onTurnStart and onTurnDone before and after each turn', (done) => { + var a = PromiseWrapper.completer(); + var b = PromiseWrapper.completer(); + + zone.run(() => { + log.add('run start'); + a.promise.then((_) => log.add('a then')); + b.promise.then((_) => log.add('b then')); + }); + + a.complete("a"); + b.complete("b"); + + PromiseWrapper.all([a.promise, b.promise]).then((_) => { + expect(log.result()).toEqual('onTurnStart; run start; onTurnDone; onTurnStart; a then; onTurnDone; onTurnStart; b then; onTurnDone'); + done(); + }); + }); + }); + + describe("runOutsideAngular", () => { + it("should run a function outside of the angular zone", () => { + zone.runOutsideAngular(log.fn('run')); + + expect(log.result()).toEqual('run'); + }); + }); + + describe("exceptions", () => { + it('should rethrow exceptions from the body', () => { + expect(() => { + zone.run(() => { + throw new BaseException('hello'); + }); + }).toThrowError('hello'); + }); + }); + }); +} diff --git a/modules/facade/src/async.dart b/modules/facade/src/async.dart index 2e1e6ae746..c44523f9d0 100644 --- a/modules/facade/src/async.dart +++ b/modules/facade/src/async.dart @@ -20,4 +20,17 @@ class PromiseWrapper { if (success == null) return promise.catchError(onError); return promise.then(success, onError: onError); } + + static completer(){ + return new _Completer(new Completer()); + } +} + +class _Completer { + Completer c; + _Completer(this.c); + + get promise => c.future; + get complete => c.complete; + } diff --git a/modules/facade/src/async.es6 b/modules/facade/src/async.es6 index 49cf45e0e3..89c778f58a 100644 --- a/modules/facade/src/async.es6 +++ b/modules/facade/src/async.es6 @@ -17,4 +17,20 @@ export class PromiseWrapper { static then(promise:Promise, success:Function, rejection:Function):Promise { return promise.then(success, rejection); } + + static completer() { + var resolve; + var reject; + + var p = new Promise(function(res, rej) { + resolve = res; + reject = rej; + }); + + return { + promise: p, + complete: resolve, + reject: reject + }; + } } \ No newline at end of file diff --git a/modules/facade/src/collection.dart b/modules/facade/src/collection.dart index 113ea9d150..f5d76e0452 100644 --- a/modules/facade/src/collection.dart +++ b/modules/facade/src/collection.dart @@ -97,6 +97,7 @@ class ListWrapper { static void insert(List l, int index, value) { l.insert(index, value); } static void removeAt(List l, int index) { l.removeAt(index); } static void clear(List l) { l.clear(); } + static String join(List l, String s) => l.join(s); } bool isListLikeIterable(obj) => obj is Iterable; diff --git a/modules/facade/src/collection.es6 b/modules/facade/src/collection.es6 index 67571b571b..00053eb6ee 100644 --- a/modules/facade/src/collection.es6 +++ b/modules/facade/src/collection.es6 @@ -121,11 +121,16 @@ export class ListWrapper { list.splice(index, 0, value); } static removeAt(list, index:int) { + var res = list[index]; list.splice(index, 1); + return res; } static clear(list) { list.splice(0, list.length); } + static join(list, s) { + return list.join(s); + } } export function isListLikeIterable(obj):boolean { diff --git a/modules/test_lib/src/test_lib.dart b/modules/test_lib/src/test_lib.dart index c116f17123..c6692d1c69 100644 --- a/modules/test_lib/src/test_lib.dart +++ b/modules/test_lib/src/test_lib.dart @@ -66,4 +66,4 @@ _handleAsync(fn) { } return fn; -} +} \ No newline at end of file diff --git a/modules/test_lib/src/test_lib.es6 b/modules/test_lib/src/test_lib.es6 index 61319f1d50..48e35aae7c 100644 --- a/modules/test_lib/src/test_lib.es6 +++ b/modules/test_lib/src/test_lib.es6 @@ -77,7 +77,6 @@ window.beforeEach(function() { }); }); - function mapToString(m) { if (!m) { return ''+m; diff --git a/modules/test_lib/src/utils.js b/modules/test_lib/src/utils.js new file mode 100644 index 0000000000..547d072f3e --- /dev/null +++ b/modules/test_lib/src/utils.js @@ -0,0 +1,23 @@ +import {List, ListWrapper} from 'facade/collection'; + +export class Log { + _result:List; + + constructor() { + this._result = []; + } + + add(value) { + ListWrapper.push(this._result, value); + } + + fn(value) { + return () => { + ListWrapper.push(this._result, value); + } + } + + result() { + return ListWrapper.join(this._result, "; "); + } +} diff --git a/modules/test_lib/test/test_lib_spec.js b/modules/test_lib/test/test_lib_spec.js index 0ae6f07f6c..e5bc44d3d0 100644 --- a/modules/test_lib/test/test_lib_spec.js +++ b/modules/test_lib/test/test_lib_spec.js @@ -1,5 +1,6 @@ -import {describe, it, iit, ddescribe, expect} from 'test_lib/test_lib'; -import {MapWrapper} from 'facade/collection'; +import {describe, it, iit, ddescribe, expect, tick, async} from 'test_lib/test_lib'; +import {MapWrapper, ListWrapper} from 'facade/collection'; +import {PromiseWrapper} from 'facade/async'; class TestObj { prop; diff --git a/package.json b/package.json index 994c240eae..67757d8dba 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "systemjs": "^0.9.1", "through2": "^0.6.1", "traceur": "0.0.74", - "which": "~1" + "which": "~1", + "zone.js": "0.3.0" }, "devDependencies": { "bower": "^1.3.12",