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

Commit

Permalink
feat(performance): onProperty handler use global wrapFn, other perfor…
Browse files Browse the repository at this point in the history
…mance improve. (#872)

* feat(performance): onProperty use global wrapFn, other perf improve

* add check
  • Loading branch information
JiaLiPassion authored and mhevery committed Aug 12, 2017
1 parent 31d38c1 commit a66595a
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 104 deletions.
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

0 comments on commit a66595a

Please sign in to comment.