refactor(VmTurnZone): use the browser microtask queue for JS

This commit is contained in:
Victor Berchet 2015-04-30 19:51:22 +02:00
parent e8a6c95e2a
commit fd1d60f03b
10 changed files with 1336 additions and 1070 deletions

View File

@ -12,10 +12,7 @@ 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/zone-microtask.js',
'zone/long-stack-trace-zone.js',
'node_modules/traceur/bin/traceur-runtime.js',

View File

@ -55,28 +55,13 @@ class VmTurnZone {
// 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,
() => _createOuterZone(Zone.current),
onError: _onErrorWithLongStackTrace);
} else {
_outerZone = _createOuterZone(
Zone.current,
handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, error, StackTrace trace) =>
_onErrorWithoutLongStackTrace(error, trace)
),
zoneValues: {'_name': 'outer'}
);
}
@ -240,4 +225,17 @@ class VmTurnZone {
throw error;
}
}
Zone _createOuterZone(Zone zone, {handleUncaughtError}) {
return zone.fork(
specification: new ZoneSpecification(
scheduleMicrotask: _scheduleMicrotask,
run: _outerRun,
runUnary: _outerRunUnary,
runBinary: _outerRunBinary,
handleUncaughtError: handleUncaughtError
),
zoneValues: {'_name': 'outer'}
);
}
}

View File

@ -13,13 +13,28 @@ import {normalizeBlank, isPresent, global} from 'angular2/src/facade/lang';
* @exportedAs angular2/core
*/
export class VmTurnZone {
// Code executed in _outerZone does not trigger the onTurnDone.
_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.
_innerZone;
_onTurnStart:Function;
_onTurnDone:Function;
_onErrorHandler:Function;
// Number of microtasks pending from _outerZone (& descendants)
_pendingMicrotask: number;
// Whether some code has been executed in the _innerZone (& descendants) in the current turn
_hasExecutedCodeInInnerZone: boolean;
// Whether the onTurnStart hook is executing
_inTurnStart: boolean;
// run() call depth in _outerZone. 0 at the end of a macrotask
// zone.run(() => { // top-level call
// zone.run(() => {}); // nested call -> in-turn
// });
_nestedRun: number;
/**
* Associates with this
*
@ -33,17 +48,14 @@ export class VmTurnZone {
this._onTurnStart = null;
this._onTurnDone = null;
this._onErrorHandler = null;
Zone._hasExecutedInnerCode = false;
this._outerZone = global.zone.fork({
_name: 'outer',
beforeTurn: () => { this._beforeTurn(); },
afterTurn: () => { this._afterTurn(); }
});
this._pendingMicrotasks = 0;
this._hasExecutedCodeInInnerZone = false;
this._inTurnStart = false;
this._nestedRun = 0;
this._outerZone = this._createOuterZone(global.zone);
this._innerZone = this._createInnerZone(this._outerZone, enableLongStackTrace);
// TODO('remove');
Zone.debug = true;
}
/**
@ -97,6 +109,50 @@ export class VmTurnZone {
return this._outerZone.run(fn);
}
_createOuterZone(zone) {
var vmTurnZone = this;
return zone.fork({
_name: 'outer',
'$run': function(parentRun) {
return function() {
try {
vmTurnZone._nestedRun++;
return parentRun.apply(this, arguments);
} finally {
vmTurnZone._nestedRun--;
// If there are no more pending microtasks, we are at the end of a VM turn (or in onTurnStart)
// _nestedRun will be 0 at the end of a macrotasks (it could be > 0 when there are nested calls
// to run()).
if (vmTurnZone._pendingMicrotasks == 0 && vmTurnZone._nestedRun == 0) {
if (vmTurnZone._onTurnDone && !vmTurnZone._inTurnStart && vmTurnZone._hasExecutedCodeInInnerZone) {
try {
parentRun.call(vmTurnZone._innerZone, vmTurnZone._onTurnDone);
} finally {
vmTurnZone._hasExecutedCodeInInnerZone = false;
}
}
}
vmTurnZone._inTurnStart = false;
}
}
},
'$scheduleMicrotask': function(parentScheduleMicrotask) {
return function(fn) {
vmTurnZone._pendingMicrotasks++;
var microtask = function() {
try {
fn();
} finally {
vmTurnZone._pendingMicrotasks--;
}
};
parentScheduleMicrotask.call(this, microtask);
}
}
});
}
_createInnerZone(zone, enableLongStackTrace) {
var vmTurnZone = this;
var errorHandling;
@ -118,40 +174,29 @@ export class VmTurnZone {
return zone
.fork(errorHandling)
.fork({
// Executes code in the _innerZone & trigger the onTurnStart hook when code is executed for the
// first time in a turn.
'$run': function (parentRun) {
return function () {
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);
vmTurnZone._maybeStartVmTurn()
return parentRun.apply(this, arguments)
}
},
_name: 'inner'
});
}
_beforeTurn() {
this._onTurnStart && this._onTurnStart();
}
_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;
_maybeStartVmTurn(): void {
if (!this._hasExecutedCodeInInnerZone) {
this._hasExecutedCodeInInnerZone = true;
if (this._onTurnStart) {
this._inTurnStart = true;
this._outerZone.run.call(this._innerZone, this._onTurnStart);
}
}
}
_onError(zone, e) {
_onError(zone, e): void {
if (isPresent(this._onErrorHandler)) {
var trace = [normalizeBlank(e.stack)];
@ -161,6 +206,8 @@ export class VmTurnZone {
}
this._onErrorHandler(e, trace);
} else {
console.log('## _onError ##');
console.log(e.stack);
throw e;
}
}

View File

@ -1,5 +1,6 @@
import {DOM} from 'angular2/src/dom/dom_adapter';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {global} from 'angular2/src/facade/lang';
import {bind} from 'angular2/di';
@ -136,14 +137,11 @@ function _it(jsmFn, name, fn) {
fn = inject([], fn);
}
// 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);
inIt = true;
fn.execute(injector);
inIt = false;
if (!async) done();
});
}
@ -357,5 +355,5 @@ function elementText(n) {
}
function getCurrentZoneName(): string {
return zone._name;
return global.zone._name;
}

View File

@ -1,5 +1,4 @@
<script src="es6-promise.js" type="text/javascript"></script>
<script src="zone.js" type="text/javascript"></script>
<script src="zone-microtask.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>

View File

@ -1,5 +1,4 @@
<script src="es6-promise.js" type="text/javascript"></script>
<script src="zone.js" type="text/javascript"></script>
<script src="zone-microtask.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>

View File

@ -1,5 +1,4 @@
<script src="es6-promise.js" type="text/javascript"></script>
<script src="zone.js" type="text/javascript"></script>
<script src="zone-microtask.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>

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,716 +0,0 @@
'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));