Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

feat(performance): onProperty handler use global wrapFn, other performance improve. #872

Merged
merged 2 commits into from
Aug 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 35 additions & 15 deletions lib/browser/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,34 +91,52 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
return pendingTask;
}

const SYMBOL_ADDEVENTLISTENER = zoneSymbol('addEventListener');
const SYMBOL_REMOVEEVENTLISTENER = zoneSymbol('removeEventListener');

let oriAddListener = (XMLHttpRequest.prototype as any)[SYMBOL_ADDEVENTLISTENER];
let oriRemoveListener = (XMLHttpRequest.prototype as any)[SYMBOL_REMOVEEVENTLISTENER];
if (!oriAddListener) {
const XMLHttpRequestEventTarget = window['XMLHttpRequestEventTarget'];
if (XMLHttpRequestEventTarget) {
oriAddListener = XMLHttpRequestEventTarget.prototype[SYMBOL_ADDEVENTLISTENER];
oriRemoveListener = XMLHttpRequestEventTarget.prototype[SYMBOL_REMOVEEVENTLISTENER];
}
}

const READY_STATE_CHANGE = 'readystatechange';
const SCHEDULED = 'scheduled';

function scheduleTask(task: Task) {
(XMLHttpRequest as any)[XHR_SCHEDULED] = false;
const data = <XHROptions>task.data;
const target = data.target;
// remove existing event listener
const listener = data.target[XHR_LISTENER];
const oriAddListener = data.target[zoneSymbol('addEventListener')];
const oriRemoveListener = data.target[zoneSymbol('removeEventListener')];
const listener = target[XHR_LISTENER];
if (!oriAddListener) {
oriAddListener = target[SYMBOL_ADDEVENTLISTENER];
oriRemoveListener = target[SYMBOL_REMOVEEVENTLISTENER];
}

if (listener) {
oriRemoveListener.apply(data.target, ['readystatechange', listener]);
oriRemoveListener.apply(target, [READY_STATE_CHANGE, listener]);
}
const newListener = data.target[XHR_LISTENER] = () => {
if (data.target.readyState === data.target.DONE) {
const newListener = target[XHR_LISTENER] = () => {
if (target.readyState === target.DONE) {
// sometimes on some browsers XMLHttpRequest will fire onreadystatechange with
// readyState=4 multiple times, so we need to check task state here
if (!data.aborted && (XMLHttpRequest as any)[XHR_SCHEDULED] &&
task.state === 'scheduled') {
if (!data.aborted && (XMLHttpRequest as any)[XHR_SCHEDULED] && task.state === SCHEDULED) {
task.invoke();
}
}
};
oriAddListener.apply(data.target, ['readystatechange', newListener]);
oriAddListener.apply(target, [READY_STATE_CHANGE, newListener]);

const storedTask: Task = data.target[XHR_TASK];
const storedTask: Task = target[XHR_TASK];
if (!storedTask) {
data.target[XHR_TASK] = task;
target[XHR_TASK] = task;
}
sendNative.apply(data.target, data.args);
sendNative.apply(target, data.args);
(XMLHttpRequest as any)[XHR_SCHEDULED] = true;
return task;
}
Expand All @@ -139,6 +157,7 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
return openNative.apply(self, args);
});

const XMLHTTPREQUEST_SOURCE = 'XMLHttpRequest.send';
const sendNative: Function = patchMethod(
window.XMLHttpRequest.prototype, 'send', () => function(self: any, args: any[]) {
const zone = Zone.current;
Expand All @@ -149,15 +168,17 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
const options: XHROptions =
{target: self, isPeriodic: false, delay: null, args: args, aborted: false};
return zone.scheduleMacroTask(
'XMLHttpRequest.send', placeholderCallback, options, scheduleTask, clearTask);
XMLHTTPREQUEST_SOURCE, placeholderCallback, options, scheduleTask, clearTask);
}
});

const STRING_TYPE = 'string';

