feat(VmTurnZone): Rework the implementation to minimize change detection runs

Before this PR there were only 2 zones: root zone = outer zone > inner
zone.
This PR creates the outer zone as a fork of the root zone: root > outer
> inner.

By doing this it is possible to detected microtasks scheduling in the
outer zone and run the change detection less often (no more than one
time per VM turn).

The PR also introduce a Promise monkey patch for the JS implementation.
It makes Promises aware of microtasks and again allow running the change
detection only once per turn.
This commit is contained in:
Victor Berchet 2015-04-10 12:42:33 +02:00
parent 358a6750ed
commit e8a6c95e2a
13 changed files with 2587 additions and 251 deletions

View File

@ -12,6 +12,12 @@ module.exports = function(config) {
// Loaded through the es6-module-loader, in `test-main.js`.
{pattern: 'dist/js/dev/es5/**', included: false, watched: false},
// Promise monkey patch & zone should be included first
'zone/es6-promise.js',
'zone/zone.js',
'zone/inner-zone.js',
'zone/long-stack-trace-zone.js',
'node_modules/traceur/bin/traceur-runtime.js',
'node_modules/es6-module-loader/dist/es6-module-loader-sans-promises.src.js',
// Including systemjs because it defines `__eval`, which produces correct stack traces.
@ -20,9 +26,6 @@ module.exports = function(config) {
'node_modules/systemjs/lib/extension-cjs.js',
'node_modules/rx/dist/rx.js',
'node_modules/reflect-metadata/Reflect.js',
'node_modules/zone.js/zone.js',
'node_modules/zone.js/long-stack-trace-zone.js',
'tools/build/file2modulename.js',
'test-main.js'
],

View File

@ -1,6 +1,6 @@
library angular.zone;
import 'dart:async' as async;
import 'dart:async';
import 'package:stack_trace/stack_trace.dart' show Chain;
/**
@ -15,49 +15,97 @@ import 'package:stack_trace/stack_trace.dart' show Chain;
* The wrapper maintains an "inner" and "outer" `Zone`. The application code will executes
* in the "inner" zone unless `runOutsideAngular` is explicitely called.
*
* A typical application will create a singleton `VmTurnZone` whose outer `Zone` is the root `Zone`
* and whose default `onTurnDone` runs the Angular digest.
* A typical application will create a singleton `VmTurnZone`. The outer `Zone` is a fork of the root
* `Zone`. The default `onTurnDone` runs the Angular change detection.
*/
class VmTurnZone {
Function _onTurnStart;
Function _onTurnDone;
Function _onScheduleMicrotask;
Function _onErrorHandler;
async.Zone _outerZone;
async.Zone _innerZone;
// Code executed in _outerZone does not trigger the onTurnDone.
Zone _outerZone;
// _innerZone is the child of _outerZone. Any code executed in this zone will trigger the
// onTurnDone hook at the end of the current VM turn.
Zone _innerZone;
int _nestedRunCounter;
// Number of microtasks pending from _outerZone (& 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
// }); // we should only check for the end of a turn once the top-level run ends
int _nestedRun = 0;
/**
* Associates with this
*
* - an "outer" `Zone`, which is the one that created this.
* - an "inner" `Zone`, which is a child of the outer `Zone`.
* - an "outer" [Zone], which is a child of 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}) {
_nestedRunCounter = 0;
_outerZone = async.Zone.current;
_innerZone = _createInnerZoneWithErrorHandling(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.
if (enableLongStackTrace) {
_outerZone = Chain.capture(
() {
return Zone.current.fork(
specification: new ZoneSpecification(
scheduleMicrotask: _scheduleMicrotask,
run: _outerRun,
runUnary: _outerRunUnary,
runBinary: _outerRunBinary
),
zoneValues: {'_name': 'outer'}
);
}, onError: _onErrorWithLongStackTrace);
} else {
_outerZone = Zone.current.fork(
specification: new ZoneSpecification(
scheduleMicrotask: _scheduleMicrotask,
run: _outerRun,
runUnary: _outerRunUnary,
runBinary: _outerRunBinary,
handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, error, StackTrace trace) =>
_onErrorWithoutLongStackTrace(error, trace)
),
zoneValues: {'_name': 'outer'}
);
}
// 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'});
}
/**
* Initializes the zone hooks.
*
* The given error handler should re-throw the passed exception. Otherwise, exceptions will not
* propagate outside of the [VmTurnZone] and can alter the application execution flow.
* Not re-throwing could be used to help testing the code or advanced use cases.
*
* @param {Function} onTurnStart called before code executes in the inner zone for each VM turn
* @param {Function} onTurnDone called at the end of a VM turn if code has executed in the inner zone
* @param {Function} onScheduleMicrotask
* @param {Function} onErrorHandler called when an exception is thrown by a macro or micro task
*/
initCallbacks({Function onTurnStart, Function onTurnDone,
Function onScheduleMicrotask, Function onErrorHandler}) {
this._onTurnStart = onTurnStart;
this._onTurnDone = onTurnDone;
this._onScheduleMicrotask = onScheduleMicrotask;
this._onErrorHandler = onErrorHandler;
void initCallbacks({Function onTurnStart, Function onTurnDone, Function onErrorHandler}) {
_onTurnStart = onTurnStart;
_onTurnDone = onTurnDone;
_onErrorHandler = onErrorHandler;
}
/**
@ -76,7 +124,12 @@ class VmTurnZone {
* }
* ```
*/
dynamic run(fn()) => _innerZone.run(fn);
dynamic run(fn()) {
// Using runGuarded() is required when executing sync code with Dart otherwise handleUncaughtError()
// would not be called on exceptions.
// see https://code.google.com/p/dart/issues/detail?id=19566 for details.
return _innerZone.runGuarded(fn);
}
/**
* Runs `fn` in the outer zone and returns whatever it returns.
@ -97,81 +150,94 @@ class VmTurnZone {
* }
* ```
*/
dynamic runOutsideAngular(fn()) => _outerZone.run(fn);
async.Zone _createInnerZoneWithErrorHandling(bool enableLongStackTrace) {
if (enableLongStackTrace) {
return Chain.capture(() {
return _createInnerZone(async.Zone.current);
}, onError: this._onErrorWithLongStackTrace);
} else {
return async.runZoned(() {
return _createInnerZone(async.Zone.current);
}, onError: this._onErrorWithoutLongStackTrace);
}
dynamic runOutsideAngular(fn()) {
return _outerZone.runGuarded(fn);
}
async.Zone _createInnerZone(async.Zone zone) {
return zone.fork(
specification: new async.ZoneSpecification(
run: _onRun,
runUnary: _onRunUnary,
scheduleMicrotask: _onMicrotask));
// 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 _onRunBase(
async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) {
_nestedRunCounter++;
try {
if (_nestedRunCounter == 1 && _onTurnStart != null) delegate.run(
zone, _onTurnStart);
return fn();
} catch (e, s) {
if (_onErrorHandler != null && _nestedRunCounter == 1) {
_onErrorHandler(e, [s.toString()]);
} else {
rethrow;
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) {
if (!_hasExecutedCodeInInnerZone) {
_hasExecutedCodeInInnerZone = true;
if (_onTurnStart != null) {
_inTurnStart = true;
parent.run(zone, _onTurnStart);
}
}
}
dynamic _outerRun(Zone self, ZoneDelegate parent, Zone zone, fn()) {
try {
_nestedRun++;
return parent.run(zone, fn);
} finally {
_nestedRunCounter--;
if (_nestedRunCounter == 0 && _onTurnDone != null) _finishTurn(
zone, delegate);
_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 (_pendingMicrotasks == 0 && _nestedRun == 0) {
if (_onTurnDone != null && !_inTurnStart && _hasExecutedCodeInInnerZone) {
// Trigger onTurnDone at the end of a turn if _innerZone has executed some code
try {
parent.run(_innerZone, _onTurnDone);
} finally {
_hasExecutedCodeInInnerZone = false;
}
}
}
_inTurnStart = false;
}
}
dynamic _onRun(async.Zone self, async.ZoneDelegate delegate, async.Zone zone,
fn()) => _onRunBase(self, delegate, zone, () => delegate.run(zone, fn));
dynamic _outerRunUnary(Zone self, ZoneDelegate parent, Zone zone, fn(arg), arg) =>
_outerRun(self, parent, zone, () => fn(arg));
dynamic _onRunUnary(async.Zone self, async.ZoneDelegate delegate,
async.Zone zone, fn(args), args) =>
_onRunBase(self, delegate, zone, () => delegate.runUnary(zone, fn, args));
dynamic _outerRunBinary(Zone self, ZoneDelegate parent, Zone zone, fn(arg1, arg2), arg1, arg2) =>
_outerRun(self, parent, zone, () => fn(arg1, arg2));
void _finishTurn(zone, delegate) {
delegate.run(zone, _onTurnDone);
void _scheduleMicrotask(Zone self, ZoneDelegate parent, Zone zone, fn) {
_pendingMicrotasks++;
var microtask = () {
try {
fn();
} finally {
_pendingMicrotasks--;
}
};
parent.scheduleMicrotask(zone, microtask);
}
_onMicrotask(
async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn) {
if (this._onScheduleMicrotask != null) {
_onScheduleMicrotask(fn);
} else {
delegate.scheduleMicrotask(zone, fn);
}
}
_onErrorWithLongStackTrace(exception, Chain chain) {
final traces = chain.terse.traces.map((t) => t.toString()).toList();
_onError(exception, traces, chain.traces[0]);
}
_onErrorWithoutLongStackTrace(exception, StackTrace trace) {
_onError(exception, [trace.toString()], trace);
}
_onError(exception, List<String> traces, StackTrace singleTrace) {
// Called by Chain.capture() on errors when long stack traces are enabled
void _onErrorWithLongStackTrace(error, Chain chain) {
if (_onErrorHandler != null) {
_onErrorHandler(exception, traces);
final traces = chain.terse.traces.map((t) => t.toString()).toList();
_onErrorHandler(error, traces);
} else {
_outerZone.handleUncaughtError(exception, singleTrace);
throw error;
}
}
// Outer zone handleUnchaughtError when long stack traces are not used
void _onErrorWithoutLongStackTrace(error, StackTrace trace) {
if (_onErrorHandler != null) {
_onErrorHandler(error, [trace.toString()]);
} else {
throw error;
}
}
}

View File

@ -7,8 +7,8 @@ import {normalizeBlank, isPresent, global} from 'angular2/src/facade/lang';
* The wrapper maintains an "inner" and "outer" `Zone`. The application code will executes
* in the "inner" zone unless `runOutsideAngular` is explicitely called.
*
* A typical application will create a singleton `VmTurnZone` whose outer `Zone` is the root `Zone`
* and whose default `onTurnDone` runs the Angular digest.
* A typical application will create a singleton `VmTurnZone`. The outer `Zone` is a fork of the root
* `Zone`. The default `onTurnDone` runs the Angular change detection.
*
* @exportedAs angular2/core
*/
@ -20,25 +20,30 @@ export class VmTurnZone {
_onTurnDone:Function;
_onErrorHandler:Function;
_nestedRunCounter:number;
/**
* Associates with this
*
* - an "outer" zone, which is the one that created this.
* - an "outer" zone, which is a child of 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.
*/
constructor({enableLongStackTrace}) {
this._nestedRunCounter = 0;
this._onTurnStart = null;
this._onTurnDone = null;
this._onErrorHandler = null;
Zone._hasExecutedInnerCode = false;
this._outerZone = global.zone;
this._outerZone = global.zone.fork({
_name: 'outer',
beforeTurn: () => { this._beforeTurn(); },
afterTurn: () => { this._afterTurn(); }
});
this._innerZone = this._createInnerZone(this._outerZone, enableLongStackTrace);
// TODO('remove');
Zone.debug = true;
}
/**
@ -46,10 +51,9 @@ export class VmTurnZone {
*
* @param {Function} onTurnStart called before code executes in the inner zone for each VM turn
* @param {Function} onTurnDone called at the end of a VM turn if code has executed in the inner zone
* @param {Function} onScheduleMicrotask
* @param {Function} onErrorHandler called when an exception is thrown by a macro or micro task
*/
initCallbacks({onTurnStart, onTurnDone, onScheduleMicrotask, onErrorHandler} = {}) {
initCallbacks({onTurnStart, onTurnDone, onErrorHandler} = {}) {
this._onTurnStart = normalizeBlank(onTurnStart);
this._onTurnDone = normalizeBlank(onTurnDone);
this._onErrorHandler = normalizeBlank(onErrorHandler);
@ -62,10 +66,10 @@ export class VmTurnZone {
* Angular's auto digest mechanism.
*
* ```
* var zone: VmTurnZone = <ref to the application zone>;
* var zone: VmTurnZone = [ref to the application zone];
*
* zone.run(() => {
* // auto-digest will run after this function is called from JS
* // the change detection will run after this function and the microtasks it enqueues have executed.
* });
* ```
*/
@ -80,7 +84,7 @@ export class VmTurnZone {
* auto-digest mechanism.
*
* ```
* var zone: VmTurnZone = <ref to the application zone>;
* var zone: VmTurnZone = [ref to the application zone];
*
* zone.runOusideAngular(() => {
* element.onClick(() => {
@ -111,23 +115,39 @@ export class VmTurnZone {
};
}
return zone.fork(errorHandling).fork({
beforeTask: () => {this._beforeTask()},
afterTask: () => {this._afterTask()}
});
return zone
.fork(errorHandling)
.fork({
'$run': function (parentRun) {
return function () {
if (!Zone._hasExecutedInnerCode) {
// Execute the beforeTurn hook when code is first executed in the inner zone in the turn
Zone._hasExecutedInnerCode = true;
var oldZone = global.zone;
global.zone = this;
this.beforeTurn();
global.zone = oldZone;
}
return parentRun.apply(this, arguments);
}
},
_name: 'inner'
});
}
_beforeTask(){
this._nestedRunCounter ++;
if(this._nestedRunCounter === 1 && this._onTurnStart) {
this._onTurnStart();
}
_beforeTurn() {
this._onTurnStart && this._onTurnStart();
}
_afterTask(){
this._nestedRunCounter --;
if(this._nestedRunCounter === 0 && this._onTurnDone) {
this._onTurnDone();
_afterTurn() {
if (this._onTurnDone) {
if (Zone._hasExecutedInnerCode) {
// Execute the onTurnDone hook in the inner zone so that microtasks are enqueued there
// The hook gets executed when code has runned in the inner zone during the current turn
this._innerZone.run(this._onTurnDone, this);
Zone._hasExecutedInnerCode = false;
}
}
}

View File

@ -225,3 +225,5 @@ String elementText(n) {
return DOM.getText(n);
}
String getCurrentZoneName() => Zone.current['_name'];

View File

@ -135,11 +135,15 @@ function _it(jsmFn, name, fn) {
if (!(fn instanceof FunctionWithParamTokens)) {
fn = inject([], fn);
}
inIt = true;
fn.execute(injector);
inIt = false;
if (!async) done();
// Use setTimeout() to run tests in a macrotasks.
// Without this, tests would be executed in the context of a microtasks (a promise .then).
setTimeout(() => {
inIt = true;
fn.execute(injector);
inIt = false;
if (!async) done();
}, 0);
});
}
@ -351,3 +355,7 @@ function elementText(n) {
return DOM.getText(n);
}
function getCurrentZoneName(): string {
return zone._name;
}

View File

@ -9,171 +9,622 @@ import {
it,
xdescribe,
xit,
Log
} from 'angular2/test_lib';
import {Log, once} from 'angular2/test_lib';
import {PromiseWrapper} from 'angular2/src/facade/async';
import {ListWrapper} from 'angular2/src/facade/collection';
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));
}
// Schedules a microtasks (using a resolved promise .then())
function microTask(fn: Function): void {
PromiseWrapper.resolve(null).then((_) => { fn(); });
}
var _log;
var _errors;
var _traces;
var _zone;
function logError(error, stackTrace) {
ListWrapper.push(_errors, error);
ListWrapper.push(_traces, stackTrace);
}
export function main() {
describe("VmTurnZone", () => {
var log, zone;
function createZone(enableLongStackTrace) {
var zone = new VmTurnZone({enableLongStackTrace: enableLongStackTrace});
zone.initCallbacks({
onTurnStart: _log.fn('onTurnStart'),
onTurnDone: _log.fn('onTurnDone')
});
return zone;
}
beforeEach(() => {
log = new Log();
zone = new VmTurnZone({enableLongStackTrace: true});
zone.initCallbacks({
onTurnStart: log.fn('onTurnStart'),
onTurnDone: log.fn('onTurnDone')
});
_log = new Log();
_errors = [];
_traces = [];
});
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', inject([AsyncTestCompleter], (async) => {
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.resolve("a");
b.resolve("b");
PromiseWrapper.all([a.promise, b.promise]).then((_) => {
expect(log.result()).toEqual('onTurnStart; run start; onTurnDone; onTurnStart; a then; onTurnDone; onTurnStart; b then; onTurnDone');
async.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", () => {
var trace, exception, saveStackTrace;
describe('long stack trace', () => {
beforeEach(() => {
trace = null;
exception = null;
saveStackTrace = (e, t) => {
exception = e;
trace = t;
};
_zone = createZone(true);
});
it('should call the on error callback when it is defined', () => {
zone.initCallbacks({onErrorHandler: saveStackTrace});
commonTests();
zone.run(() => {
throw new BaseException('aaa');
});
it('should produce long stack traces', inject([AsyncTestCompleter],
(async) => {
macroTask(() => {
_zone.initCallbacks({onErrorHandler: logError});
var c = PromiseWrapper.completer();
expect(exception).toBeDefined();
});
it('should rethrow exceptions from the body when no callback defined', () => {
expect(() => {
zone.run(() => {
throw new BaseException('bbb');
});
}).toThrowError('bbb');
});
it('should produce long stack traces', inject([AsyncTestCompleter], (async) => {
zone.initCallbacks({onErrorHandler: saveStackTrace});
var c = PromiseWrapper.completer();
zone.run(function () {
PromiseWrapper.setTimeout(function () {
PromiseWrapper.setTimeout(function () {
c.resolve(null);
throw new BaseException('ccc');
_zone.run(() => {
PromiseWrapper.setTimeout(() => {
PromiseWrapper.setTimeout(() => {
c.resolve(null);
throw new BaseException('ccc');
}, 0);
}, 0);
}, 0);
});
});
c.promise.then((_) => {
// then number of traces for JS and Dart is different
expect(trace.length).toBeGreaterThan(1);
async.done();
c.promise.then((_) => {
expect(_traces.length).toBe(1);
expect(_traces[0].length).toBeGreaterThan(1);
async.done();
});
});
}));
it('should produce long stack traces (when using promises)', inject([AsyncTestCompleter], (async) => {
zone.initCallbacks({onErrorHandler: saveStackTrace});
it('should produce long stack traces (when using microtasks)', inject(
[AsyncTestCompleter], (async) => {
macroTask(() => {
_zone.initCallbacks({onErrorHandler: logError});
var c = PromiseWrapper.completer();
var c = PromiseWrapper.completer();
zone.run(function () {
PromiseWrapper.resolve(null).then((_) => {
return PromiseWrapper.resolve(null).then((__) => {
c.resolve(null);
throw new BaseException("ddd");
_zone.run(() => {
microTask(() => {
microTask(() => {
c.resolve(null);
throw new BaseException("ddd");
});
});
});
});
c.promise.then((_) => {
// then number of traces for JS and Dart is different
expect(trace.length).toBeGreaterThan(1);
async.done();
c.promise.then((_) => {
expect(_traces.length).toBe(1);
expect(_traces[0].length).toBeGreaterThan(1);
async.done();
});
});
}));
});
describe('short stack trace', () => {
beforeEach(() => {
_zone = createZone(false);
});
commonTests();
it('should disable long stack traces', inject([AsyncTestCompleter], (async) => {
var zone = new VmTurnZone({enableLongStackTrace: false});
zone.initCallbacks({onErrorHandler: saveStackTrace});
macroTask(() => {
_zone.initCallbacks({onErrorHandler: logError});
var c = PromiseWrapper.completer();
var c = PromiseWrapper.completer();
zone.run(function () {
PromiseWrapper.setTimeout(function () {
PromiseWrapper.setTimeout(function () {
c.resolve(null);
throw new BaseException('ccc');
_zone.run(() => {
PromiseWrapper.setTimeout(() => {
PromiseWrapper.setTimeout(() => {
c.resolve(null);
throw new BaseException('ccc');
}, 0);
}, 0);
}, 0);
});
});
c.promise.then((_) => {
expect(trace.length).toEqual(1);
async.done();
c.promise.then((_) => {
expect(_traces.length).toBe(1);
expect(_traces[0].length).toEqual(1);
async.done();
});
});
}));
});
});
}
function commonTests() {
describe("run", () => {
it('should return the body return value from run', inject([AsyncTestCompleter], (async) => {
macroTask(() => {
expect(_zone.run(() => {
return 6;
})).toEqual(6);
});
macroTask(() => {
async.done();
});
}));
it('should call onTurnStart and onTurnDone', inject([AsyncTestCompleter], (async) => {
macroTask(() => {
_zone.run(_log.fn('run'));
});
macroTask(() => {
expect(_log.result()).toEqual('onTurnStart; run; onTurnDone');
async.done();
});
}));
it('should call onTurnStart once before a turn and onTurnDone once after the turn',
inject([AsyncTestCompleter], (async) => {
macroTask(() => {
_zone.run(() => {
_log.add('run start');
microTask(() => { _log.add('async'); });
_log.add('run end');
});
});
macroTask(() => {
// The microtask (async) is executed after the macrotask (run)
expect(_log.result()).toEqual('onTurnStart; run start; run end; async; onTurnDone');
async.done();
});
}));
it('should not run onTurnStart and onTurnDone for nested Zone.run',
inject([AsyncTestCompleter], (async) => {
macroTask(() => {
_zone.run(() => {
_log.add('start run');
_zone.run(() => {
_log.add('nested run');
microTask(() => _log.add('nested run microtask'));
});
_log.add('end run');
});
});
macroTask(() => {
expect(_log.result()).toEqual('onTurnStart; start run; nested run; end run; nested run microtask; onTurnDone');
async.done();
});
}));
it('should call onTurnStart and onTurnDone before and after each top-level run',
inject([AsyncTestCompleter], (async) => {
macroTask(() => {
_zone.run(_log.fn('run1'));
_zone.run(_log.fn('run2'));
});
macroTask(() => {
_zone.run(_log.fn('run3'));
});
macroTask(() => {
expect(_log.result()).toEqual('onTurnStart; run1; run2; onTurnDone; onTurnStart; run3; onTurnDone');
async.done();
});
}));
it('should call onTurnStart and onTurnDone before and after each turn',
inject([AsyncTestCompleter], (async) => {
var a;
var b;
macroTask(() => {
a = PromiseWrapper.completer();
b = PromiseWrapper.completer();
_zone.run(() => {
_log.add('run start');
a.promise.then((_) => {
return _log.add('a then');
});
b.promise.then((_) => {
return _log.add('b then');
});
});
});
macroTask(() => {
a.resolve('a');
b.resolve('b');
});
macroTask(() => {
expect(_log.result()).toEqual('onTurnStart; run start; onTurnDone; onTurnStart; a then; b then; onTurnDone');
async.done();
});
}));
it('should run a function outside of the angular zone', inject([AsyncTestCompleter], (async) => {
macroTask(() => {
_zone.runOutsideAngular(_log.fn('run'));
});
macroTask(() => {
expect(_log.result()).toEqual('run');
async.done()
});
}));
it('should call onTurnStart and onTurnDone when an inner microtask is scheduled from outside angular',
inject([AsyncTestCompleter], (async) => {
var completer;
macroTask(() => {
_zone.runOutsideAngular(() => {
completer = PromiseWrapper.completer();
});
});
macroTask(() => {
_zone.run(() => {
completer.promise.then((_) => {
_log.add('executedMicrotask');
});
});
});
macroTask(() => {
_zone.runOutsideAngular(() => {
_log.add('scheduling a microtask');
completer.resolve(null);
});
});
macroTask(() => {
expect(_log.result()).toEqual(
// First VM turn => setup Promise then
'onTurnStart; onTurnDone; ' +
// Second VM turn (outside of anguler)
'scheduling a microtask; ' +
// Third VM Turn => execute the microtask (inside angular)
'onTurnStart; executedMicrotask; onTurnDone'
);
async.done();
});
}));
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;
_zone.initCallbacks({
onTurnStart: _log.fn('onTurnStart'),
onTurnDone: () => {
_log.add('onTurnDone(begin)');
if (!ran) {
microTask(() => {
ran = true;
_log.add('executedMicrotask');});
}
_log.add('onTurnDone(end)');
}});
macroTask(() => {
_zone.run(() => {
_log.add('run');
});
});
macroTask(() => {
expect(_log.result()).toEqual(
// First VM turn => 'run' macrotask
'onTurnStart; run; onTurnDone(begin); onTurnDone(end); ' +
// Second VM Turn => microtask enqueued from onTurnDone
'onTurnStart; executedMicrotask; onTurnDone(begin); onTurnDone(end)'
);
async.done();
});
}));
it('should call onTurnStart and onTurnDone for a scheduleMicrotask in onTurnDone triggered by ' +
'a scheduleMicrotask in run', inject([AsyncTestCompleter], (async) => {
var ran = false;
_zone.initCallbacks({
onTurnStart: _log.fn('onTurnStart'),
onTurnDone: () => {
_log.add('onTurnDone(begin)');
if (!ran) {
_log.add('onTurnDone(scheduleMicrotask)');
microTask(() => {
ran = true;
_log.add('onTurnDone(executeMicrotask)');
});
}
_log.add('onTurnDone(end)');
}});
macroTask(() => {
_zone.run(() => {
_log.add('scheduleMicrotask');
microTask(() => { _log.add('run(executeMicrotask)'); });
});
});
macroTask(() => {
expect(_log.result()).toEqual(
// First VM Turn => a macrotask + the microtask it enqueues
'onTurnStart; scheduleMicrotask; run(executeMicrotask); onTurnDone(begin); onTurnDone(scheduleMicrotask); onTurnDone(end); ' +
// Second VM Turn => the microtask enqueued from onTurnDone
'onTurnStart; onTurnDone(executeMicrotask); onTurnDone(begin); onTurnDone(end)'
);
async.done();
});
}));
it('should execute promises scheduled in onTurnStart before promises scheduled in run',
inject([AsyncTestCompleter], (async) => {
var donePromiseRan = false;
var startPromiseRan = false;
_zone.initCallbacks({
onTurnStart: () => {
_log.add('onTurnStart(begin)');
if (!startPromiseRan) {
_log.add('onTurnStart(schedulePromise)');
microTask(() => { _log.add('onTurnStart(executePromise)'); });
startPromiseRan = true;
}
_log.add('onTurnStart(end)');
},
onTurnDone: () => {
_log.add('onTurnDone(begin)');
if (!donePromiseRan) {
_log.add('onTurnDone(schedulePromise)');
microTask(() => { _log.add('onTurnDone(executePromise)'); });
donePromiseRan = true;
}
_log.add('onTurnDone(end)');
}});
macroTask(() => {
_zone.run(() => {
_log.add('run start');
PromiseWrapper.resolve(null)
.then((_) => {
_log.add('promise then');
PromiseWrapper.resolve(null).then((_) => { _log.add('promise foo'); });
return PromiseWrapper.resolve(null);
})
.then((_) => {
_log.add('promise bar');
});
_log.add('run end');
});
});
macroTask(() => {
expect(_log.result()).toEqual(
// First VM turn: enqueue a microtask in onTurnStart
'onTurnStart(begin); onTurnStart(schedulePromise); onTurnStart(end); ' +
// First VM turn: execute the macrotask which enqueues microtasks
'run start; run end; ' +
// First VM turn: execute enqueued microtasks
'onTurnStart(executePromise); promise then; promise foo; promise bar; ' +
// First VM turn: onTurnEnd, enqueue a microtask
'onTurnDone(begin); onTurnDone(schedulePromise); onTurnDone(end); ' +
// Second VM turn: execute the microtask from onTurnEnd
'onTurnStart(begin); onTurnStart(end); onTurnDone(executePromise); onTurnDone(begin); onTurnDone(end)'
);
async.done();
});
}));
it('should call onTurnStart and onTurnDone before and after each turn, respectively',
inject([AsyncTestCompleter], (async) => {
var completerA, completerB;
macroTask(() => {
_zone.run(() => {
completerA = PromiseWrapper.completer();
completerB = PromiseWrapper.completer();
completerA.promise.then((_) => _log.add('a then'));
completerB.promise.then((_) => _log.add('b then'));
_log.add('run start');
});
});
macroTask(() => {
_zone.run(() => {
completerA.resolve(null);
});
});
macroTask(() => {
_zone.run(() => {
completerB.resolve(null);
});
});
macroTask(() => {
expect(_log.result()).toEqual(
// First VM turn
'onTurnStart; run start; onTurnDone; ' +
// Second VM turn
'onTurnStart; a then; onTurnDone; ' +
// Third VM turn
'onTurnStart; b then; onTurnDone');
async.done();
});
}));
it('should call onTurnStart and onTurnDone before and after (respectively) all turns in a chain',
inject([AsyncTestCompleter], (async) => {
macroTask(() => {
_zone.run(() => {
_log.add('run start');
microTask(() => {
_log.add('async1');
microTask(() => {
_log.add('async2');
});
});
_log.add('run end');
});
});
macroTask(() => {
expect(_log.result()).toEqual('onTurnStart; run start; run end; async1; async2; onTurnDone');
async.done();
});
}));
it('should call onTurnStart and onTurnDone for promises created outside of run body',
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');
});
_log.add('zone run');
});
});
macroTask(() => {
expect(_log.result()).toEqual('onTurnStart; zone run; promise then; onTurnDone');
async.done();
});
}));
});
describe('exceptions', () => {
it('should call the on error callback when it is defined', inject([AsyncTestCompleter], (async) => {
macroTask(() => {
_zone.initCallbacks({onErrorHandler: logError});
var exception = new BaseException('sync');
_zone.run(() => {
throw exception;
});
expect(_errors.length).toBe(1);
expect(_errors[0]).toBe(exception);
async.done();
});
}));
it('should call onError for errors from microtasks', inject([AsyncTestCompleter], (async) => {
_zone.initCallbacks({onErrorHandler: logError});
var exception = new BaseException('async');
macroTask(() => {
_zone.run(() => {
microTask(() => { throw exception; });
});
});
macroTask(() => {
expect(_errors.length).toBe(1);
expect(_errors[0]).toEqual(exception);
async.done();
});
}));
it('should call onError when onTurnDone throws and the zone is sync',
inject([AsyncTestCompleter], (async) => {
var exception = new BaseException('fromOnTurnDone');
_zone.initCallbacks({
onErrorHandler: logError,
onTurnDone: () => { throw exception; }
});
macroTask(() => {
_zone.run(() => { });
});
macroTask(() => {
expect(_errors.length).toBe(1);
expect(_errors[0]).toEqual(exception);
async.done();
});
}));
it('should call onError when onTurnDone throws and the zone is async',
inject([AsyncTestCompleter], (async) => {
var asyncRan = false;
var exception = new BaseException('fromOnTurnDone');
_zone.initCallbacks({
onErrorHandler: logError,
onTurnDone: () => { throw exception; }});
macroTask(() => {
_zone.run(() => {
microTask(() => {
asyncRan = true;
});
});
});
macroTask(() => {
expect(asyncRan).toBe(true);
expect(_errors.length).toBe(1);
expect(_errors[0]).toEqual(exception);
async.done();
});
}));
});
}

View File

@ -1,7 +1,8 @@
<script src="traceur-runtime.js" type="text/javascript"></script>
<script src="es6-module-loader-sans-promises.src.js" type="text/javascript"></script>
<script src="es6-promise.js" type="text/javascript"></script>
<script src="zone.js" type="text/javascript"></script>
<script src="long-stack-trace-zone.js" type="text/javascript"></script>
<script src="traceur-runtime.js" type="text/javascript"></script>
<script src="es6-module-loader-sans-promises.src.js" type="text/javascript"></script>
<script src="system.src.js" type="text/javascript"></script>
<script src="extension-register.js" type="text/javascript"></script>
<script src="extension-cjs.js" type="text/javascript"></script>

View File

@ -1,8 +1,9 @@
<script src="es6-promise.js" type="text/javascript"></script>
<script src="zone.js" type="text/javascript"></script>
<script src="long-stack-trace-zone.js" type="text/javascript"></script>
<script src="url_params_to_form.js" type="text/javascript"></script>
<script src="traceur-runtime.js" type="text/javascript"></script>
<script src="es6-module-loader-sans-promises.src.js" type="text/javascript"></script>
<script src="zone.js" type="text/javascript"></script>
<script src="long-stack-trace-zone.js" type="text/javascript"></script>
<script src="system.src.js" type="text/javascript"></script>
<script src="extension-register.js" type="text/javascript"></script>
<script src="extension-cjs.js" type="text/javascript"></script>

View File

@ -1,9 +1,10 @@
<script src="es6-promise.js" type="text/javascript"></script>
<script src="zone.js" type="text/javascript"></script>
<script src="long-stack-trace-zone.js" type="text/javascript"></script>
<script src="angular.js" type="text/javascript"></script>
<script src="url_params_to_form.js" type="text/javascript"></script>
<script src="traceur-runtime.js" type="text/javascript"></script>
<script src="es6-module-loader-sans-promises.src.js" type="text/javascript"></script>
<script src="zone.js" type="text/javascript"></script>
<script src="long-stack-trace-zone.js" type="text/javascript"></script>
<script src="system.src.js" type="text/javascript"></script>
<script src="extension-register.js" type="text/javascript"></script>
<script src="extension-cjs.js" type="text/javascript"></script>

View File

@ -78,9 +78,10 @@ module.exports = function makeBrowserTree(options, destinationPath) {
var vendorScriptsTree = flatten(new Funnel('.', {
files: [
'zone/es6-promise.js',
'zone/zone.js',
'zone/long-stack-trace-zone.js',
'node_modules/es6-module-loader/dist/es6-module-loader-sans-promises.src.js',
'node_modules/zone.js/zone.js',
'node_modules/zone.js/long-stack-trace-zone.js',
'node_modules/systemjs/dist/system.src.js',
'node_modules/systemjs/lib/extension-register.js',
'node_modules/systemjs/lib/extension-cjs.js',

978
zone/es6-promise.js Normal file
View File

@ -0,0 +1,978 @@
/*!
* @overview es6-promise - a tiny implementation of Promises/A+.
* @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
* @license Licensed under MIT license
* See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
* @version 2.1.1
*/
(function() {
"use strict";
function lib$es6$promise$utils$$objectOrFunction(x) {
return typeof x === 'function' || (typeof x === 'object' && x !== null);
}
function lib$es6$promise$utils$$isFunction(x) {
return typeof x === 'function';
}
function lib$es6$promise$utils$$isMaybeThenable(x) {
return typeof x === 'object' && x !== null;
}
var lib$es6$promise$utils$$_isArray;
if (!Array.isArray) {
lib$es6$promise$utils$$_isArray = function (x) {
return Object.prototype.toString.call(x) === '[object Array]';
};
} else {
lib$es6$promise$utils$$_isArray = Array.isArray;
}
var lib$es6$promise$utils$$isArray = lib$es6$promise$utils$$_isArray;
var lib$es6$promise$utils$$_global;
function lib$es6$promise$utils$$getGlobal() {
if (!lib$es6$promise$utils$$_global) {
if (typeof global !== 'undefined') {
lib$es6$promise$utils$$_global = global;
} else if (typeof self !== 'undefined') {
lib$es6$promise$utils$$_global = self;
} else {
try {
lib$es6$promise$utils$$_global = Function('return this')();
} catch (e) {
throw new Error('polyfill failed because global object is unavailable in this environment');
}
}
}
return lib$es6$promise$utils$$_global;
}
function lib$es6$promise$utils$$getCurrentZone() {
return lib$es6$promise$utils$$getGlobal().zone;
}
function lib$es6$promise$utils$$setCurrentZone(zone) {
lib$es6$promise$utils$$getGlobal().zone = zone;
}
function lib$es6$promise$asap$microtask$$asap(callback, arg) {
lib$es6$promise$utils$$getCurrentZone().scheduleMicrotask(function() {
callback(arg);
});
}
var lib$es6$promise$asap$microtask$$default = lib$es6$promise$asap$microtask$$asap;
function lib$es6$promise$$internal$$noop() {}
var lib$es6$promise$$internal$$PENDING = void 0;
var lib$es6$promise$$internal$$FULFILLED = 1;
var lib$es6$promise$$internal$$REJECTED = 2;
var lib$es6$promise$$internal$$INZONE = 3;
var lib$es6$promise$$internal$$GET_THEN_ERROR = new lib$es6$promise$$internal$$ErrorObject();
function lib$es6$promise$$internal$$selfFullfillment() {
return new TypeError("You cannot resolve a promise with itself");
}
function lib$es6$promise$$internal$$cannotReturnOwn() {
return new TypeError('A promises callback cannot return that same promise.');
}
function lib$es6$promise$$internal$$getThen(promise) {
try {
return promise.then;
} catch(error) {
lib$es6$promise$$internal$$GET_THEN_ERROR.error = error;
return lib$es6$promise$$internal$$GET_THEN_ERROR;
}
}
function lib$es6$promise$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
try {
then.call(value, fulfillmentHandler, rejectionHandler);
} catch(e) {
return e;
}
}
function lib$es6$promise$$internal$$handleForeignThenable(promise, thenable, then) {
lib$es6$promise$asap$microtask$$default(function(promise) {
var sealed = false;
var error = lib$es6$promise$$internal$$tryThen(then, thenable, function(value) {
if (sealed) { return; }
sealed = true;
if (thenable !== value) {
lib$es6$promise$$internal$$resolve(promise, value);
} else {
lib$es6$promise$$internal$$fulfill(promise, value);
}
}, function(reason) {
if (sealed) { return; }
sealed = true;
lib$es6$promise$$internal$$reject(promise, reason);
}, 'Settle: ' + (promise._label || ' unknown promise'));
if (!sealed && error) {
sealed = true;
lib$es6$promise$$internal$$reject(promise, error);
}
}, promise);
}
function lib$es6$promise$$internal$$handleOwnThenable(promise, thenable) {
if (thenable._state === lib$es6$promise$$internal$$FULFILLED) {
lib$es6$promise$$internal$$fulfill(promise, thenable._result);
} else if (thenable._state === lib$es6$promise$$internal$$REJECTED) {
lib$es6$promise$$internal$$reject(promise, thenable._result);
} else {
lib$es6$promise$$internal$$subscribe(thenable, undefined, function(value) {
lib$es6$promise$$internal$$resolve(promise, value);
}, function(reason) {
lib$es6$promise$$internal$$reject(promise, reason);
});
}
}
function lib$es6$promise$$internal$$handleMaybeThenable(promise, maybeThenable) {
if (maybeThenable.constructor === promise.constructor) {
lib$es6$promise$$internal$$handleOwnThenable(promise, maybeThenable);
} else {
var then = lib$es6$promise$$internal$$getThen(maybeThenable);
if (then === lib$es6$promise$$internal$$GET_THEN_ERROR) {
lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$GET_THEN_ERROR.error);
} else if (then === undefined) {
lib$es6$promise$$internal$$fulfill(promise, maybeThenable);
} else if (lib$es6$promise$utils$$isFunction(then)) {
lib$es6$promise$$internal$$handleForeignThenable(promise, maybeThenable, then);
} else {
lib$es6$promise$$internal$$fulfill(promise, maybeThenable);
}
}
}
function lib$es6$promise$$internal$$resolve(promise, value) {
if (promise === value) {
lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$selfFullfillment());
} else if (lib$es6$promise$utils$$objectOrFunction(value)) {
lib$es6$promise$$internal$$handleMaybeThenable(promise, value);
} else {
lib$es6$promise$$internal$$fulfill(promise, value);
}
}
function lib$es6$promise$$internal$$publishRejection(promise) {
if (promise._onerror) {
promise._onerror(promise._result);
}
lib$es6$promise$$internal$$publish(promise);
}
function lib$es6$promise$$internal$$fulfill(promise, value) {
if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; }
promise._result = value;
promise._state = lib$es6$promise$$internal$$FULFILLED;
if (promise._subscribers.length !== 0) {
lib$es6$promise$asap$microtask$$default(lib$es6$promise$$internal$$publish, promise);
}
}
function lib$es6$promise$$internal$$reject(promise, reason) {
if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; }
promise._state = lib$es6$promise$$internal$$REJECTED;
promise._result = reason;
lib$es6$promise$asap$microtask$$default(lib$es6$promise$$internal$$publishRejection, promise);
}
function lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
var subscribers = parent._subscribers;
var length = subscribers.length;
parent._onerror = null;
subscribers[length] = child;
subscribers[length + lib$es6$promise$$internal$$FULFILLED] = onFulfillment;
subscribers[length + lib$es6$promise$$internal$$REJECTED] = onRejection;
subscribers[length + lib$es6$promise$$internal$$INZONE] = lib$es6$promise$utils$$getCurrentZone();
if (length === 0 && parent._state) {
lib$es6$promise$asap$microtask$$default(lib$es6$promise$$internal$$publish, parent);
}
}
function lib$es6$promise$$internal$$publish(promise) {
var subscribers = promise._subscribers;
var settled = promise._state;
if (subscribers.length === 0) { return; }
var child, callback, zone, detail = promise._result;
for (var i = 0; i < subscribers.length; i += 4) {
child = subscribers[i];
callback = subscribers[i + settled];
zone = subscribers[i + lib$es6$promise$$internal$$INZONE];
if (child) {
lib$es6$promise$$internal$$invokeCallback(settled, child, callback, detail, zone);
} else {
zone.run(callback, void 0, [detail]);
}
}
promise._subscribers.length = 0;
}
function lib$es6$promise$$internal$$ErrorObject() {
this.error = null;
}
var lib$es6$promise$$internal$$TRY_CATCH_ERROR = new lib$es6$promise$$internal$$ErrorObject();
// .then(successCb, errorCb) microtasks should run in the zone where they were scheduled
// For this to work, the zone error handler should rethrow excpetions
function lib$es6$promise$$internal$$tryCatch(callback, detail, zone) {
try {
return zone.run(callback, void 0, [detail]);
} catch(e) {
lib$es6$promise$$internal$$TRY_CATCH_ERROR.error = e;
return lib$es6$promise$$internal$$TRY_CATCH_ERROR;
}
}
function lib$es6$promise$$internal$$invokeCallback(settled, promise, callback, detail, zone) {
var hasCallback = lib$es6$promise$utils$$isFunction(callback),
value, error, succeeded, failed;
if (hasCallback) {
value = lib$es6$promise$$internal$$tryCatch(callback, detail, zone);
if (value === lib$es6$promise$$internal$$TRY_CATCH_ERROR) {
failed = true;
error = value.error;
value = null;
} else {
succeeded = true;
}
if (promise === value) {
lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$cannotReturnOwn());
return;
}
} else {
value = detail;
succeeded = true;
}
if (promise._state !== lib$es6$promise$$internal$$PENDING) {
// noop
} else if (hasCallback && succeeded) {
lib$es6$promise$$internal$$resolve(promise, value);
} else if (failed) {
lib$es6$promise$$internal$$reject(promise, error);
} else if (settled === lib$es6$promise$$internal$$FULFILLED) {
lib$es6$promise$$internal$$fulfill(promise, value);
} else if (settled === lib$es6$promise$$internal$$REJECTED) {
lib$es6$promise$$internal$$reject(promise, value);
}
}
function lib$es6$promise$$internal$$initializePromise(promise, resolver) {
try {
resolver(function resolvePromise(value){
lib$es6$promise$$internal$$resolve(promise, value);
}, function rejectPromise(reason) {
lib$es6$promise$$internal$$reject(promise, reason);
});
} catch(e) {
lib$es6$promise$$internal$$reject(promise, e);
}
}
var lib$es6$promise$asap$$len = 0;
var lib$es6$promise$asap$$toString = {}.toString;
var lib$es6$promise$asap$$vertxNext;
function lib$es6$promise$asap$$asap(callback, arg) {
lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len] = callback;
lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len + 1] = arg;
lib$es6$promise$asap$$len += 2;
if (lib$es6$promise$asap$$len === 2) {
// If len is 2, that means that we need to schedule an async flush.
// If additional callbacks are queued before the queue is flushed, they
// will be processed by this flush that we are scheduling.
lib$es6$promise$asap$$scheduleFlush();
}
}
var lib$es6$promise$asap$$default = lib$es6$promise$asap$$asap;
var lib$es6$promise$asap$$browserWindow = (typeof window !== 'undefined') ? window : undefined;
var lib$es6$promise$asap$$browserGlobal = lib$es6$promise$asap$$browserWindow || {};
var lib$es6$promise$asap$$BrowserMutationObserver = lib$es6$promise$asap$$browserGlobal.MutationObserver || lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver;
var lib$es6$promise$asap$$isNode = typeof process !== 'undefined' && {}.toString.call(process) === '[object process]';
// test for web worker but not in IE10
var lib$es6$promise$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
typeof importScripts !== 'undefined' &&
typeof MessageChannel !== 'undefined';
// node
function lib$es6$promise$asap$$useNextTick() {
var nextTick = process.nextTick;
// node version 0.10.x displays a deprecation warning when nextTick is used recursively
// setImmediate should be used instead instead
var version = process.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/);
if (Array.isArray(version) && version[1] === '0' && version[2] === '10') {
nextTick = setImmediate;
}
return function() {
nextTick(lib$es6$promise$asap$$flush);
};
}
// vertx
function lib$es6$promise$asap$$useVertxTimer() {
return function() {
lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush);
};
}
function lib$es6$promise$asap$$useMutationObserver() {
var iterations = 0;
var observer = new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush);
var node = document.createTextNode('');
observer.observe(node, { characterData: true });
return function() {
node.data = (iterations = ++iterations % 2);
};
}
// web worker
function lib$es6$promise$asap$$useMessageChannel() {
var channel = new MessageChannel();
channel.port1.onmessage = lib$es6$promise$asap$$flush;
return function () {
channel.port2.postMessage(0);
};
}
function lib$es6$promise$asap$$useSetTimeout() {
return function() {
setTimeout(lib$es6$promise$asap$$flush, 1);
};
}
var lib$es6$promise$asap$$queue = new Array(1000);
function lib$es6$promise$asap$$flush() {
for (var i = 0; i < lib$es6$promise$asap$$len; i+=2) {
var callback = lib$es6$promise$asap$$queue[i];
var arg = lib$es6$promise$asap$$queue[i+1];
callback(arg);
lib$es6$promise$asap$$queue[i] = undefined;
lib$es6$promise$asap$$queue[i+1] = undefined;
}
lib$es6$promise$asap$$len = 0;
}
function lib$es6$promise$asap$$attemptVertex() {
try {
var r = require;
var vertx = r('vertx');
lib$es6$promise$asap$$vertxNext = vertx.runOnLoop || vertx.runOnContext;
return lib$es6$promise$asap$$useVertxTimer();
} catch(e) {
return lib$es6$promise$asap$$useSetTimeout();
}
}
var lib$es6$promise$asap$$scheduleFlush;
// Decide what async method to use to triggering processing of queued callbacks:
if (lib$es6$promise$asap$$isNode) {
lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useNextTick();
} else if (lib$es6$promise$asap$$BrowserMutationObserver) {
lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMutationObserver();
} else if (lib$es6$promise$asap$$isWorker) {
lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMessageChannel();
} else if (lib$es6$promise$asap$$browserWindow === undefined && typeof require === 'function') {
lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$attemptVertex();
} else {
lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useSetTimeout();
}
function lib$es6$promise$enumerator$$Enumerator(Constructor, input) {
var enumerator = this;
enumerator._instanceConstructor = Constructor;
enumerator.promise = new Constructor(lib$es6$promise$$internal$$noop);
if (enumerator._validateInput(input)) {
enumerator._input = input;
enumerator.length = input.length;
enumerator._remaining = input.length;
enumerator._init();
if (enumerator.length === 0) {
lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result);
} else {
enumerator.length = enumerator.length || 0;
enumerator._enumerate();
if (enumerator._remaining === 0) {
lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result);
}
}
} else {
lib$es6$promise$$internal$$reject(enumerator.promise, enumerator._validationError());
}
}
lib$es6$promise$enumerator$$Enumerator.prototype._validateInput = function(input) {
return lib$es6$promise$utils$$isArray(input);
};
lib$es6$promise$enumerator$$Enumerator.prototype._validationError = function() {
return new Error('Array Methods must be provided an Array');
};
lib$es6$promise$enumerator$$Enumerator.prototype._init = function() {
this._result = new Array(this.length);
};
var lib$es6$promise$enumerator$$default = lib$es6$promise$enumerator$$Enumerator;
lib$es6$promise$enumerator$$Enumerator.prototype._enumerate = function() {
var enumerator = this;
var length = enumerator.length;
var promise = enumerator.promise;
var input = enumerator._input;
for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) {
enumerator._eachEntry(input[i], i);
}
};
lib$es6$promise$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
var enumerator = this;
var c = enumerator._instanceConstructor;
if (lib$es6$promise$utils$$isMaybeThenable(entry)) {
if (entry.constructor === c && entry._state !== lib$es6$promise$$internal$$PENDING) {
entry._onerror = null;
enumerator._settledAt(entry._state, i, entry._result);
} else {
enumerator._willSettleAt(c.resolve(entry), i);
}
} else {
enumerator._remaining--;
enumerator._result[i] = entry;
}
};
lib$es6$promise$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
var enumerator = this;
var promise = enumerator.promise;
if (promise._state === lib$es6$promise$$internal$$PENDING) {
enumerator._remaining--;
if (state === lib$es6$promise$$internal$$REJECTED) {
lib$es6$promise$$internal$$reject(promise, value);
} else {
enumerator._result[i] = value;
}
}
if (enumerator._remaining === 0) {
lib$es6$promise$$internal$$fulfill(promise, enumerator._result);
}
};
lib$es6$promise$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
var enumerator = this;
lib$es6$promise$$internal$$subscribe(promise, undefined, function(value) {
enumerator._settledAt(lib$es6$promise$$internal$$FULFILLED, i, value);
}, function(reason) {
enumerator._settledAt(lib$es6$promise$$internal$$REJECTED, i, reason);
});
};
function lib$es6$promise$promise$all$$all(entries) {
return new lib$es6$promise$enumerator$$default(this, entries).promise;
}
var lib$es6$promise$promise$all$$default = lib$es6$promise$promise$all$$all;
function lib$es6$promise$promise$race$$race(entries) {
/*jshint validthis:true */
var Constructor = this;
var promise = new Constructor(lib$es6$promise$$internal$$noop);
if (!lib$es6$promise$utils$$isArray(entries)) {
lib$es6$promise$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
return promise;
}
var length = entries.length;
function onFulfillment(value) {
lib$es6$promise$$internal$$resolve(promise, value);
}
function onRejection(reason) {
lib$es6$promise$$internal$$reject(promise, reason);
}
for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) {
lib$es6$promise$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
}
return promise;
}
var lib$es6$promise$promise$race$$default = lib$es6$promise$promise$race$$race;
function lib$es6$promise$promise$resolve$$resolve(object) {
/*jshint validthis:true */
var Constructor = this;
if (object && typeof object === 'object' && object.constructor === Constructor) {
return object;
}
var promise = new Constructor(lib$es6$promise$$internal$$noop);
lib$es6$promise$$internal$$resolve(promise, object);
return promise;
}
var lib$es6$promise$promise$resolve$$default = lib$es6$promise$promise$resolve$$resolve;
function lib$es6$promise$promise$reject$$reject(reason) {
/*jshint validthis:true */
var Constructor = this;
var promise = new Constructor(lib$es6$promise$$internal$$noop);
lib$es6$promise$$internal$$reject(promise, reason);
return promise;
}
var lib$es6$promise$promise$reject$$default = lib$es6$promise$promise$reject$$reject;
var lib$es6$promise$promise$$counter = 0;
function lib$es6$promise$promise$$needsResolver() {
throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
}
function lib$es6$promise$promise$$needsNew() {
throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
}
var lib$es6$promise$promise$$default = lib$es6$promise$promise$$Promise;
/**
Promise objects represent the eventual result of an asynchronous operation. The
primary way of interacting with a promise is through its `then` method, which
registers callbacks to receive either a promises eventual value or the reason
why the promise cannot be fulfilled.
Terminology
-----------
- `promise` is an object or function with a `then` method whose behavior conforms to this specification.
- `thenable` is an object or function that defines a `then` method.
- `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
- `exception` is a value that is thrown using the throw statement.
- `reason` is a value that indicates why a promise was rejected.
- `settled` the final resting state of a promise, fulfilled or rejected.
A promise can be in one of three states: pending, fulfilled, or rejected.
Promises that are fulfilled have a fulfillment value and are in the fulfilled
state. Promises that are rejected have a rejection reason and are in the
rejected state. A fulfillment value is never a thenable.
Promises can also be said to *resolve* a value. If this value is also a
promise, then the original promise's settled state will match the value's
settled state. So a promise that *resolves* a promise that rejects will
itself reject, and a promise that *resolves* a promise that fulfills will
itself fulfill.
Basic Usage:
------------
```js
var promise = new Promise(function(resolve, reject) {
// on success
resolve(value);
// on failure
reject(reason);
});
promise.then(function(value) {
// on fulfillment
}, function(reason) {
// on rejection
});
```
Advanced Usage:
---------------
Promises shine when abstracting away asynchronous interactions such as
`XMLHttpRequest`s.
```js
function getJSON(url) {
return new Promise(function(resolve, reject){
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = handler;
xhr.responseType = 'json';
xhr.setRequestHeader('Accept', 'application/json');
xhr.send();
function handler() {
if (this.readyState === this.DONE) {
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
}
}
};
});
}
getJSON('/posts.json').then(function(json) {
// on fulfillment
}, function(reason) {
// on rejection
});
```
Unlike callbacks, promises are great composable primitives.
```js
Promise.all([
getJSON('/posts'),
getJSON('/comments')
]).then(function(values){
values[0] // => postsJSON
values[1] // => commentsJSON
return values;
});
```
@class Promise
@param {function} resolver
Useful for tooling.
@constructor
*/
function lib$es6$promise$promise$$Promise(resolver) {
this._id = lib$es6$promise$promise$$counter++;
this._state = undefined;
this._result = undefined;
this._subscribers = [];
if (lib$es6$promise$$internal$$noop !== resolver) {
if (!lib$es6$promise$utils$$isFunction(resolver)) {
lib$es6$promise$promise$$needsResolver();
}
if (!(this instanceof lib$es6$promise$promise$$Promise)) {
lib$es6$promise$promise$$needsNew();
}
lib$es6$promise$$internal$$initializePromise(this, resolver);
}
}
lib$es6$promise$promise$$Promise.all = lib$es6$promise$promise$all$$default;
lib$es6$promise$promise$$Promise.race = lib$es6$promise$promise$race$$default;
lib$es6$promise$promise$$Promise.resolve = lib$es6$promise$promise$resolve$$default;
lib$es6$promise$promise$$Promise.reject = lib$es6$promise$promise$reject$$default;
lib$es6$promise$promise$$Promise.prototype = {
constructor: lib$es6$promise$promise$$Promise,
/**
The primary way of interacting with a promise is through its `then` method,
which registers callbacks to receive either a promise's eventual value or the
reason why the promise cannot be fulfilled.
```js
findUser().then(function(user){
// user is available
}, function(reason){
// user is unavailable, and you are given the reason why
});
```
Chaining
--------
The return value of `then` is itself a promise. This second, 'downstream'
promise is resolved with the return value of the first promise's fulfillment
or rejection handler, or rejected if the handler throws an exception.
```js
findUser().then(function (user) {
return user.name;
}, function (reason) {
return 'default name';
}).then(function (userName) {
// If `findUser` fulfilled, `userName` will be the user's name, otherwise it
// will be `'default name'`
});
findUser().then(function (user) {
throw new Error('Found user, but still unhappy');
}, function (reason) {
throw new Error('`findUser` rejected and we're unhappy');
}).then(function (value) {
// never reached
}, function (reason) {
// if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
// If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
});
```
If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
```js
findUser().then(function (user) {
throw new PedagogicalException('Upstream error');
}).then(function (value) {
// never reached
}).then(function (value) {
// never reached
}, function (reason) {
// The `PedgagocialException` is propagated all the way down to here
});
```
Assimilation
------------
Sometimes the value you want to propagate to a downstream promise can only be
retrieved asynchronously. This can be achieved by returning a promise in the
fulfillment or rejection handler. The downstream promise will then be pending
until the returned promise is settled. This is called *assimilation*.
```js
findUser().then(function (user) {
return findCommentsByAuthor(user);
}).then(function (comments) {
// The user's comments are now available
});
```
If the assimliated promise rejects, then the downstream promise will also reject.
```js
findUser().then(function (user) {
return findCommentsByAuthor(user);
}).then(function (comments) {
// If `findCommentsByAuthor` fulfills, we'll have the value here
}, function (reason) {
// If `findCommentsByAuthor` rejects, we'll have the reason here
});
```
Simple Example
--------------
Synchronous Example
```javascript
var result;
try {
result = findResult();
// success
} catch(reason) {
// failure
}
```
Errback Example
```js
findResult(function(result, err){
if (err) {
// failure
} else {
// success
}
});
```
Promise Example;
```javascript
findResult().then(function(result){
// success
}, function(reason){
// failure
});
```
Advanced Example
--------------
Synchronous Example
```javascript
var author, books;
try {
author = findAuthor();
books = findBooksByAuthor(author);
// success
} catch(reason) {
// failure
}
```
Errback Example
```js
function foundBooks(books) {
}
function failure(reason) {
}
findAuthor(function(author, err){
if (err) {
failure(err);
// failure
} else {
try {
findBoooksByAuthor(author, function(books, err) {
if (err) {
failure(err);
} else {
try {
foundBooks(books);
} catch(reason) {
failure(reason);
}
}
});
} catch(error) {
failure(err);
}
// success
}
});
```
Promise Example;
```javascript
findAuthor().
then(findBooksByAuthor).
then(function(books){
// found books
}).catch(function(reason){
// something went wrong
});
```
@method then
@param {Function} onFulfilled
@param {Function} onRejected
Useful for tooling.
@return {Promise}
*/
then: function(onFulfillment, onRejection) {
var parent = this;
var state = parent._state;
if (state === lib$es6$promise$$internal$$FULFILLED && !onFulfillment || state === lib$es6$promise$$internal$$REJECTED && !onRejection) {
return this;
}
var child = new this.constructor(lib$es6$promise$$internal$$noop);
var result = parent._result;
var zone = lib$es6$promise$utils$$getCurrentZone();
if (state) {
var callback = arguments[state - 1];
lib$es6$promise$asap$microtask$$default(function() {
lib$es6$promise$$internal$$invokeCallback(state, child, callback, result, zone);
});
} else {
lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection);
}
return child;
},
/**
`catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
as the catch block of a try/catch statement.
```js
function findAuthor(){
throw new Error('couldn't find that author');
}
// synchronous
try {
findAuthor();
} catch(reason) {
// something went wrong
}
// async with promises
findAuthor().catch(function(reason){
// something went wrong
});
```
@method catch
@param {Function} onRejection
Useful for tooling.
@return {Promise}
*/
'catch': function(onRejection) {
return this.then(null, onRejection);
}
};
function lib$es6$promise$polyfill$$polyfill() {
lib$es6$promise$utils$$getGlobal().Promise = lib$es6$promise$promise$$default;
}
var lib$es6$promise$polyfill$$default = lib$es6$promise$polyfill$$polyfill;
var lib$es6$promise$umd$$ES6Promise = {
'Promise': lib$es6$promise$promise$$default,
'polyfill': lib$es6$promise$polyfill$$default
};
/* global define:true module:true window: true */
if (typeof define === 'function' && define['amd']) {
define(function() { return lib$es6$promise$umd$$ES6Promise; });
} else if (typeof module !== 'undefined' && module['exports']) {
module['exports'] = lib$es6$promise$umd$$ES6Promise;
} else if (typeof this !== 'undefined') {
this['ES6Promise'] = lib$es6$promise$umd$$ES6Promise;
}
lib$es6$promise$polyfill$$default();
}).call(this);

View File

@ -0,0 +1,88 @@
/*
* Wrapped stacktrace
*
* We need this because in some implementations, constructing a trace is slow
* and so we want to defer accessing the trace for as long as possible
*/
Zone.Stacktrace = function (e) {
this._e = e;
};
Zone.Stacktrace.prototype.get = function () {
if (zone.stackFramesFilter) {
return this._e.stack.
split('\n').
filter(zone.stackFramesFilter).
join('\n');
}
return this._e.stack;
}
Zone.getStacktrace = function () {
function getStacktraceWithUncaughtError () {
return new Zone.Stacktrace(new Error());
}
function getStacktraceWithCaughtError () {
try {
throw new Error();
} catch (e) {
return new Zone.Stacktrace(e);
}
}
// Some implementations of exception handling don't create a stack trace if the exception
// isn't thrown, however it's faster not to actually throw the exception.
var stack = getStacktraceWithUncaughtError();
if (stack && stack._e.stack) {
Zone.getStacktrace = getStacktraceWithUncaughtError;
return stack;
} else {
Zone.getStacktrace = getStacktraceWithCaughtError;
return Zone.getStacktrace();
}
};
Zone.longStackTraceZone = {
getLongStacktrace: function (exception) {
var trace = [];
var zone = this;
if (exception) {
if (zone.stackFramesFilter) {
trace.push(exception.stack.split('\n').
filter(zone.stackFramesFilter).
join('\n'));
} else {
trace.push(exception.stack);
}
}
var now = Date.now();
while (zone && zone.constructedAtException) {
trace.push(
'--- ' + (Date(zone.constructedAtTime)).toString() +
' - ' + (now - zone.constructedAtTime) + 'ms ago',
zone.constructedAtException.get());
zone = zone.parent;
}
return trace.join('\n');
},
stackFramesFilter: function (line) {
return line.indexOf('zone.js') === -1;
},
onError: function (exception) {
var reporter = this.reporter || console.log.bind(console);
reporter(exception.toString());
reporter(this.getLongStacktrace(exception));
},
fork: function (locals) {
var newZone = this._fork(locals);
newZone.constructedAtException = Zone.getStacktrace();
newZone.constructedAtTime = Date.now();
return newZone;
},
_fork: zone.fork
};

716
zone/zone.js Normal file
View File

@ -0,0 +1,716 @@
'use strict';
(function (exports) {
var zone = null;
function Zone(parentZone, data) {
var zone = (arguments.length) ? Object.create(parentZone) : this;
zone.parent = parentZone;
Object.keys(data || {}).forEach(function(property) {
var _property = property.substr(1);
// augment the new zone with a hook decorates the parent's hook
if (property[0] === '$') {
zone[_property] = data[property](parentZone[_property] || function () {});
// augment the new zone with a hook that runs after the parent's hook
} else if (property[0] === '+') {
if (parentZone[_property]) {
zone[_property] = function () {
var result = parentZone[_property].apply(this, arguments);
data[property].apply(this, arguments);
return result;
};
} else {
zone[_property] = data[property];
}
// augment the new zone with a hook that runs before the parent's hook
} else if (property[0] === '-') {
if (parentZone[_property]) {
zone[_property] = function () {
data[property].apply(this, arguments);
return parentZone[_property].apply(this, arguments);
};
} else {
zone[_property] = data[property];
}
// set the new zone's hook (replacing the parent zone's)
} else {
zone[property] = (typeof data[property] === 'object') ?
JSON.parse(JSON.stringify(data[property])) :
data[property];
}
});
zone.$id = ++Zone.nextId;
return zone;
}
Zone.prototype = {
constructor: Zone,
fork: function (locals) {
this.onZoneCreated();
return new Zone(this, locals);
},
bind: function (fn, skipEnqueue) {
skipEnqueue || this.enqueueTask(fn);
var zone = this.fork();
return function zoneBoundFn() {
return zone.run(fn, this, arguments);
};
},
bindOnce: function (fn) {
var boundZone = this;
return this.bind(function () {
var result = fn.apply(this, arguments);
boundZone.dequeueTask(fn);
return result;
});
},
scheduleMicrotask: function (fn) {
var executionZone = this;
Zone.microtaskQueue.push(function() {
executionZone.run(fn);
});
},
run: function run (fn, applyTo, applyWith) {
applyWith = applyWith || [];
var oldZone = zone,
result;
exports.zone = zone = this;
try {
Zone.nestedRun++;
this.beforeTask();
return fn.apply(applyTo, applyWith);
} catch (e) {
if (zone.onError) {
zone.onError(e);
} else {
throw e;
}
} finally {
this.afterTask();
Zone.nestedRun--;
// Check if there are microtasks to execute unless:
// - we are already executing them (drainingMicrotasks is true),
// - we are in a recursive call to run (nesetdRun > 0)
if (!Zone.drainingMicrotasks && Zone.nestedRun == 0) {
this.runMicrotasks();
}
exports.zone = zone = oldZone;
}
},
runMicrotasks: function () {
Zone.drainingMicrotasks = true;
do {
// Drain the microtask queue
while (Zone.microtaskQueue.length > 0) {
var microtask = Zone.microtaskQueue.shift();
microtask();
}
this.afterTurn();
// Check the queue length again as afterTurn might have enqueued more microtasks
} while (Zone.microtaskQueue.length > 0)
Zone.drainingMicrotasks = false;
},
afterTurn: function() {},
beforeTask: function () {},
onZoneCreated: function () {},
afterTask: function () {},
enqueueTask: function () {},
dequeueTask: function () {}
};
Zone.patchSetClearFn = function (obj, fnNames) {
fnNames.map(function (name) {
return name[0].toUpperCase() + name.substr(1);
}).
forEach(function (name) {
var setName = 'set' + name;
var delegate = obj[setName];
if (delegate) {
var clearName = 'clear' + name;
var ids = {};
var bindArgs = setName === 'setInterval' ? Zone.bindArguments : Zone.bindArgumentsOnce;
zone[setName] = function (fn) {
var id, fnRef = fn;
arguments[0] = function () {
delete ids[id];
return fnRef.apply(this, arguments);
};
var args = bindArgs(arguments);
id = delegate.apply(obj, args);
ids[id] = true;
return id;
};
obj[setName] = function () {
return zone[setName].apply(this, arguments);
};
var clearDelegate = obj[clearName];
zone[clearName] = function (id) {
if (ids[id]) {
delete ids[id];
zone.dequeueTask();
}
return clearDelegate.apply(this, arguments);
};
obj[clearName] = function () {
return zone[clearName].apply(this, arguments);
};
}
});
};
Zone.nextId = 1;
// Pending microtasks to be executed after the macrotask
Zone.microtaskQueue = [];
// Whether we are currently draining the microtask queue
Zone.drainingMicrotasks = false;
// Recursive calls to run
Zone.nestedRun = 0;
Zone.patchSetFn = function (obj, fnNames) {
fnNames.forEach(function (name) {
var delegate = obj[name];
if (delegate) {
zone[name] = function (fn) {
var fnRef = fn;
arguments[0] = function () {
return fnRef.apply(this, arguments);
};
var args = Zone.bindArgumentsOnce(arguments);
return delegate.apply(obj, args);
};
obj[name] = function () {
return zone[name].apply(this, arguments);
};
}
});
};
Zone.patchPrototype = function (obj, fnNames) {
fnNames.forEach(function (name) {
var delegate = obj[name];
if (delegate) {
obj[name] = function () {
return delegate.apply(this, Zone.bindArguments(arguments));
};
}
});
};
Zone.bindArguments = function (args) {
for (var i = args.length - 1; i >= 0; i--) {
if (typeof args[i] === 'function') {
args[i] = zone.bind(args[i]);
}
}
return args;
};
Zone.bindArgumentsOnce = function (args) {
for (var i = args.length - 1; i >= 0; i--) {
if (typeof args[i] === 'function') {
args[i] = zone.bindOnce(args[i]);
}
}
return args;
};
/*
* patch a fn that returns a promise
*/
Zone.bindPromiseFn = (function() {
// if the browser natively supports Promises, we can just return a native promise
if (window.Promise) {
return function (delegate) {
return function() {
var delegatePromise = delegate.apply(this, arguments);
if (delegatePromise instanceof Promise) {
return delegatePromise;
} else {
return new Promise(function(resolve, reject) {
delegatePromise.then(resolve, reject);
});
}
};
};
} else {
// if the browser does not have native promises, we have to patch each promise instance
return function (delegate) {
return function () {
return patchThenable(delegate.apply(this, arguments));
};
};
}
function patchThenable(thenable) {
var then = thenable.then;
thenable.then = function () {
var args = Zone.bindArguments(arguments);
var nextThenable = then.apply(thenable, args);
return patchThenable(nextThenable);
};
var ocatch = thenable.catch;
thenable.catch = function () {
var args = Zone.bindArguments(arguments);
var nextThenable = ocatch.apply(thenable, args);
return patchThenable(nextThenable);
};
return thenable;
}
}());
Zone.patchableFn = function (obj, fnNames) {
fnNames.forEach(function (name) {
var delegate = obj[name];
zone[name] = function () {
return delegate.apply(obj, arguments);
};
obj[name] = function () {
return zone[name].apply(this, arguments);
};
});
};
Zone.patchProperty = function (obj, prop) {
var desc = Object.getOwnPropertyDescriptor(obj, prop) || {
enumerable: true,
configurable: true
};
// A property descriptor cannot have getter/setter and be writable
// deleting the writable and value properties avoids this error:
//
// TypeError: property descriptors must not specify a value or be writable when a
// getter or setter has been specified
delete desc.writable;
delete desc.value;
// substr(2) cuz 'onclick' -> 'click', etc
var eventName = prop.substr(2);
var _prop = '_' + prop;
desc.set = function (fn) {
if (this[_prop]) {
this.removeEventListener(eventName, this[_prop]);
}
if (typeof fn === 'function') {
this[_prop] = fn;
this.addEventListener(eventName, fn, false);
} else {
this[_prop] = null;
}
};
desc.get = function () {
return this[_prop];
};
Object.defineProperty(obj, prop, desc);
};
Zone.patchProperties = function (obj, properties) {
(properties || (function () {
var props = [];
for (var prop in obj) {
props.push(prop);
}
return props;
}()).
filter(function (propertyName) {
return propertyName.substr(0,2) === 'on';
})).
forEach(function (eventName) {
Zone.patchProperty(obj, eventName);
});
};
Zone.patchEventTargetMethods = function (obj) {
var addDelegate = obj.addEventListener;
obj.addEventListener = function (eventName, fn) {
arguments[1] = fn._bound = zone.bind(fn);
return addDelegate.apply(this, arguments);
};
var removeDelegate = obj.removeEventListener;
obj.removeEventListener = function (eventName, fn) {
arguments[1] = arguments[1]._bound || arguments[1];
var result = removeDelegate.apply(this, arguments);
zone.dequeueTask(fn);
return result;
};
};
Zone.patch = function patch () {
Zone.patchSetClearFn(window, [
'timeout',
'interval',
'immediate'
]);
Zone.patchSetFn(window, [
'requestAnimationFrame',
'mozRequestAnimationFrame',
'webkitRequestAnimationFrame'
]);
Zone.patchableFn(window, ['alert', 'prompt']);
// patched properties depend on addEventListener, so this needs to come first
if (window.EventTarget) {
Zone.patchEventTargetMethods(window.EventTarget.prototype);
// Note: EventTarget is not available in all browsers,
// if it's not available, we instead patch the APIs in the IDL that inherit from EventTarget
} else {
[ 'ApplicationCache',
'EventSource',
'FileReader',
'InputMethodContext',
'MediaController',
'MessagePort',
'Node',
'Performance',
'SVGElementInstance',
'SharedWorker',
'TextTrack',
'TextTrackCue',
'TextTrackList',
'WebKitNamedFlow',
'Window',
'Worker',
'WorkerGlobalScope',
'XMLHttpRequestEventTarget',
'XMLHttpRequestUpload'
].
filter(function (thing) {
return window[thing];
}).
map(function (thing) {
return window[thing].prototype;
}).
forEach(Zone.patchEventTargetMethods);
}
if (Zone.canPatchViaPropertyDescriptor()) {
Zone.patchViaPropertyDescriptor();
} else {
Zone.patchViaCapturingAllTheEvents();
Zone.patchClass('XMLHttpRequest');
Zone.patchWebSocket();
}
// Do not patch promises when using out own version supporting microtasks
//// patch promises
//if (window.Promise) {
// Zone.patchPrototype(Promise.prototype, [
// 'then',
// 'catch'
// ]);
//}
Zone.patchMutationObserverClass('MutationObserver');
Zone.patchMutationObserverClass('WebKitMutationObserver');
Zone.patchDefineProperty();
Zone.patchRegisterElement();
};
//
Zone.canPatchViaPropertyDescriptor = function () {
if (!Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'onclick') &&
typeof Element !== 'undefined') {
// WebKit https://bugs.webkit.org/show_bug.cgi?id=134364
// IDL interface attributes are not configurable
var desc = Object.getOwnPropertyDescriptor(Element.prototype, 'onclick');
if (desc && !desc.configurable) return false;
}
Object.defineProperty(HTMLElement.prototype, 'onclick', {
get: function () {
return true;
}
});
var elt = document.createElement('div');
var result = !!elt.onclick;
Object.defineProperty(HTMLElement.prototype, 'onclick', {});
return result;
};
// for browsers that we can patch the descriptor:
// - eventually Chrome once this bug gets resolved
// - Firefox
Zone.patchViaPropertyDescriptor = function () {
Zone.patchProperties(HTMLElement.prototype, Zone.onEventNames);
Zone.patchProperties(XMLHttpRequest.prototype);
};
// Whenever any event fires, we check the event target and all parents
// for `onwhatever` properties and replace them with zone-bound functions
// - Chrome (for now)
Zone.patchViaCapturingAllTheEvents = function () {
Zone.eventNames.forEach(function (property) {
var onproperty = 'on' + property;
document.addEventListener(property, function (event) {
var elt = event.target, bound;
while (elt) {
if (elt[onproperty] && !elt[onproperty]._unbound) {
bound = zone.bind(elt[onproperty]);
bound._unbound = elt[onproperty];
elt[onproperty] = bound;
}
elt = elt.parentElement;
}
}, true);
});
};
// we have to patch the instance since the proto is non-configurable
Zone.patchWebSocket = function() {
var WS = window.WebSocket;
window.WebSocket = function(a, b) {
var socket = arguments.length > 1 ? new WS(a, b) : new WS(a);
Zone.patchProperties(socket, ['onclose', 'onerror', 'onmessage', 'onopen']);
return socket;
};
}
// wrap some native API on `window`
Zone.patchClass = function (className) {
var OriginalClass = window[className];
if (!OriginalClass) {
return;
}
window[className] = function () {
var a = Zone.bindArguments(arguments);
switch (a.length) {
case 0: this._o = new OriginalClass(); break;
case 1: this._o = new OriginalClass(a[0]); break;
case 2: this._o = new OriginalClass(a[0], a[1]); break;
case 3: this._o = new OriginalClass(a[0], a[1], a[2]); break;
case 4: this._o = new OriginalClass(a[0], a[1], a[2], a[3]); break;
default: throw new Error('what are you even doing?');
}
};
var instance = new OriginalClass(className.substr(-16) === 'MutationObserver' ? function () {} : undefined);
var prop;
for (prop in instance) {
(function (prop) {
if (typeof instance[prop] === 'function') {
window[className].prototype[prop] = function () {
return this._o[prop].apply(this._o, arguments);
};
} else {
Object.defineProperty(window[className].prototype, prop, {
set: function (fn) {
if (typeof fn === 'function') {
this._o[prop] = zone.bind(fn);
} else {
this._o[prop] = fn;
}
},
get: function () {
return this._o[prop];
}
});
}
}(prop));
};
};
// wrap some native API on `window`
Zone.patchMutationObserverClass = function (className) {
var OriginalClass = window[className];
if (!OriginalClass) {
return;
}
window[className] = function (fn) {
this._o = new OriginalClass(zone.bind(fn, true));
};
var instance = new OriginalClass(function () {});
window[className].prototype.disconnect = function () {
var result = this._o.disconnect.apply(this._o, arguments);
this._active && zone.dequeueTask();
this._active = false;
return result;
};
window[className].prototype.observe = function () {
if (!this._active) {
zone.enqueueTask();
}
this._active = true;
return this._o.observe.apply(this._o, arguments);
};
var prop;
for (prop in instance) {
(function (prop) {
if (typeof window[className].prototype !== undefined) {
return;
}
if (typeof instance[prop] === 'function') {
window[className].prototype[prop] = function () {
return this._o[prop].apply(this._o, arguments);
};
} else {
Object.defineProperty(window[className].prototype, prop, {
set: function (fn) {
if (typeof fn === 'function') {
this._o[prop] = zone.bind(fn);
} else {
this._o[prop] = fn;
}
},
get: function () {
return this._o[prop];
}
});
}
}(prop));
}
};
// might need similar for object.freeze
// i regret nothing
Zone.patchDefineProperty = function () {
var _defineProperty = Object.defineProperty;
var _getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
var _create = Object.create;
Object.defineProperty = function (obj, prop, desc) {
if (isUnconfigurable(obj, prop)) {
throw new TypeError('Cannot assign to read only property \'' + prop + '\' of ' + obj);
}
if (prop !== 'prototype') {
desc = rewriteDescriptor(obj, prop, desc);
}
return _defineProperty(obj, prop, desc);
};
Object.defineProperties = function (obj, props) {
Object.keys(props).forEach(function (prop) {
Object.defineProperty(obj, prop, props[prop]);
});
return obj;
};
Object.create = function (obj, proto) {
if (typeof proto === 'object') {
Object.keys(proto).forEach(function (prop) {
proto[prop] = rewriteDescriptor(obj, prop, proto[prop]);
});
}
return _create(obj, proto);
};
Object.getOwnPropertyDescriptor = function (obj, prop) {
var desc = _getOwnPropertyDescriptor(obj, prop);
if (isUnconfigurable(obj, prop)) {
desc.configurable = false;
}
return desc;
};
Zone._redefineProperty = function (obj, prop, desc) {
desc = rewriteDescriptor(obj, prop, desc);
return _defineProperty(obj, prop, desc);
};
function isUnconfigurable (obj, prop) {
return obj && obj.__unconfigurables && obj.__unconfigurables[prop];
}
function rewriteDescriptor (obj, prop, desc) {
desc.configurable = true;
if (!desc.configurable) {
if (!obj.__unconfigurables) {
_defineProperty(obj, '__unconfigurables', { writable: true, value: {} });
}
obj.__unconfigurables[prop] = true;
}
return desc;
}
};
Zone.patchRegisterElement = function () {
if (!('registerElement' in document)) {
return;
}
var _registerElement = document.registerElement;
var callbacks = [
'createdCallback',
'attachedCallback',
'detachedCallback',
'attributeChangedCallback'
];
document.registerElement = function (name, opts) {
callbacks.forEach(function (callback) {
if (opts.prototype[callback]) {
var descriptor = Object.getOwnPropertyDescriptor(opts.prototype, callback);
if (descriptor.value) {
descriptor.value = zone.bind(descriptor.value || opts.prototype[callback]);
Zone._redefineProperty(opts.prototype, callback, descriptor);
}
}
});
return _registerElement.apply(document, [name, opts]);
};
}
Zone.eventNames = 'copy cut paste abort blur focus canplay canplaythrough change click contextmenu dblclick drag dragend dragenter dragleave dragover dragstart drop durationchange emptied ended input invalid keydown keypress keyup load loadeddata loadedmetadata loadstart message mousedown mouseenter mouseleave mousemove mouseout mouseover mouseup pause play playing progress ratechange reset scroll seeked seeking select show stalled submit suspend timeupdate volumechange waiting mozfullscreenchange mozfullscreenerror mozpointerlockchange mozpointerlockerror error webglcontextrestored webglcontextlost webglcontextcreationerror'.split(' ');
Zone.onEventNames = Zone.eventNames.map(function (property) {
return 'on' + property;
});
Zone.init = function init () {
exports.zone = zone = new Zone();
Zone.patch();
};
Zone.init();
exports.Zone = Zone;
}((typeof module !== 'undefined' && module && module.exports) ?
module.exports : window));