From dd96b3c7c3c26b43faef57d93856311019f27dea Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Wed, 19 Jul 2017 01:30:26 +0900 Subject: [PATCH 1/2] feat(performance): onProperty use global wrapFn, other perf improve --- lib/browser/browser.ts | 50 +++++++++++++++++++--------- lib/browser/define-property.ts | 9 +++-- lib/browser/property-descriptor.ts | 11 ++++--- lib/browser/websocket.ts | 15 +++++++-- lib/common/error-rewrite.ts | 29 +++++++++++----- lib/common/promise.ts | 32 +++++++++++------- lib/common/timers.ts | 28 +++++++++------- lib/common/to-string.ts | 20 +++++++---- lib/common/utils.ts | 53 +++++++++++++++++++----------- lib/extra/bluebird.ts | 5 +-- lib/extra/cordova.ts | 11 ++++--- lib/zone.ts | 4 ++- test/browser/browser.spec.ts | 11 ++++--- 13 files changed, 184 insertions(+), 94 deletions(-) diff --git a/lib/browser/browser.ts b/lib/browser/browser.ts index 2347345a6..e145c560a 100644 --- a/lib/browser/browser.ts +++ b/lib/browser/browser.ts @@ -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 = 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; } @@ -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; @@ -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 @@ -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; diff --git a/lib/browser/define-property.ts b/lib/browser/define-property.ts index 737e26417..22a286449 100644 --- a/lib/browser/define-property.ts +++ b/lib/browser/define-property.ts @@ -17,6 +17,9 @@ 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) { @@ -24,7 +27,7 @@ export function propertyPatch() { 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); @@ -38,7 +41,7 @@ export function propertyPatch() { }; Object.create = 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]); }); @@ -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; diff --git a/lib/browser/property-descriptor.ts b/lib/browser/property-descriptor.ts index ecbf50b2b..1186c1637 100644 --- a/lib/browser/property-descriptor.ts +++ b/lib/browser/property-descriptor.ts @@ -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 = [ @@ -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(window)['SVGElement'] !== 'undefined') { @@ -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; } diff --git a/lib/browser/websocket.ts b/lib/browser/websocket.ts index 29ee6c746..d9f3354c9 100644 --- a/lib/browser/websocket.ts +++ b/lib/browser/websocket.ts @@ -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 { @@ -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]; } } diff --git a/lib/common/error-rewrite.ts b/lib/common/error-rewrite.ts index 081403572..e9ea0639d 100644 --- a/lib/common/error-rewrite.ts +++ b/lib/common/error-rewrite.ts @@ -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. } @@ -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; @@ -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; } @@ -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): @@ -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; diff --git a/lib/common/promise.ts b/lib/common/promise.ts index c0cf3eeba..6dcb4e76b 100644 --- a/lib/common/promise.ts +++ b/lib/common/promise.ts @@ -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]); } @@ -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, state: boolean, value: any): ZoneAwarePromise { 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) { @@ -130,7 +137,7 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr (value as any)[symbolState] !== UNRESOLVED) { clearRejectedNoCatch(>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)) @@ -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;) { @@ -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): void { if ((promise as any)[symbolState] === REJECTED_NO_CATCH) { // if the promise is rejected no catch status @@ -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) { @@ -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( @@ -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 implements Promise { static toString() { - return 'function ZoneAwarePromise() { [native code] }'; + return ZONE_AWARE_PROMISE_TO_STRING; } static resolve(value: R): Promise { @@ -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); } } diff --git a/lib/common/timers.ts b/lib/common/timers.ts index 9f46c75f7..33d234023 100644 --- a/lib/common/timers.ts +++ b/lib/common/timers.ts @@ -24,6 +24,12 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam cancelName += nameSuffix; const tasksByHandleId: {[id: number]: Task} = {}; + const NUMBER = 'number'; + const STRING = 'string'; + const FUNCTION = 'function'; + const INTERVAL = 'Interval'; + const TIMEOUT = 'Timeout'; + const NOT_SCHEDULED = 'notScheduled'; function scheduleTask(task: Task) { const data = task.data; @@ -31,7 +37,7 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam try { task.invoke.apply(this, arguments); } finally { - if (typeof data.handleId === 'number') { + if (typeof data.handleId === NUMBER) { // Node returns complex objects as handleIds delete tasksByHandleId[data.handleId]; } @@ -39,7 +45,7 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam } data.args[0] = timer; data.handleId = setNative.apply(window, data.args); - if (typeof data.handleId === 'number') { + if (typeof data.handleId === NUMBER) { // Node returns complex objects as handleIds -> no need to keep them around. Additionally, // this throws an // exception in older node versions and has no effect there, because of the stringified key. @@ -49,7 +55,7 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam } function clearTask(task: Task) { - if (typeof(task.data).handleId === 'number') { + if (typeof(task.data).handleId === NUMBER) { // Node returns complex objects as handleIds delete tasksByHandleId[(task.data).handleId]; } @@ -58,12 +64,12 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam setNative = patchMethod(window, setName, (delegate: Function) => function(self: any, args: any[]) { - if (typeof args[0] === 'function') { + if (typeof args[0] === FUNCTION) { const zone = Zone.current; const options: TimerOptions = { handleId: null, - isPeriodic: nameSuffix === 'Interval', - delay: (nameSuffix === 'Timeout' || nameSuffix === 'Interval') ? args[1] || 0 : null, + isPeriodic: nameSuffix === INTERVAL, + delay: (nameSuffix === TIMEOUT || nameSuffix === INTERVAL) ? args[1] || 0 : null, args: args }; const task = zone.scheduleMacroTask(setName, args[0], options, scheduleTask, clearTask); @@ -74,8 +80,8 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam const handle: any = (task.data).handleId; // check whether handle is null, because some polyfill or browser // may return undefined from setTimeout/setInterval/setImmediate/requestAnimationFrame - if (handle && handle.ref && handle.unref && typeof handle.ref === 'function' && - typeof handle.unref === 'function') { + if (handle && handle.ref && handle.unref && typeof handle.ref === FUNCTION && + typeof handle.unref === FUNCTION) { (task).ref = (handle).ref.bind(handle); (task).unref = (handle).unref.bind(handle); } @@ -88,9 +94,9 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam clearNative = patchMethod(window, cancelName, (delegate: Function) => function(self: any, args: any[]) { - const task: Task = typeof args[0] === 'number' ? tasksByHandleId[args[0]] : args[0]; - if (task && typeof task.type === 'string') { - if (task.state !== 'notScheduled' && + const task: Task = typeof args[0] === NUMBER ? tasksByHandleId[args[0]] : args[0]; + if (task && typeof task.type === STRING) { + if (task.state !== NOT_SCHEDULED && (task.cancelFn && task.data.isPeriodic || task.runCount === 0)) { // Do not cancel already canceled functions task.zone.cancelTask(task); diff --git a/lib/common/to-string.ts b/lib/common/to-string.ts index 3d3bfb035..9e7d7fe71 100644 --- a/lib/common/to-string.ts +++ b/lib/common/to-string.ts @@ -13,24 +13,29 @@ Zone.__load_patch('toString', (global: any, Zone: ZoneType, api: _ZonePrivate) = // patch Func.prototype.toString to let them look like native const originalFunctionToString = (Zone as any)['__zone_symbol__originalToString'] = Function.prototype.toString; + + const FUNCTION = 'function'; + const ORIGINAL_DELEGATE_SYMBOL = zoneSymbol('OriginalDelegate'); + const PROMISE_SYMBOL = zoneSymbol('Promise'); + const ERROR_SYMBOL = zoneSymbol('Error'); Function.prototype.toString = function() { - if (typeof this === 'function') { - const originalDelegate = this[zoneSymbol('OriginalDelegate')]; + if (typeof this === FUNCTION) { + const originalDelegate = this[ORIGINAL_DELEGATE_SYMBOL]; if (originalDelegate) { - if (typeof originalDelegate === 'function') { - return originalFunctionToString.apply(this[zoneSymbol('OriginalDelegate')], arguments); + if (typeof originalDelegate === FUNCTION) { + return originalFunctionToString.apply(this[ORIGINAL_DELEGATE_SYMBOL], arguments); } else { return Object.prototype.toString.call(originalDelegate); } } if (this === Promise) { - const nativePromise = global[zoneSymbol('Promise')]; + const nativePromise = global[PROMISE_SYMBOL]; if (nativePromise) { return originalFunctionToString.apply(nativePromise, arguments); } } if (this === Error) { - const nativeError = global[zoneSymbol('Error')]; + const nativeError = global[ERROR_SYMBOL]; if (nativeError) { return originalFunctionToString.apply(nativeError, arguments); } @@ -42,9 +47,10 @@ Zone.__load_patch('toString', (global: any, Zone: ZoneType, api: _ZonePrivate) = // patch Object.prototype.toString to let them look like native const originalObjectToString = Object.prototype.toString; + const PROMISE_OBJECT_TO_STRING = '[object Promise]'; Object.prototype.toString = function() { if (this instanceof Promise) { - return '[object Promise]'; + return PROMISE_OBJECT_TO_STRING; } return originalObjectToString.apply(this, arguments); }; diff --git a/lib/common/utils.ts b/lib/common/utils.ts index de6421621..429532a98 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -18,9 +18,13 @@ export const zoneSymbol = Zone.__symbol__; const _global: any = typeof window === 'object' && window || typeof self === 'object' && self || global; +const FUNCTION = 'function'; +const UNDEFINED = 'undefined'; +const REMOVE_ATTRIBUTE = 'removeAttribute'; + export function bindArguments(args: any[], source: string): any[] { for (let i = args.length - 1; i >= 0; i--) { - if (typeof args[i] === 'function') { + if (typeof args[i] === FUNCTION) { args[i] = Zone.current.wrap(args[i], source + '_' + i); } } @@ -57,7 +61,7 @@ export function isPropertyWritable(propertyDesc: any) { return false; } - if (typeof propertyDesc.get === 'function' && typeof propertyDesc.set === 'undefined') { + if (typeof propertyDesc.get === FUNCTION && typeof propertyDesc.set === UNDEFINED) { return false; } @@ -83,6 +87,22 @@ export const isMix: boolean = typeof _global.process !== 'undefined' && {}.toString.call(_global.process) === '[object process]' && !isWebWorker && !!(typeof window !== 'undefined' && (window as any)['HTMLElement']); +const ON_PROPERTY_HANDLER_SYMBOL = zoneSymbol('onPropertyHandler'); +const zoneSymbolEventNames: {[eventName: string]: string} = {}; +const wrapFn = function(event: Event) { + let eventNameSymbol = zoneSymbolEventNames[event.type]; + if (!eventNameSymbol) { + eventNameSymbol = zoneSymbolEventNames[event.type] = zoneSymbol('ON_PROPERTY' + event.type); + } + const listener = this[eventNameSymbol]; + let result = listener && listener.apply(this, arguments); + + if (result != undefined && !result) { + event.preventDefault(); + } + return result; +}; + export function patchProperty(obj: any, prop: string, prototype?: any) { let desc = Object.getOwnPropertyDescriptor(obj, prop); if (!desc && prototype) { @@ -109,7 +129,11 @@ export function patchProperty(obj: any, prop: string, prototype?: any) { // substr(2) cuz 'onclick' -> 'click', etc const eventName = prop.substr(2); - const _prop = zoneSymbol('_' + prop); + + let eventNameSymbol = zoneSymbolEventNames[eventName]; + if (!eventNameSymbol) { + eventNameSymbol = zoneSymbolEventNames[eventName] = zoneSymbol('ON_PROPERTY' + eventName); + } desc.set = function(newValue) { // in some of windows's onproperty callback, this is undefined @@ -121,25 +145,16 @@ export function patchProperty(obj: any, prop: string, prototype?: any) { if (!target) { return; } - let previousValue = target[_prop]; + let previousValue = target[eventNameSymbol]; if (previousValue) { - target.removeEventListener(eventName, previousValue); + target.removeEventListener(eventName, wrapFn); } if (typeof newValue === 'function') { - const wrapFn = function(event: Event) { - let result = newValue.apply(this, arguments); - - if (result != undefined && !result) { - event.preventDefault(); - } - return result; - }; - - target[_prop] = wrapFn; + target[eventNameSymbol] = newValue; target.addEventListener(eventName, wrapFn, false); } else { - target[_prop] = null; + target[eventNameSymbol] = null; } }; @@ -155,8 +170,8 @@ export function patchProperty(obj: any, prop: string, prototype?: any) { if (!target) { return null; } - if (target.hasOwnProperty(_prop)) { - return target[_prop]; + if (target.hasOwnProperty(eventNameSymbol)) { + return wrapFn; } else if (originalDescGet) { // result will be null when use inline event attribute, // such as @@ -167,7 +182,7 @@ export function patchProperty(obj: any, prop: string, prototype?: any) { let value = originalDescGet && originalDescGet.apply(this); if (value) { desc.set.apply(this, [value]); - if (typeof target['removeAttribute'] === 'function') { + if (typeof target[REMOVE_ATTRIBUTE] === FUNCTION) { target.removeAttribute(prop); } return value; diff --git a/lib/extra/bluebird.ts b/lib/extra/bluebird.ts index 093c88378..0a70f5cbf 100644 --- a/lib/extra/bluebird.ts +++ b/lib/extra/bluebird.ts @@ -11,9 +11,10 @@ Zone.__load_patch('bluebird', (global: any, Zone: ZoneType, api: _ZonePrivate) = // global.Promise is not Bluebird, and Bluebird is just be // used by other libraries such as sequelize, so I think it is // safe to just expose a method to patch Bluebird explicitly - (Zone as any)[Zone.__symbol__('bluebird')] = function patchBluebird(Bluebird: any) { + const BLUEBIRD = 'bluebird'; + (Zone as any)[Zone.__symbol__(BLUEBIRD)] = function patchBluebird(Bluebird: any) { Bluebird.setScheduler((fn: Function) => { - Zone.current.scheduleMicroTask('bluebird', fn); + Zone.current.scheduleMicroTask(BLUEBIRD, fn); }); }; }); \ No newline at end of file diff --git a/lib/extra/cordova.ts b/lib/extra/cordova.ts index d6ac49b12..6331f73eb 100644 --- a/lib/extra/cordova.ts +++ b/lib/extra/cordova.ts @@ -7,13 +7,16 @@ */ Zone.__load_patch('cordova', (global: any, Zone: ZoneType, api: _ZonePrivate) => { if (global.cordova) { + const SUCCESS_SOURCE = 'cordova.exec.success'; + const ERROR_SOURCE = 'cordova.exec.error'; + const FUNCTION = 'function'; const nativeExec: Function = api.patchMethod( global.cordova, 'exec', (delegate: Function) => function(self: any, args: any[]) { - if (args.length > 0 && typeof args[0] === 'function') { - args[0] = Zone.current.wrap(args[0], 'cordova.exec.success'); + if (args.length > 0 && typeof args[0] === FUNCTION) { + args[0] = Zone.current.wrap(args[0], SUCCESS_SOURCE); } - if (args.length > 1 && typeof args[1] === 'function') { - args[1] = Zone.current.wrap(args[1], 'cordova.exec.error'); + if (args.length > 1 && typeof args[1] === FUNCTION) { + args[1] = Zone.current.wrap(args[1], ERROR_SOURCE); } return nativeExec.apply(self, args); }); diff --git a/lib/zone.ts b/lib/zone.ts index 25538118a..bf4fd3480 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -624,6 +624,8 @@ type AmbientZone = Zone; type AmbientZoneDelegate = ZoneDelegate; const Zone: ZoneType = (function(global: any) { + const FUNCTION = 'function'; + const performance: {mark(name: string): void; measure(name: string, label: string): void;} = global['performance']; function mark(name: string) { @@ -720,7 +722,7 @@ const Zone: ZoneType = (function(global: any) { } public wrap(callback: T, source: string): T { - if (typeof callback !== 'function') { + if (typeof callback !== FUNCTION) { throw new Error('Expecting function got: ' + callback); } const _callback = this._zoneDelegate.intercept(this, callback, source); diff --git a/test/browser/browser.spec.ts b/test/browser/browser.spec.ts index 545456fc4..0d34a4c81 100644 --- a/test/browser/browser.spec.ts +++ b/test/browser/browser.spec.ts @@ -127,11 +127,14 @@ describe('Zone', function() { function checkIsOnPropertiesPatched(target: any) { for (let prop in target) { - if (prop.substr(0, 2) === 'on') { + if (prop.substr(0, 2) === 'on' && prop.length > 2) { target[prop] = noop; - expect(target[Zone.__symbol__('_' + prop)]).toBeTruthy(); + if (!target[Zone.__symbol__('ON_PROPERTY' + prop.substr(2))]) { + console.log('prop', prop); + } + expect(target[Zone.__symbol__('ON_PROPERTY' + prop.substr(2))]).toBeTruthy(); target[prop] = null; - expect(!target[Zone.__symbol__('_' + prop)]).toBeTruthy(); + expect(!target[Zone.__symbol__('ON_PROPERTY' + prop.substr(2))]).toBeTruthy(); } } } @@ -188,7 +191,7 @@ describe('Zone', function() { it('window onresize should be patched', ifEnvSupports(canPatchOnProperty(window, 'onmousedown'), function() { window.onresize = eventListenerSpy; - const innerResizeProp: any = (window as any)[zoneSymbol('_onresize')]; + const innerResizeProp: any = (window as any)[zoneSymbol('ON_PROPERTYresize')]; expect(innerResizeProp).toBeTruthy(); innerResizeProp(); expect(eventListenerSpy).toHaveBeenCalled(); From 614cc806f67d0ed3704e6c25605c4d9191fc1c94 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Fri, 11 Aug 2017 02:35:02 +0900 Subject: [PATCH 2/2] add check --- lib/common/utils.ts | 2 +- test/rxjs/rxjs.Observable.combine.spec.ts | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 429532a98..0015abdcf 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -170,7 +170,7 @@ export function patchProperty(obj: any, prop: string, prototype?: any) { if (!target) { return null; } - if (target.hasOwnProperty(eventNameSymbol)) { + if (target[eventNameSymbol]) { return wrapFn; } else if (originalDescGet) { // result will be null when use inline event attribute, diff --git a/test/rxjs/rxjs.Observable.combine.spec.ts b/test/rxjs/rxjs.Observable.combine.spec.ts index c524637f8..2196369e1 100644 --- a/test/rxjs/rxjs.Observable.combine.spec.ts +++ b/test/rxjs/rxjs.Observable.combine.spec.ts @@ -20,13 +20,11 @@ describe('Observable.combine', () => { const constructorZone1: Zone = Zone.current.fork({name: 'Constructor Zone1'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); observable1 = constructorZone1.run(() => { - const source = Rx.Observable.interval(10); - const highOrder = source - .map((src: any) => { - expect(Zone.current.name).toEqual(constructorZone1.name); - return Rx.Observable.interval(50).take(3); - }) - .take(2); + const source = Rx.Observable.of(1, 2); + const highOrder = source.map((src: any) => { + expect(Zone.current.name).toEqual(constructorZone1.name); + return Rx.Observable.of(src); + }); return highOrder.combineAll(); }); @@ -42,12 +40,10 @@ describe('Observable.combine', () => { () => { log.push('completed'); expect(Zone.current.name).toEqual(subscriptionZone.name); - expect(log).toEqual([[0, 0], [1, 0], [1, 1], [2, 1], [2, 2], 'completed']); + expect(log).toEqual([[1, 2], 'completed']); done(); }); }); - - expect(log).toEqual([]); }, Zone.root)); it('combineAll func callback should run in the correct zone with project function',