const abortNative = patchMethod(
window.XMLHttpRequest.prototype, 'abort',
(delegate: Function) => function(self: any, args: any[]) {
const task: Task = findPendingTask(self);
if (task && typeof task.type == 'string') {
if (task && typeof task.type == STRING_TYPE) {
// If the XHR has already completed, do nothing.
// If the XHR has already been aborted, do nothing.
// Fix #569, call abort multiple times before done will cause
Expand Down Expand Up @@ -207,7 +228,6 @@ Zone.__load_patch('PromiseRejectionEvent', (global: any, Zone: ZoneType, api: _Z
}
});


Zone.__load_patch('util', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
api.patchOnProperties = patchOnProperties;
api.patchMethod = patchMethod;
Expand Down
9 changes: 6 additions & 3 deletions lib/browser/define-property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ const _getOwnPropertyDescriptor = (Object as any)[zoneSymbol('getOwnPropertyDesc
Object.getOwnPropertyDescriptor;
const _create = Object.create;
const unconfigurablesKey = zoneSymbol('unconfigurables');
const PROTOTYPE = 'prototype';
const OBJECT = 'object';
const UNDEFINED = 'undefined';

export function propertyPatch() {
Object.defineProperty = function(obj, prop, desc) {
if (isUnconfigurable(obj, prop)) {
throw new TypeError('Cannot assign to read only property \'' + prop + '\' of ' + obj);
}
const originalConfigurableFlag = desc.configurable;
if (prop !== 'prototype') {
if (prop !== PROTOTYPE) {
desc = rewriteDescriptor(obj, prop, desc);
}
return _tryDefineProperty(obj, prop, desc, originalConfigurableFlag);
Expand All @@ -38,7 +41,7 @@ export function propertyPatch() {
};

Object.create = <any>function(obj: any, proto: any) {
if (typeof proto === 'object' && !Object.isFrozen(proto)) {
if (typeof proto === OBJECT && !Object.isFrozen(proto)) {
Object.keys(proto).forEach(function(prop) {
proto[prop] = rewriteDescriptor(obj, prop, proto[prop]);
});
Expand Down Expand Up @@ -83,7 +86,7 @@ function _tryDefineProperty(obj: any, prop: string, desc: any, originalConfigura
if (desc.configurable) {
// In case of errors, when the configurable flag was likely set by rewriteDescriptor(), let's
// retry with the original flag value
if (typeof originalConfigurableFlag == 'undefined') {
if (typeof originalConfigurableFlag == UNDEFINED) {
delete desc.configurable;
} else {
desc.configurable = originalConfigurableFlag;
Expand Down
11 changes: 6 additions & 5 deletions lib/browser/property-descriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ const webglEventNames = ['webglcontextrestored', 'webglcontextlost', 'webglconte
const formEventNames = ['autocomplete', 'autocompleteerror'];
const detailEventNames = ['toggle'];
const frameEventNames = ['load'];
const frameSetEventNames = ['blur', 'error', 'focus', 'load', 'resize', 'scroll'];
const frameSetEventNames = ['blur', 'error', 'focus', 'load', 'resize', 'scroll', 'messageerror'];
const marqueeEventNames = ['bounce', 'finish', 'start'];

const XMLHttpRequestEventNames = [
Expand All @@ -241,7 +241,7 @@ export function propertyDescriptorPatch(api: _ZonePrivate, _global: any) {
if (isBrowser) {
// in IE/Edge, onProp not exist in window object, but in WindowPrototype
// so we need to pass WindowPrototype to check onProp exist or not
patchOnProperties(window, eventNames, Object.getPrototypeOf(window));
patchOnProperties(window, eventNames.concat(['messageerror']), Object.getPrototypeOf(window));
patchOnProperties(Document.prototype, eventNames);

if (typeof(<any>window)['SVGElement'] !== 'undefined') {
Expand Down Expand Up @@ -319,20 +319,21 @@ function canPatchViaPropertyDescriptor() {
Object.defineProperty(XMLHttpRequest.prototype, 'onreadystatechange', xhrDesc || {});
return result;
} else {
const SYMBOL_FAKE_ONREADYSTATECHANGE = zoneSymbol('fakeonreadystatechange');
Object.defineProperty(XMLHttpRequest.prototype, 'onreadystatechange', {
enumerable: true,
configurable: true,
get: function() {
return this[zoneSymbol('fakeonreadystatechange')];
return this[SYMBOL_FAKE_ONREADYSTATECHANGE];
},
set: function(value) {
this[zoneSymbol('fakeonreadystatechange')] = value;
this[SYMBOL_FAKE_ONREADYSTATECHANGE] = value;
}
});
const req = new XMLHttpRequest();
const detectFunc = () => {};
req.onreadystatechange = detectFunc;
const result = (req as any)[zoneSymbol('fakeonreadystatechange')] === detectFunc;
const result = (req as any)[SYMBOL_FAKE_ONREADYSTATECHANGE] === detectFunc;
req.onreadystatechange = null;
return result;
}
Expand Down
15 changes: 12 additions & 3 deletions lib/browser/websocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,15 @@ export function apply(api: _ZonePrivate, _global: any) {
proxySocketProto = socket;
['addEventListener', 'removeEventListener', 'send', 'close'].forEach(function(propName) {
proxySocket[propName] = function() {
return socket[propName].apply(socket, arguments);
const args = Array.prototype.slice.call(arguments);
if (propName === 'addEventListener' || propName === 'removeEventListener') {
const eventName = args.length > 0 ? args[0] : undefined;
if (eventName) {
const propertySymbol = Zone.__symbol__('ON_PROPERTY' + eventName);
socket[propertySymbol] = proxySocket[propertySymbol];
}
}
return socket[propName].apply(socket, args);
};
});
} else {
Expand All @@ -42,10 +50,11 @@ export function apply(api: _ZonePrivate, _global: any) {
}

patchOnProperties(proxySocket, ['close', 'error', 'message', 'open'], proxySocketProto);

return proxySocket;
};

const globalWebSocket = _global['WebSocket'];
for (const prop in WS) {
_global['WebSocket'][prop] = WS[prop];
globalWebSocket[prop] = WS[prop];
}
}
29 changes: 20 additions & 9 deletions lib/common/error-rewrite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,10 @@ Zone.__load_patch('Error', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
// We got called with a `new` operator AND we are subclass of ZoneAwareError
// in that case we have to copy all of our properties to `this`.
Object.keys(error).concat('stack', 'message').forEach((key) => {
if ((error as any)[key] !== undefined) {
const value = (error as any)[key];
if (value !== undefined) {
try {
this[key] = (error as any)[key];
this[key] = value;
} catch (e) {
// ignore the assignment in case it is a setter and it throws.
}
Expand Down Expand Up @@ -166,6 +167,7 @@ Zone.__load_patch('Error', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
});
}

const ZONE_CAPTURESTACKTRACE = 'zoneCaptureStackTrace';
Object.defineProperty(ZoneAwareError, 'prepareStackTrace', {
get: function() {
return NativeError.prepareStackTrace;
Expand All @@ -181,7 +183,7 @@ Zone.__load_patch('Error', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
for (let i = 0; i < structuredStackTrace.length; i++) {
const st = structuredStackTrace[i];
// remove the first function which name is zoneCaptureStackTrace
if (st.getFunctionName() === 'zoneCaptureStackTrace') {
if (st.getFunctionName() === ZONE_CAPTURESTACKTRACE) {
structuredStackTrace.splice(i, 1);
break;
}
Expand All @@ -196,6 +198,15 @@ Zone.__load_patch('Error', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
// run/runGuarded/runTask frames. This is done by creating a detect zone and then threading
// the execution through all of the above methods so that we can look at the stack trace and
// find the frames of interest.
const ZONE_AWARE_ERROR = 'ZoneAwareError';
const ERROR_DOT = 'Error.';
const EMPTY = '';
const RUN_GUARDED = 'runGuarded';
const RUN_TASK = 'runTask';
const RUN = 'run';
const BRACKETS = '(';
const AT = '@';

let detectZone: Zone = Zone.current.fork({
name: 'detect',
onHandleError: function(parentZD: ZoneDelegate, current: Zone, target: Zone, error: any):
Expand All @@ -215,18 +226,18 @@ Zone.__load_patch('Error', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
// Chrome: at Zone.run (http://localhost:9876/base/build/lib/zone.js:100:24)
// FireFox: Zone.prototype.run@http://localhost:9876/base/build/lib/zone.js:101:24
// Safari: run@http://localhost:9876/base/build/lib/zone.js:101:24
let fnName: string = frame.split('(')[0].split('@')[0];
let fnName: string = frame.split(BRACKETS)[0].split(AT)[0];
let frameType = FrameType.transition;
if (fnName.indexOf('ZoneAwareError') !== -1) {
if (fnName.indexOf(ZONE_AWARE_ERROR) !== -1) {
zoneAwareFrame1 = frame;
zoneAwareFrame2 = frame.replace('Error.', '');
zoneAwareFrame2 = frame.replace(ERROR_DOT, EMPTY);
blackListedStackFrames[zoneAwareFrame2] = FrameType.blackList;
}
if (fnName.indexOf('runGuarded') !== -1) {
if (fnName.indexOf(RUN_GUARDED) !== -1) {
runGuardedFrame = true;
} else if (fnName.indexOf('runTask') !== -1) {
} else if (fnName.indexOf(RUN_TASK) !== -1) {
runTaskFrame = true;
} else if (fnName.indexOf('run') !== -1) {
} else if (fnName.indexOf(RUN) !== -1) {
runFrame = true;
} else {
frameType = FrameType.blackList;
Expand Down
32 changes: 21 additions & 11 deletions lib/common/promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
}
};

const UNHANDLED_PROMISE_REJECTION_HANDLER_SYMBOL = __symbol__('unhandledPromiseRejectionHandler');

function handleUnhandledRejection(e: any) {
api.onUnhandledError(e);
try {
const handler = (Zone as any)[__symbol__('unhandledPromiseRejectionHandler')];
const handler = (Zone as any)[UNHANDLED_PROMISE_REJECTION_HANDLER_SYMBOL];
if (handler && typeof handler === 'function') {
handler.apply(this, [e]);
}
Expand Down Expand Up @@ -104,18 +106,23 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
};
};

const TYPE_ERROR = 'Promise resolved with itself';
const OBJECT = 'object';
const FUNCTION = 'function';
const CURRENT_TASK_SYMBOL = __symbol__('currentTask');

// Promise Resolution
function resolvePromise(
promise: ZoneAwarePromise<any>, state: boolean, value: any): ZoneAwarePromise<any> {
const onceWrapper = once();
if (promise === value) {
throw new TypeError('Promise resolved with itself');
throw new TypeError(TYPE_ERROR);
}
if ((promise as any)[symbolState] === UNRESOLVED) {
// should only get value.then once based on promise spec.
let then: any = null;
try {
if (typeof value === 'object' || typeof value === 'function') {
if (typeof value === OBJECT || typeof value === FUNCTION) {
then = value && value.then;
}
} catch (err) {
Expand All @@ -130,7 +137,7 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
(value as any)[symbolState] !== UNRESOLVED) {
clearRejectedNoCatch(<Promise<any>>value);
resolvePromise(promise, (value as any)[symbolState], (value as any)[symbolValue]);
} else if (state !== REJECTED && typeof then === 'function') {
} else if (state !== REJECTED && typeof then === FUNCTION) {
try {
then.apply(value, [
onceWrapper(makeResolver(promise, state)), onceWrapper(makeResolver(promise, false))
Expand All @@ -148,7 +155,7 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
// record task information in value when error occurs, so we can
// do some additional work such as render longStackTrace
if (state === REJECTED && value instanceof Error) {
(value as any)[__symbol__('currentTask')] = Zone.currentTask;
(value as any)[CURRENT_TASK_SYMBOL] = Zone.currentTask;
}

for (let i = 0; i < queue.length;) {
Expand Down Expand Up @@ -176,6 +183,7 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
return promise;
}

const REJECTION_HANDLED_HANDLER = __symbol__('rejectionHandledHandler');
function clearRejectedNoCatch(promise: ZoneAwarePromise<any>): void {
if ((promise as any)[symbolState] === REJECTED_NO_CATCH) {
// if the promise is rejected no catch status
Expand All @@ -184,8 +192,8 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
// windows.rejectionhandled eventHandler or nodejs rejectionHandled
// eventHandler
try {
const handler = (Zone as any)[__symbol__('rejectionHandledHandler')];
if (handler && typeof handler === 'function') {
const handler = (Zone as any)[REJECTION_HANDLED_HANDLER];
if (handler && typeof handler === FUNCTION) {
handler.apply(this, [{rejection: (promise as any)[symbolValue], promise: promise}]);
}
} catch (err) {
Expand All @@ -204,8 +212,8 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
onFulfilled?: (value: R) => U, onRejected?: (error: any) => U): void {
clearRejectedNoCatch(promise);
const delegate = (promise as any)[symbolState] ?
(typeof onFulfilled === 'function') ? onFulfilled : forwardResolution :
(typeof onRejected === 'function') ? onRejected : forwardRejection;
(typeof onFulfilled === FUNCTION) ? onFulfilled : forwardResolution :
(typeof onRejected === FUNCTION) ? onRejected : forwardRejection;
zone.scheduleMicroTask(source, () => {
try {
resolvePromise(
Expand All @@ -216,9 +224,11 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
});
}

const ZONE_AWARE_PROMISE_TO_STRING = 'function ZoneAwarePromise() { [native code] }';

class ZoneAwarePromise<R> implements Promise<R> {
static toString() {
return 'function ZoneAwarePromise() { [native code] }';
return ZONE_AWARE_PROMISE_TO_STRING;
}

static resolve<R>(value: R): Promise<R> {
Expand Down Expand Up @@ -364,7 +374,7 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
patchThen(NativePromise);

let fetch = global['fetch'];
if (typeof fetch == 'function') {
if (typeof fetch == FUNCTION) {
global['fetch'] = zoneify(fetch);
}
}
Expand Down
Loading