From c79aac01b01bb692adf9abe21027e0ac62edf0b9 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Sun, 19 Aug 2018 11:09:24 +0900 Subject: [PATCH 1/2] feat(module): can let zone run in native mode --- lib/browser/browser.ts | 54 +++++++++++----------- lib/browser/define-property.ts | 11 ++++- lib/browser/event-target.ts | 2 +- lib/browser/property-descriptor.ts | 50 ++++++++++---------- lib/browser/register-element.ts | 5 +- lib/browser/shadydom.ts | 4 +- lib/browser/webapis-media-query.ts | 6 +-- lib/browser/webapis-notification.ts | 2 +- lib/browser/webapis-resize-observer.ts | 8 ++-- lib/browser/webapis-rtc-peer-connection.ts | 2 +- lib/browser/websocket.ts | 4 +- lib/common/events.ts | 16 ++++++- lib/common/fetch.ts | 10 +++- lib/common/promise.ts | 3 ++ lib/common/timers.ts | 6 +-- lib/common/to-string.ts | 2 +- lib/common/utils.ts | 43 +++++++++++------ lib/extra/bluebird.ts | 2 +- lib/extra/cordova.ts | 2 +- lib/extra/electron.ts | 2 +- lib/extra/jsonp.ts | 4 +- lib/extra/socket-io.ts | 2 +- lib/node/events.ts | 4 +- lib/node/fs.ts | 4 +- lib/node/node.ts | 31 +++++++------ lib/rxjs/rxjs-fake-async.ts | 6 +-- lib/zone.ts | 24 ++++++++-- test/browser/browser.spec.ts | 10 ++-- test/browser/requestAnimationFrame.spec.ts | 2 +- test/common/util.spec.ts | 19 ++++---- test/test-util.ts | 18 ++++++++ test/zone-spec/fake-async-test.spec.ts | 8 ++-- 32 files changed, 226 insertions(+), 140 deletions(-) diff --git a/lib/browser/browser.ts b/lib/browser/browser.ts index bf01626d7..792a8bcf8 100644 --- a/lib/browser/browser.ts +++ b/lib/browser/browser.ts @@ -25,21 +25,21 @@ Zone.__load_patch('util', (global: any, Zone: ZoneType, api: _ZonePrivate) => { api.bindArguments = bindArguments; }); -Zone.__load_patch('timers', (global: any) => { +Zone.__load_patch('timers', (global: any, _: ZoneType, api: _ZonePrivate) => { const set = 'set'; const clear = 'clear'; - patchTimer(global, set, clear, 'Timeout'); - patchTimer(global, set, clear, 'Interval'); - patchTimer(global, set, clear, 'Immediate'); + patchTimer(global, set, clear, 'Timeout', api); + patchTimer(global, set, clear, 'Interval', api); + patchTimer(global, set, clear, 'Immediate', api); }); -Zone.__load_patch('requestAnimationFrame', (global: any) => { - patchTimer(global, 'request', 'cancel', 'AnimationFrame'); - patchTimer(global, 'mozRequest', 'mozCancel', 'AnimationFrame'); - patchTimer(global, 'webkitRequest', 'webkitCancel', 'AnimationFrame'); +Zone.__load_patch('requestAnimationFrame', (global: any, _: ZoneType, api: _ZonePrivate) => { + patchTimer(global, 'request', 'cancel', 'AnimationFrame', api); + patchTimer(global, 'mozRequest', 'mozCancel', 'AnimationFrame', api); + patchTimer(global, 'webkitRequest', 'webkitCancel', 'AnimationFrame', api); }); -Zone.__load_patch('blocking', (global: any, Zone: ZoneType) => { +Zone.__load_patch('blocking', (global: any, Zone: ZoneType, api: _ZonePrivate) => { const blockingMethods = ['alert', 'prompt', 'confirm']; for (let i = 0; i < blockingMethods.length; i++) { const name = blockingMethods[i]; @@ -47,7 +47,7 @@ Zone.__load_patch('blocking', (global: any, Zone: ZoneType) => { return function(s: any, args: any[]) { return Zone.current.run(delegate, global, args, name); }; - }); + }, api); } }); @@ -63,33 +63,33 @@ Zone.__load_patch('EventTarget', (global: any, Zone: ZoneType, api: _ZonePrivate // patch XMLHttpRequestEventTarget's addEventListener/removeEventListener const XMLHttpRequestEventTarget = (global as any)['XMLHttpRequestEventTarget']; if (XMLHttpRequestEventTarget && XMLHttpRequestEventTarget.prototype) { - api.patchEventTarget(global, [XMLHttpRequestEventTarget.prototype]); + api.patchEventTarget(global, [XMLHttpRequestEventTarget.prototype], api); } - patchClass('MutationObserver'); - patchClass('WebKitMutationObserver'); - patchClass('IntersectionObserver'); - patchClass('FileReader'); + patchClass('MutationObserver', api); + patchClass('WebKitMutationObserver', api); + patchClass('IntersectionObserver', api); + patchClass('FileReader', api); }); Zone.__load_patch('on_property', (global: any, Zone: ZoneType, api: _ZonePrivate) => { propertyDescriptorPatch(api, global); - propertyPatch(); - registerElementPatch(global); + propertyPatch(api); + registerElementPatch(global, api); }); -Zone.__load_patch('canvas', (global: any) => { +Zone.__load_patch('canvas', (global: any, _: ZoneType, api: _ZonePrivate) => { const HTMLCanvasElement = global['HTMLCanvasElement']; if (typeof HTMLCanvasElement !== 'undefined' && HTMLCanvasElement.prototype && HTMLCanvasElement.prototype.toBlob) { patchMacroTask(HTMLCanvasElement.prototype, 'toBlob', (self: any, args: any[]) => { return {name: 'HTMLCanvasElement.toBlob', target: self, cbIdx: 0, args: args}; - }); + }, api); } }); -Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => { +Zone.__load_patch('XHR', (global: any, Zone: ZoneType, api: _ZonePrivate) => { // Treat XMLHttpRequest as a macrotask. - patchXHR(global); + patchXHR(global, api); const XHR_TASK = zoneSymbol('xhrTask'); const XHR_SYNC = zoneSymbol('xhrSync'); @@ -105,7 +105,7 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => { aborted: boolean; } - function patchXHR(window: any) { + function patchXHR(window: any, api: _ZonePrivate) { const XMLHttpRequestPrototype: any = XMLHttpRequest.prototype; function findPendingTask(target: any) { @@ -201,7 +201,7 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => { self[XHR_SYNC] = args[2] == false; self[XHR_URL] = args[1]; return openNative!.apply(self, args); - }); + }, api); const XMLHTTPREQUEST_SOURCE = 'XMLHttpRequest.send'; const fetchTaskAborting = zoneSymbol('fetchTaskAborting'); @@ -230,7 +230,7 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => { task.invoke(); } } - }); + }, api); const abortNative = patchMethod(XMLHttpRequestPrototype, 'abort', () => function(self: any, args: any[]) { @@ -251,14 +251,14 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => { // Otherwise, we are trying to abort an XHR which has not yet been sent, so there is no // task // to cancel. Do nothing. - }); + }, api); } }); -Zone.__load_patch('geolocation', (global: any) => { +Zone.__load_patch('geolocation', (global: any, _: ZoneType, api: _ZonePrivate) => { /// GEO_LOCATION if (global['navigator'] && global['navigator'].geolocation) { - patchPrototype(global['navigator'].geolocation, ['getCurrentPosition', 'watchPosition']); + patchPrototype(global['navigator'].geolocation, ['getCurrentPosition', 'watchPosition'], api); } }); diff --git a/lib/browser/define-property.ts b/lib/browser/define-property.ts index 872631148..bdfe5913f 100644 --- a/lib/browser/define-property.ts +++ b/lib/browser/define-property.ts @@ -18,8 +18,11 @@ const _getOwnPropertyDescriptor = (Object as any)[zoneSymbol('getOwnPropertyDesc const _create = Object.create; const unconfigurablesKey = zoneSymbol('unconfigurables'); -export function propertyPatch() { +export function propertyPatch(api: _ZonePrivate) { Object.defineProperty = function(obj: any, prop: string, desc: any) { + if (api.getMode() === 'native') { + return _defineProperty(obj, prop, desc); + } if (isUnconfigurable(obj, prop)) { throw new TypeError('Cannot assign to read only property \'' + prop + '\' of ' + obj); } @@ -38,6 +41,9 @@ export function propertyPatch() { }; Object.create = function(obj: any, proto: any) { + if (api.getMode() === 'native') { + return _create(obj, proto); + } if (typeof proto === 'object' && !Object.isFrozen(proto)) { Object.keys(proto).forEach(function(prop) { proto[prop] = rewriteDescriptor(obj, prop, proto[prop]); @@ -48,6 +54,9 @@ export function propertyPatch() { Object.getOwnPropertyDescriptor = function(obj, prop) { const desc = _getOwnPropertyDescriptor(obj, prop); + if (api.getMode() === 'native') { + return desc; + } if (desc && isUnconfigurable(obj, prop)) { desc.configurable = false; } diff --git a/lib/browser/event-target.ts b/lib/browser/event-target.ts index 4a67d7567..1694bfbef 100644 --- a/lib/browser/event-target.ts +++ b/lib/browser/event-target.ts @@ -103,7 +103,7 @@ export function eventTargetPatch(_global: any, api: _ZonePrivate) { } // vh is validateHandler to check event handler // is valid or not(for security check) - patchEventTarget(_global, apiTypes, {vh: checkIEAndCrossContext}); + patchEventTarget(_global, apiTypes, api, {vh: checkIEAndCrossContext}); api.patchEventTarget = patchEventTarget; return true; diff --git a/lib/browser/property-descriptor.ts b/lib/browser/property-descriptor.ts index 1ac33b3d4..28a372dcc 100644 --- a/lib/browser/property-descriptor.ts +++ b/lib/browser/property-descriptor.ts @@ -116,7 +116,7 @@ const documentEventNames = [ 'afterscriptexecute', 'beforescriptexecute', 'DOMContentLoaded', 'fullscreenchange', 'mozfullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange', 'fullscreenerror', 'mozfullscreenerror', 'webkitfullscreenerror', 'msfullscreenerror', 'readystatechange', - 'visibilitychange' + 'visibilitychange', 'freeze', 'resume' ]; const windowEventNames = [ 'absolutedeviceorientation', @@ -255,14 +255,14 @@ function filterProperties( } export function patchFilteredProperties( - target: any, onProperties: string[], ignoreProperties: IgnoreProperty[], prototype?: any) { + target: any, onProperties: string[], ignoreProperties: IgnoreProperty[], api: _ZonePrivate, prototype?: any) { // check whether target is available, sometimes target will be undefined // because different browser or some 3rd party plugin. if (!target) { return; } const filteredProperties: string[] = filterProperties(target, onProperties, ignoreProperties); - patchOnProperties(target, filteredProperties, prototype); + patchOnProperties(target, filteredProperties, api, prototype); } export function propertyDescriptorPatch(api: _ZonePrivate, _global: any) { @@ -282,56 +282,56 @@ export function propertyDescriptorPatch(api: _ZonePrivate, _global: any) { // so we need to pass WindowPrototype to check onProp exist or not patchFilteredProperties( internalWindow, eventNames.concat(['messageerror']), - ignoreProperties ? ignoreProperties.concat(ignoreErrorProperties) : ignoreProperties, + ignoreProperties ? ignoreProperties.concat(ignoreErrorProperties) : ignoreProperties, api, ObjectGetPrototypeOf(internalWindow)); - patchFilteredProperties(Document.prototype, eventNames, ignoreProperties); + patchFilteredProperties(Document.prototype, eventNames, ignoreProperties, api); if (typeof internalWindow['SVGElement'] !== 'undefined') { patchFilteredProperties( - internalWindow['SVGElement'].prototype, eventNames, ignoreProperties); + internalWindow['SVGElement'].prototype, eventNames, ignoreProperties, api); } - patchFilteredProperties(Element.prototype, eventNames, ignoreProperties); - patchFilteredProperties(HTMLElement.prototype, eventNames, ignoreProperties); - patchFilteredProperties(HTMLMediaElement.prototype, mediaElementEventNames, ignoreProperties); + patchFilteredProperties(Element.prototype, eventNames, ignoreProperties, api); + patchFilteredProperties(HTMLElement.prototype, eventNames, ignoreProperties, api); + patchFilteredProperties(HTMLMediaElement.prototype, mediaElementEventNames, ignoreProperties, api); patchFilteredProperties( HTMLFrameSetElement.prototype, windowEventNames.concat(frameSetEventNames), - ignoreProperties); + ignoreProperties, api); patchFilteredProperties( - HTMLBodyElement.prototype, windowEventNames.concat(frameSetEventNames), ignoreProperties); - patchFilteredProperties(HTMLFrameElement.prototype, frameEventNames, ignoreProperties); - patchFilteredProperties(HTMLIFrameElement.prototype, frameEventNames, ignoreProperties); + HTMLBodyElement.prototype, windowEventNames.concat(frameSetEventNames), ignoreProperties, api); + patchFilteredProperties(HTMLFrameElement.prototype, frameEventNames, ignoreProperties, api); + patchFilteredProperties(HTMLIFrameElement.prototype, frameEventNames, ignoreProperties, api); const HTMLMarqueeElement = internalWindow['HTMLMarqueeElement']; if (HTMLMarqueeElement) { - patchFilteredProperties(HTMLMarqueeElement.prototype, marqueeEventNames, ignoreProperties); + patchFilteredProperties(HTMLMarqueeElement.prototype, marqueeEventNames, ignoreProperties, api); } const Worker = internalWindow['Worker']; if (Worker) { - patchFilteredProperties(Worker.prototype, workerEventNames, ignoreProperties); + patchFilteredProperties(Worker.prototype, workerEventNames, ignoreProperties, api); } } - patchFilteredProperties(XMLHttpRequest.prototype, XMLHttpRequestEventNames, ignoreProperties); + patchFilteredProperties(XMLHttpRequest.prototype, XMLHttpRequestEventNames, ignoreProperties, api); const XMLHttpRequestEventTarget = _global['XMLHttpRequestEventTarget']; if (XMLHttpRequestEventTarget) { patchFilteredProperties( XMLHttpRequestEventTarget && XMLHttpRequestEventTarget.prototype, - XMLHttpRequestEventNames, ignoreProperties); + XMLHttpRequestEventNames, ignoreProperties, api); } if (typeof IDBIndex !== 'undefined') { - patchFilteredProperties(IDBIndex.prototype, IDBIndexEventNames, ignoreProperties); - patchFilteredProperties(IDBRequest.prototype, IDBIndexEventNames, ignoreProperties); - patchFilteredProperties(IDBOpenDBRequest.prototype, IDBIndexEventNames, ignoreProperties); - patchFilteredProperties(IDBDatabase.prototype, IDBIndexEventNames, ignoreProperties); - patchFilteredProperties(IDBTransaction.prototype, IDBIndexEventNames, ignoreProperties); - patchFilteredProperties(IDBCursor.prototype, IDBIndexEventNames, ignoreProperties); + patchFilteredProperties(IDBIndex.prototype, IDBIndexEventNames, ignoreProperties, api); + patchFilteredProperties(IDBRequest.prototype, IDBIndexEventNames, ignoreProperties, api); + patchFilteredProperties(IDBOpenDBRequest.prototype, IDBIndexEventNames, ignoreProperties, api); + patchFilteredProperties(IDBDatabase.prototype, IDBIndexEventNames, ignoreProperties, api); + patchFilteredProperties(IDBTransaction.prototype, IDBIndexEventNames, ignoreProperties, api); + patchFilteredProperties(IDBCursor.prototype, IDBIndexEventNames, ignoreProperties, api); } if (supportsWebSocket) { - patchFilteredProperties(WebSocket.prototype, websocketEventNames, ignoreProperties); + patchFilteredProperties(WebSocket.prototype, websocketEventNames, ignoreProperties, api); } } else { // Safari, Android browsers (Jelly Bean) patchViaCapturingAllTheEvents(); - patchClass('XMLHttpRequest'); + patchClass('XMLHttpRequest', api); if (supportsWebSocket) { webSocketPatch.apply(api, _global); } diff --git a/lib/browser/register-element.ts b/lib/browser/register-element.ts index aa1abbd7a..29521e98a 100644 --- a/lib/browser/register-element.ts +++ b/lib/browser/register-element.ts @@ -10,7 +10,7 @@ import {attachOriginToPatched, isBrowser, isMix, ObjectGetOwnPropertyDescriptor, import {_redefineProperty} from './define-property'; -export function registerElementPatch(_global: any) { +export function registerElementPatch(_global: any, api: _ZonePrivate) { if ((!isBrowser && !isMix) || !('registerElement' in (_global).document)) { return; } @@ -20,6 +20,9 @@ export function registerElementPatch(_global: any) { ['createdCallback', 'attachedCallback', 'detachedCallback', 'attributeChangedCallback']; (document).registerElement = function(name: any, opts: any) { + if (api.getMode() === 'native') { + return _registerElement.call(document, name, opts); + } if (opts && opts.prototype) { callbacks.forEach(function(callback) { const source = 'Document.registerElement::' + callback; diff --git a/lib/browser/shadydom.ts b/lib/browser/shadydom.ts index f308308cd..2c1e421b0 100644 --- a/lib/browser/shadydom.ts +++ b/lib/browser/shadydom.ts @@ -14,11 +14,11 @@ Zone.__load_patch('shadydom', (global: any, Zone: ZoneType, api: _ZonePrivate) = if (windowPrototype && windowPrototype.hasOwnProperty('addEventListener')) { (windowPrototype as any)[Zone.__symbol__('addEventListener')] = null; (windowPrototype as any)[Zone.__symbol__('removeEventListener')] = null; - api.patchEventTarget(global, [windowPrototype]); + api.patchEventTarget(global, [windowPrototype], api); } if (Node.prototype.hasOwnProperty('addEventListener')) { (Node.prototype as any)[Zone.__symbol__('addEventListener')] = null; (Node.prototype as any)[Zone.__symbol__('removeEventListener')] = null; - api.patchEventTarget(global, [Node.prototype]); + api.patchEventTarget(global, [Node.prototype], api); } }); diff --git a/lib/browser/webapis-media-query.ts b/lib/browser/webapis-media-query.ts index 7ca46e171..86142a473 100644 --- a/lib/browser/webapis-media-query.ts +++ b/lib/browser/webapis-media-query.ts @@ -16,7 +16,7 @@ Zone.__load_patch('mediaQuery', (global: any, Zone: ZoneType, api: _ZonePrivate) } else { return delegate.apply(self, args); } - }); + }, api); } function patchRemoveListener(proto: any) { @@ -32,7 +32,7 @@ Zone.__load_patch('mediaQuery', (global: any, Zone: ZoneType, api: _ZonePrivate) } else { return delegate.apply(self, args); } - }); + }, api); } if (global['MediaQueryList']) { @@ -60,6 +60,6 @@ Zone.__load_patch('mediaQuery', (global: any, Zone: ZoneType, api: _ZonePrivate) } } return mql; - }); + }, api); } }); diff --git a/lib/browser/webapis-notification.ts b/lib/browser/webapis-notification.ts index 2d663e73c..854fc0dec 100644 --- a/lib/browser/webapis-notification.ts +++ b/lib/browser/webapis-notification.ts @@ -14,5 +14,5 @@ Zone.__load_patch('notification', (global: any, Zone: ZoneType, api: _ZonePrivat if (!desc || !desc.configurable) { return; } - api.patchOnProperties(Notification.prototype, null); + api.patchOnProperties(Notification.prototype, null, api); }); diff --git a/lib/browser/webapis-resize-observer.ts b/lib/browser/webapis-resize-observer.ts index 8e7f73643..a7f9fac64 100644 --- a/lib/browser/webapis-resize-observer.ts +++ b/lib/browser/webapis-resize-observer.ts @@ -43,7 +43,7 @@ Zone.__load_patch('ResizeObserver', (global: any, Zone: any, api: _ZonePrivate) }; } return args.length > 0 ? new ResizeObserver(args[0]) : new ResizeObserver(); - }); + }, api); api.patchMethod( ResizeObserver.prototype, 'observe', (delegate: Function) => (self: any, args: any[]) => { @@ -58,7 +58,7 @@ Zone.__load_patch('ResizeObserver', (global: any, Zone: any, api: _ZonePrivate) targets.push(target); target[resizeObserverSymbol] = Zone.current; return delegate.apply(self, args); - }); + }, api); api.patchMethod( ResizeObserver.prototype, 'unobserve', (delegate: Function) => (self: any, args: any[]) => { @@ -77,7 +77,7 @@ Zone.__load_patch('ResizeObserver', (global: any, Zone: any, api: _ZonePrivate) } target[resizeObserverSymbol] = undefined; return delegate.apply(self, args); - }); + }, api); api.patchMethod( ResizeObserver.prototype, 'disconnect', (delegate: Function) => (self: any, args: any[]) => { @@ -89,5 +89,5 @@ Zone.__load_patch('ResizeObserver', (global: any, Zone: any, api: _ZonePrivate) self[resizeObserverSymbol] = undefined; } return delegate.apply(self, args); - }); + }, api); }); diff --git a/lib/browser/webapis-rtc-peer-connection.ts b/lib/browser/webapis-rtc-peer-connection.ts index 5930445ef..b43ba4bbb 100644 --- a/lib/browser/webapis-rtc-peer-connection.ts +++ b/lib/browser/webapis-rtc-peer-connection.ts @@ -22,5 +22,5 @@ Zone.__load_patch('RTCPeerConnection', (global: any, Zone: ZoneType, api: _ZoneP RTCPeerConnection.prototype[addSymbol] = null; RTCPeerConnection.prototype[removeSymbol] = null; - api.patchEventTarget(global, [RTCPeerConnection.prototype], {useG: false}); + api.patchEventTarget(global, [RTCPeerConnection.prototype], api, {useG: false}); }); diff --git a/lib/browser/websocket.ts b/lib/browser/websocket.ts index ab3cc0d41..0bd026ef6 100644 --- a/lib/browser/websocket.ts +++ b/lib/browser/websocket.ts @@ -15,7 +15,7 @@ export function apply(api: _ZonePrivate, _global: any) { // On Safari window.EventTarget doesn't exist so need to patch WS add/removeEventListener // On older Chrome, no need since EventTarget was already patched if (!(_global).EventTarget) { - patchEventTarget(_global, [WS.prototype]); + patchEventTarget(_global, [WS.prototype], api); } (_global).WebSocket = function(x: any, y: any) { const socket = arguments.length > 1 ? new WS(x, y) : new WS(x); @@ -50,7 +50,7 @@ export function apply(api: _ZonePrivate, _global: any) { proxySocket = socket; } - patchOnProperties(proxySocket, ['close', 'error', 'message', 'open'], proxySocketProto); + patchOnProperties(proxySocket, ['close', 'error', 'message', 'open'], api, proxySocketProto); return proxySocket; }; diff --git a/lib/common/events.ts b/lib/common/events.ts index 4d404b427..724f2113d 100644 --- a/lib/common/events.ts +++ b/lib/common/events.ts @@ -72,7 +72,7 @@ export interface PatchEventTargetOptions { } export function patchEventTarget( - _global: any, apis: any[], patchOptions?: PatchEventTargetOptions) { + _global: any, apis: any[], api: _ZonePrivate, patchOptions?: PatchEventTargetOptions) { const ADD_EVENT_LISTENER = (patchOptions && patchOptions.add) || ADD_EVENT_LISTENER_STR; const REMOVE_EVENT_LISTENER = (patchOptions && patchOptions.rm) || REMOVE_EVENT_LISTENER_STR; @@ -329,6 +329,9 @@ export function patchEventTarget( nativeListener: any, addSource: string, customScheduleFn: any, customCancelFn: any, returnTarget = false, prepend = false) { return function() { + if (api.getMode() === 'native') { + return nativeListener.apply(this, arguments); + } const target = this || _global; let delegate = arguments[1]; if (!delegate) { @@ -488,6 +491,9 @@ export function patchEventTarget( } proto[REMOVE_EVENT_LISTENER] = function() { + if (api.getMode() === 'native') { + return nativeRemoveAllListeners.apply(this, arguments); + } const target = this || _global; const eventName = arguments[0]; const options = arguments[2]; @@ -548,6 +554,9 @@ export function patchEventTarget( }; proto[LISTENERS_EVENT_LISTENER] = function() { + if (api.getMode() === 'native') { + return nativeListeners && nativeListeners.apply(this, arguments); + } const target = this || _global; const eventName = arguments[0]; @@ -563,6 +572,9 @@ export function patchEventTarget( }; proto[REMOVE_ALL_LISTENERS_EVENT_LISTENER] = function() { + if (api.getMode() === 'native') { + return nativeRemoveAllListeners && nativeRemoveAllListeners.apply(this, arguments); + } const target = this || _global; const eventName = arguments[0]; @@ -664,6 +676,6 @@ export function patchEventPrototype(global: any, api: _ZonePrivate) { // in case in some hybrid application, some part of // application will be controlled by zone, some are not delegate && delegate.apply(self, args); - }); + }, api); } } diff --git a/lib/common/fetch.ts b/lib/common/fetch.ts index 5fda7a5ab..55c90781c 100644 --- a/lib/common/fetch.ts +++ b/lib/common/fetch.ts @@ -21,6 +21,9 @@ Zone.__load_patch('fetch', (global: any, Zone: ZoneType, api: _ZonePrivate) => { if (supportAbort) { global['AbortController'] = function() { const abortController = new OriginalAbortController(); + if (api.getMode() === 'native') { + return abortController; + } const signal = abortController.signal; signal.abortController = abortController; return abortController; @@ -32,10 +35,13 @@ Zone.__load_patch('fetch', (global: any, Zone: ZoneType, api: _ZonePrivate) => { return self.task.zone.cancelTask(self.task); } return delegate.apply(self, args); - }); + }, api); } const placeholder = function() {}; global['fetch'] = function() { + if (api.getMode() === 'native') { + return fetch.apply(this, arguments); + } const args = Array.prototype.slice.call(arguments); const options = args.length > 1 ? args[1] : null; const signal = options && options.signal; @@ -97,4 +103,4 @@ Zone.__load_patch('fetch', (global: any, Zone: ZoneType, api: _ZonePrivate) => { } }); }; -}); \ No newline at end of file +}); diff --git a/lib/common/promise.ts b/lib/common/promise.ts index ce765a260..6894836c8 100644 --- a/lib/common/promise.ts +++ b/lib/common/promise.ts @@ -403,6 +403,9 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr desc = {configurable: true, enumerable: true}; } desc.get = function() { + if (api.getMode() === 'native') { + return NativePromise; + } // if we already set ZoneAwarePromise, use patched one // otherwise return native one. return global[ZONE_AWARE_PROMISE] ? global[ZONE_AWARE_PROMISE] : global[symbolPromise]; diff --git a/lib/common/timers.ts b/lib/common/timers.ts index ccf53aaba..7fbc65415 100644 --- a/lib/common/timers.ts +++ b/lib/common/timers.ts @@ -19,7 +19,7 @@ interface TimerOptions extends TaskData { args: any[]; } -export function patchTimer(window: any, setName: string, cancelName: string, nameSuffix: string) { +export function patchTimer(window: any, setName: string, cancelName: string, nameSuffix: string, api: _ZonePrivate) { let setNative: Function|null = null; let clearNative: Function|null = null; setName += nameSuffix; @@ -99,7 +99,7 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam // cause an error by calling it directly. return delegate.apply(window, args); } - }); + }, api); clearNative = patchMethod(window, cancelName, (delegate: Function) => function(self: any, args: any[]) { @@ -131,5 +131,5 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam // cause an error by calling it directly. delegate.apply(window, args); } - }); + }, api); } diff --git a/lib/common/to-string.ts b/lib/common/to-string.ts index ae884a7ac..e7bc42781 100644 --- a/lib/common/to-string.ts +++ b/lib/common/to-string.ts @@ -9,7 +9,7 @@ import {zoneSymbol} from './utils'; // override Function.prototype.toString to make zone.js patched function // look like native function -Zone.__load_patch('toString', (global: any) => { +Zone.__load_patch('toString', (global: any, _: ZoneType, api: _ZonePrivate) => { // patch Func.prototype.toString to let them look like native const originalFunctionToString = Function.prototype.toString; diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 77554e524..9ae07f0df 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -68,7 +68,7 @@ export function bindArguments(args: any[], source: string): any[] { return args; } -export function patchPrototype(prototype: any, fnNames: string[]) { +export function patchPrototype(prototype: any, fnNames: string[], api: _ZonePrivate) { const source = prototype.constructor['name']; for (let i = 0; i < fnNames.length; i++) { const name = fnNames[i]; @@ -80,6 +80,9 @@ export function patchPrototype(prototype: any, fnNames: string[]) { } prototype[name] = ((delegate: Function) => { const patched: any = function() { + if (api.getMode() === 'native') { + return delegate.apply(this, arguments); + } return delegate.apply(this, bindArguments(arguments, source + '.' + name)); }; attachOriginToPatched(patched, delegate); @@ -158,7 +161,7 @@ const wrapFn = function(event: Event) { return result; }; -export function patchProperty(obj: any, prop: string, prototype?: any) { +export function patchProperty(obj: any, prop: string, api: _ZonePrivate, prototype?: any) { let desc = ObjectGetOwnPropertyDescriptor(obj, prop); if (!desc && prototype) { // when patch window object, use prototype to check prop exist or not @@ -197,6 +200,9 @@ export function patchProperty(obj: any, prop: string, prototype?: any) { } desc.set = function(newValue) { + if (api.getMode() === 'native' && originalDescSet) { + return originalDescSet.call(this, newValue); + } // in some of windows's onproperty callback, this is undefined // so we need to check it let target = this; @@ -228,6 +234,9 @@ export function patchProperty(obj: any, prop: string, prototype?: any) { // The getter would return undefined for unassigned properties but the default value of an // unassigned property is null desc.get = function() { + if (api.getMode() === 'native' && originalDescGet) { + return originalDescGet.call(this); + } // in some of windows's onproperty callback, this is undefined // so we need to check it let target = this; @@ -264,10 +273,10 @@ export function patchProperty(obj: any, prop: string, prototype?: any) { obj[onPropPatchedSymbol] = true; } -export function patchOnProperties(obj: any, properties: string[]|null, prototype?: any) { +export function patchOnProperties(obj: any, properties: string[]|null, api: _ZonePrivate, prototype?: any) { if (properties) { for (let i = 0; i < properties.length; i++) { - patchProperty(obj, 'on' + properties[i], prototype); + patchProperty(obj, 'on' + properties[i], api, prototype); } } else { const onProperties = []; @@ -277,7 +286,7 @@ export function patchOnProperties(obj: any, properties: string[]|null, prototype } } for (let j = 0; j < onProperties.length; j++) { - patchProperty(obj, onProperties[j], prototype); + patchProperty(obj, onProperties[j], api, prototype); } } } @@ -285,14 +294,14 @@ export function patchOnProperties(obj: any, properties: string[]|null, prototype const originalInstanceKey = zoneSymbol('originalInstance'); // wrap some native API on `window` -export function patchClass(className: string) { +export function patchClass(className: string, api: _ZonePrivate) { const OriginalClass = _global[className]; if (!OriginalClass) return; // keep original class in global _global[zoneSymbol(className)] = OriginalClass; _global[className] = function() { - const a = bindArguments(arguments, className); + const a = api.getMode() === 'native' ? arguments : bindArguments(arguments, className); switch (a.length) { case 0: this[originalInstanceKey] = new OriginalClass(); @@ -331,7 +340,7 @@ export function patchClass(className: string) { } else { ObjectDefineProperty(_global[className].prototype, prop, { set: function(fn) { - if (typeof fn === 'function') { + if (typeof fn === 'function' && api.getMode() === 'default') { this[originalInstanceKey][prop] = wrapWithCurrentZone(fn, className + '.' + prop); // keep callback in wrapped function so we can // use it in Function.prototype.toString to return @@ -388,8 +397,8 @@ export function setShouldCopySymbolProperties(flag: boolean) { export function patchMethod( target: any, name: string, - patchFn: (delegate: Function, delegateName: string, name: string) => (self: any, args: any[]) => - any): Function|null { + patchFnFactory: (delegate: Function, delegateName: string, name: string) => (self: any, args: any[]) => + any, api: _ZonePrivate): Function|null { let proto = target; while (proto && !proto.hasOwnProperty(name)) { proto = ObjectGetPrototypeOf(proto); @@ -407,8 +416,12 @@ export function patchMethod( // some property is readonly in safari, such as HtmlCanvasElement.prototype.toBlob const desc = proto && ObjectGetOwnPropertyDescriptor(proto, name); if (isPropertyWritable(desc)) { - const patchDelegate = patchFn(delegate!, delegateName, name); + const patchDelegate = patchFnFactory(delegate!, delegateName, name); proto[name] = function() { + // if current mode is `native`, just use `native` delegate. + if (api.getMode() === 'native') { + return delegate && delegate.apply(this, arguments); + } return patchDelegate(this, arguments as any); }; attachOriginToPatched(proto[name], delegate); @@ -429,7 +442,7 @@ export interface MacroTaskMeta extends TaskData { // TODO: @JiaLiPassion, support cancel task later if necessary export function patchMacroTask( - obj: any, funcName: string, metaCreator: (self: any, args: any[]) => MacroTaskMeta) { + obj: any, funcName: string, metaCreator: (self: any, args: any[]) => MacroTaskMeta, api: _ZonePrivate) { let setNative: Function|null = null; function scheduleTask(task: Task) { @@ -449,7 +462,7 @@ export function patchMacroTask( // cause an error by calling it directly. return delegate.apply(self, args); } - }); + }, api); } export interface MicroTaskMeta extends TaskData { @@ -460,7 +473,7 @@ export interface MicroTaskMeta extends TaskData { } export function patchMicroTask( - obj: any, funcName: string, metaCreator: (self: any, args: any[]) => MicroTaskMeta) { + obj: any, funcName: string, metaCreator: (self: any, args: any[]) => MicroTaskMeta, api: _ZonePrivate) { let setNative: Function|null = null; function scheduleTask(task: Task) { @@ -480,7 +493,7 @@ export function patchMicroTask( // cause an error by calling it directly. return delegate.apply(self, args); } - }); + }, api); } export function attachOriginToPatched(patched: Function, original: any) { diff --git a/lib/extra/bluebird.ts b/lib/extra/bluebird.ts index 59b4feec4..3dc869e4a 100644 --- a/lib/extra/bluebird.ts +++ b/lib/extra/bluebird.ts @@ -38,7 +38,7 @@ Zone.__load_patch('bluebird', (global: any, Zone: ZoneType, api: _ZonePrivate) = } } return delegate.apply(self, args); - }); + }, api); }); Bluebird.onPossiblyUnhandledRejection(function(e: any, promise: any) { diff --git a/lib/extra/cordova.ts b/lib/extra/cordova.ts index c37938889..faccbc056 100644 --- a/lib/extra/cordova.ts +++ b/lib/extra/cordova.ts @@ -19,7 +19,7 @@ Zone.__load_patch('cordova', (global: any, Zone: ZoneType, api: _ZonePrivate) => args[1] = Zone.current.wrap(args[1], ERROR_SOURCE); } return nativeExec!.apply(self, args); - }); + }, api); } }); diff --git a/lib/extra/electron.ts b/lib/extra/electron.ts index 73693eaab..926a035ec 100644 --- a/lib/extra/electron.ts +++ b/lib/extra/electron.ts @@ -9,7 +9,7 @@ Zone.__load_patch('electron', (global: any, Zone: ZoneType, api: _ZonePrivate) = function patchArguments(target: any, name: string, source: string): Function|null { return api.patchMethod(target, name, (delegate: Function) => (self: any, args: any[]) => { return delegate && delegate.apply(self, api.bindArguments(args, source)); - }); + }, api); } const {desktopCapturer, shell, CallbacksRegistry} = require('electron'); // patch api in renderer process directly diff --git a/lib/extra/jsonp.ts b/lib/extra/jsonp.ts index 95532ce17..6d1512297 100644 --- a/lib/extra/jsonp.ts +++ b/lib/extra/jsonp.ts @@ -36,7 +36,7 @@ Zone.__load_patch('jsonp', (global: any, Zone: ZoneType, api: _ZonePrivate) => { } else { return delegate.apply(self, args); } - }); + }, api); } else { Object.defineProperty(global, methodName, { configurable: true, @@ -74,6 +74,6 @@ Zone.__load_patch('jsonp', (global: any, Zone: ZoneType, api: _ZonePrivate) => { Zone.current.scheduleMacroTask('jsonp', noop, {}, (task: Task) => { return delegate.apply(self, args); }, noop); - }); + }, api); }; }); diff --git a/lib/extra/socket-io.ts b/lib/extra/socket-io.ts index ffb91670a..da780eea2 100644 --- a/lib/extra/socket-io.ts +++ b/lib/extra/socket-io.ts @@ -8,7 +8,7 @@ Zone.__load_patch('socketio', (global: any, Zone: ZoneType, api: _ZonePrivate) => { (Zone as any)[Zone.__symbol__('socketio')] = function patchSocketIO(io: any) { // patch io.Socket.prototype event listener related method - api.patchEventTarget(global, [io.Socket.prototype], { + api.patchEventTarget(global, [io.Socket.prototype], api, { useG: false, chkDup: false, rt: true, diff --git a/lib/node/events.ts b/lib/node/events.ts index d7eda2596..c49a57375 100644 --- a/lib/node/events.ts +++ b/lib/node/events.ts @@ -8,7 +8,7 @@ import {patchEventTarget} from '../common/events'; -Zone.__load_patch('EventEmitter', (global: any) => { +Zone.__load_patch('EventEmitter', (global: any, _: ZoneType, api: _ZonePrivate) => { // For EventEmitter const EE_ADD_LISTENER = 'addListener'; const EE_PREPEND_LISTENER = 'prependListener'; @@ -23,7 +23,7 @@ Zone.__load_patch('EventEmitter', (global: any) => { }; function patchEventEmitterMethods(obj: any) { - const result = patchEventTarget(global, [obj], { + const result = patchEventTarget(global, [obj], api, { useG: false, add: EE_ADD_LISTENER, rm: EE_REMOVE_LISTENER, diff --git a/lib/node/fs.ts b/lib/node/fs.ts index 9af536abc..d2b50c657 100644 --- a/lib/node/fs.ts +++ b/lib/node/fs.ts @@ -8,7 +8,7 @@ import {patchMacroTask} from '../common/utils'; -Zone.__load_patch('fs', () => { +Zone.__load_patch('fs', (globa: any, _: ZoneType, api: _ZonePrivate) => { let fs: any; try { fs = require('fs'); @@ -35,7 +35,7 @@ Zone.__load_patch('fs', () => { cbIdx: args.length > 0 ? args.length - 1 : -1, target: self }; - }); + }, api); }); } }); diff --git a/lib/node/node.ts b/lib/node/node.ts index 94ef7fb05..731c78900 100644 --- a/lib/node/node.ts +++ b/lib/node/node.ts @@ -17,7 +17,7 @@ import {ArraySlice, isMix, patchMacroTask, patchMicroTask} from '../common/utils const set = 'set'; const clear = 'clear'; -Zone.__load_patch('node_timers', (global: any, Zone: ZoneType) => { +Zone.__load_patch('node_timers', (global: any, Zone: ZoneType, api: _ZonePrivate) => { // Timers let globalUseTimeoutFromTimer = false; try { @@ -38,9 +38,9 @@ Zone.__load_patch('node_timers', (global: any, Zone: ZoneType) => { clearTimeout(detectTimeout); timers.setTimeout = originSetTimeout; } - patchTimer(timers, set, clear, 'Timeout'); - patchTimer(timers, set, clear, 'Interval'); - patchTimer(timers, set, clear, 'Immediate'); + patchTimer(timers, set, clear, 'Timeout', api); + patchTimer(timers, set, clear, 'Interval', api); + patchTimer(timers, set, clear, 'Immediate', api); } catch (error) { // timers module not exists, for example, when we using nativeScript // timers is not available @@ -55,9 +55,9 @@ Zone.__load_patch('node_timers', (global: any, Zone: ZoneType) => { // 1. global setTimeout equals timers setTimeout // 2. or global don't use timers setTimeout(maybe some other library patch setTimeout) // 3. or load timers module error happens, we should patch global setTimeout - patchTimer(global, set, clear, 'Timeout'); - patchTimer(global, set, clear, 'Interval'); - patchTimer(global, set, clear, 'Immediate'); + patchTimer(global, set, clear, 'Timeout', api); + patchTimer(global, set, clear, 'Interval', api); + patchTimer(global, set, clear, 'Immediate', api); } else { // global use timers setTimeout, but not equals // this happens when use nodejs v0.10.x, global setTimeout will @@ -71,7 +71,7 @@ Zone.__load_patch('node_timers', (global: any, Zone: ZoneType) => { }); // patch process related methods -Zone.__load_patch('nextTick', () => { +Zone.__load_patch('nextTick', (global: any, _: ZoneType, api: _ZonePrivate) => { // patch nextTick as microTask patchMicroTask(process, 'nextTick', (self: any, args: any[]) => { return { @@ -80,7 +80,7 @@ Zone.__load_patch('nextTick', () => { cbIdx: (args.length > 0 && typeof args[0] === 'function') ? 0 : -1, target: process }; - }); + }, api); }); Zone.__load_patch( @@ -110,7 +110,7 @@ Zone.__load_patch( // Crypto -Zone.__load_patch('crypto', () => { +Zone.__load_patch('crypto', (global: any, _: ZoneType, api: _ZonePrivate) => { let crypto: any; try { crypto = require('crypto'); @@ -126,22 +126,25 @@ Zone.__load_patch('crypto', () => { name: 'crypto.' + name, args: args, cbIdx: (args.length > 0 && typeof args[args.length - 1] === 'function') ? - args.length - 1 : - -1, + args.length - 1 : + -1, target: crypto }; - }); + }, api); }); } }); -Zone.__load_patch('console', (global: any, Zone: ZoneType) => { +Zone.__load_patch('console', (global: any, Zone: ZoneType, api: _ZonePrivate) => { const consoleMethods = ['dir', 'log', 'info', 'error', 'warn', 'assert', 'debug', 'timeEnd', 'trace']; consoleMethods.forEach((m: string) => { const originalMethod = (console as any)[Zone.__symbol__(m)] = (console as any)[m]; if (originalMethod) { (console as any)[m] = function() { + if (api.getMode() === 'native') { + return originalMethod.apply(this, arguments); + } const args = ArraySlice.call(arguments); if (Zone.current === Zone.root) { return originalMethod.apply(this, args); diff --git a/lib/rxjs/rxjs-fake-async.ts b/lib/rxjs/rxjs-fake-async.ts index 85ef31dc7..dd288a859 100644 --- a/lib/rxjs/rxjs-fake-async.ts +++ b/lib/rxjs/rxjs-fake-async.ts @@ -13,11 +13,11 @@ import {async} from 'rxjs/scheduler/async'; Zone.__load_patch('rxjs.Scheduler.now', (global: any, Zone: ZoneType, api: _ZonePrivate) => { api.patchMethod(Scheduler, 'now', (delegate: Function) => (self: any, args: any[]) => { return Date.now.apply(self, args); - }); + }, api); api.patchMethod(async, 'now', (delegate: Function) => (self: any, args: any[]) => { return Date.now.apply(self, args); - }); + }, api); api.patchMethod(asap, 'now', (delegate: Function) => (self: any, args: any[]) => { return Date.now.apply(self, args); - }); + }, api); }); diff --git a/lib/zone.ts b/lib/zone.ts index 4de4e077d..117d62e24 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -320,15 +320,16 @@ interface _ZonePrivate { onUnhandledError: (error: Error) => void; microtaskDrainDone: () => void; showUncaughtError: () => boolean; - patchEventTarget: (global: any, apis: any[], options?: any) => boolean[]; - patchOnProperties: (obj: any, properties: string[]|null) => void; + patchEventTarget: (global: any, apis: any[], api: _ZonePrivate, options?: any) => boolean[]; + patchOnProperties: (obj: any, properties: string[]|null, api: _ZonePrivate) => void; patchThen: (ctro: Function) => void; setNativePromise: (nativePromise: any) => void; patchMethod: (target: any, name: string, patchFn: (delegate: Function, delegateName: string, name: string) => - (self: any, args: any[]) => any) => Function | null; + (self: any, args: any[]) => any, api: _ZonePrivate) => Function | null; bindArguments: (args: any[], source: string) => any[]; + getMode: () => _ZoneMode; } /** @internal */ @@ -632,6 +633,8 @@ interface EventTask extends Task { type AmbientZone = Zone; /** @internal */ type AmbientZoneDelegate = ZoneDelegate; +/** @internal */ +type _ZoneMode = 'default' | 'native'; const Zone: ZoneType = (function(global: any) { const performance: {mark(name: string): void; measure(name: string, label: string): void;} = @@ -702,6 +705,10 @@ const Zone: ZoneType = (function(global: any) { } } + static __set_mode(zoneMode: _ZoneMode) { + _mode = zoneMode; + } + public get parent(): AmbientZone|null { return this._parent; } @@ -760,9 +767,12 @@ const Zone: ZoneType = (function(global: any) { public run( callback: (...args: any[]) => T, applyThis?: any, applyArgs?: any[], source?: string): T { _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; + const _previous_mode = _mode; try { + _mode = 'default'; return this._zoneDelegate.invoke(this, callback, applyThis, applyArgs, source); } finally { + _mode = _previous_mode; _currentZoneFrame = _currentZoneFrame.parent!; } } @@ -772,10 +782,13 @@ const Zone: ZoneType = (function(global: any) { callback: (...args: any[]) => T, applyThis: any = null, applyArgs?: any[], source?: string) { _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; + const _previous_mode = _mode; try { try { + _mode = 'default'; return this._zoneDelegate.invoke(this, callback, applyThis, applyArgs, source); } catch (error) { + _mode = _previous_mode; if (this._zoneDelegate.handleError(this, error)) { throw error; } @@ -806,7 +819,9 @@ const Zone: ZoneType = (function(global: any) { const previousTask = _currentTask; _currentTask = task; _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; + const _previous_mode = _mode; try { + _mode = 'default'; if (task.type == macroTask && task.data && !task.data.isPeriodic) { task.cancelFn = undefined; } @@ -818,6 +833,7 @@ const Zone: ZoneType = (function(global: any) { } } } finally { + _mode = _previous_mode; // if the task's state is notScheduled or unknown, then it has already been cancelled // we should not reset the state to scheduled if (task.state !== notScheduled && task.state !== unknown) { @@ -1351,10 +1367,12 @@ const Zone: ZoneType = (function(global: any) { nativeMicroTaskQueuePromise = NativePromise.resolve(0); } }, + getMode: () => _mode }; let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)}; let _currentTask: Task|null = null; let _numberOfNestedTaskFrames = 0; + let _mode: _ZoneMode = 'default'; function noop() {} diff --git a/test/browser/browser.spec.ts b/test/browser/browser.spec.ts index 93c840c4c..3a0ad1588 100644 --- a/test/browser/browser.spec.ts +++ b/test/browser/browser.spec.ts @@ -9,7 +9,7 @@ import {patchFilteredProperties} from '../../lib/browser/property-descriptor'; import {patchEventTarget} from '../../lib/common/events'; import {isBrowser, isIEOrEdge, isMix, zoneSymbol} from '../../lib/common/utils'; -import {getEdgeVersion, getIEVersion, ifEnvSupports, ifEnvSupportsWithDone, isEdge} from '../test-util'; +import {getEdgeVersion, getIEVersion, ifEnvSupports, ifEnvSupportsWithDone, isEdge, testApi} from '../test-util'; import Spy = jasmine.Spy; declare const global: any; @@ -201,7 +201,7 @@ describe('Zone', function() { it('should not patch ignored on properties', function() { const TestTarget: any = (window as any)['TestTarget']; patchFilteredProperties( - TestTarget.prototype, ['prop1', 'prop2'], global['__Zone_ignore_on_properties']); + TestTarget.prototype, ['prop1', 'prop2'], global['__Zone_ignore_on_properties'], testApi); const testTarget = new TestTarget(); Zone.current.fork({name: 'test'}).run(() => { testTarget.onprop1 = function() { @@ -240,7 +240,7 @@ describe('Zone', function() { it('should be able to clear on handler added before load zone.js', function() { const TestTarget: any = (window as any)['TestTarget']; patchFilteredProperties( - TestTarget.prototype, ['prop3'], global['__Zone_ignore_on_properties']); + TestTarget.prototype, ['prop3'], global['__Zone_ignore_on_properties'], testApi); const testTarget = new TestTarget(); Zone.current.fork({name: 'test'}).run(() => { expect(testTarget.onprop3).toBeTruthy(); @@ -336,7 +336,7 @@ describe('Zone', function() { (HTMLSpanElement.prototype as any)['__zone_symbol__addEventListener'] = null; - patchEventTarget(window, [HTMLSpanElement.prototype]); + patchEventTarget(window, [HTMLSpanElement.prototype], testApi); const span = document.createElement('span'); document.body.appendChild(span); @@ -1023,7 +1023,7 @@ describe('Zone', function() { })); it('should change options to boolean if not support passive', () => { - patchEventTarget(window, [TestEventListener.prototype]); + patchEventTarget(window, [TestEventListener.prototype], testApi); const testEventListener = new TestEventListener(); const listener = function() {}; diff --git a/test/browser/requestAnimationFrame.spec.ts b/test/browser/requestAnimationFrame.spec.ts index 42a771ed7..6953d4d5c 100644 --- a/test/browser/requestAnimationFrame.spec.ts +++ b/test/browser/requestAnimationFrame.spec.ts @@ -43,7 +43,7 @@ describe('requestAnimationFrame', function() { expect(previousTimeStamp).not.toBeGreaterThan(timestamp); previousTimeStamp = timestamp; - if (frames++ > 15) { + if (frames++ > 2) { (jasmine).DEFAULT_TIMEOUT_INTERVAL = originalTimeout; return done(); } diff --git a/test/common/util.spec.ts b/test/common/util.spec.ts index 449d9b247..2009095e5 100644 --- a/test/common/util.spec.ts +++ b/test/common/util.spec.ts @@ -7,6 +7,7 @@ */ import {patchMethod, patchProperty, patchPrototype, zoneSymbol} from '../../lib/common/utils'; +import { testApi } from '../test-util'; describe('utils', function() { describe('patchMethod', () => { @@ -32,7 +33,7 @@ describe('utils', function() { return function(self, args) { return delegate.apply(self, ['patch', args[0]]); }; - })).toBe(delegateMethod!); + }, testApi)).toBe(delegateMethod!); expect(instance.method('a0')).toEqual('OK'); expect(args).toEqual(['patch', 'a0']); @@ -49,14 +50,14 @@ describe('utils', function() { return function(self, args: any[]) { return delegate.apply(self, ['patch', ...args]); }; - }); + }, testApi); const pMethod = Type.prototype.method; expect(pMethod).not.toBe(method); patchMethod(Type.prototype, 'method', (delegate) => { return function(self, args) { return delegate.apply(self, ['patch', ...args]); }; - }); + }, testApi); expect(pMethod).toBe(Type.prototype.method); }); @@ -72,7 +73,7 @@ describe('utils', function() { TestType.prototype, 'nonConfigurableProperty', {configurable: false, writable: true, value: 'test'}); } - patchProperty(TestType.prototype, 'nonConfigurableProperty'); + patchProperty(TestType.prototype, 'nonConfigurableProperty', testApi); const desc = Object.getOwnPropertyDescriptor(TestType.prototype, 'nonConfigurableProperty'); expect(desc!.writable).toBeTruthy(); expect(!desc!.get).toBeTruthy(); @@ -117,7 +118,7 @@ describe('utils', function() { expect(log).toEqual(['property1', 'property2']); log.length = 0; - patchPrototype(TestFunction.prototype, ['property1', 'property2']); + patchPrototype(TestFunction.prototype, ['property1', 'property2'], testApi); zone.run(() => { const instance = new TestFunction(); @@ -168,7 +169,7 @@ describe('utils', function() { expect(log).toEqual(['property1', 'property2']); log.length = 0; - patchPrototype(TestFunction.prototype, ['property1', 'property2']); + patchPrototype(TestFunction.prototype, ['property1', 'property2'], testApi); zone.run(() => { const instance = new TestFunction(); @@ -227,7 +228,7 @@ describe('utils', function() { expect(log).toEqual(['property1', 'property2']); log.length = 0; - patchPrototype(TestFunction.prototype, ['property1', 'property2']); + patchPrototype(TestFunction.prototype, ['property1', 'property2'], testApi); zone.run(() => { const instance = new TestFunction(); @@ -273,7 +274,7 @@ describe('utils', function() { return function(self: any, args: any) { log.push('patched property2'); }; - }); + }, testApi); zone.run(() => { const instance = new TestFunction(); @@ -317,7 +318,7 @@ describe('utils', function() { return function(self: any, args: any) { log.push('patched property2'); }; - }); + }, testApi); zone.run(() => { const instance = new TestFunction(); diff --git a/test/test-util.ts b/test/test-util.ts index 46a28c699..2dea6f0f7 100644 --- a/test/test-util.ts +++ b/test/test-util.ts @@ -132,3 +132,21 @@ export function getEdgeVersion() { } return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); } + +const noop = function () { }; + +export const testApi: _ZonePrivate = { + symbol: Zone.__symbol__, + currentZoneFrame: () => ({ parent: null, zone: Zone.current }), + onUnhandledError: noop, + microtaskDrainDone: noop, + scheduleMicroTask: noop, + showUncaughtError: () => !(Zone as any)[Zone.__symbol__('ignoreConsoleErrorUncaughtError')], + patchEventTarget: () => [], + patchOnProperties: noop, + patchMethod: () => noop, + bindArguments: () => [], + patchThen: () => noop, + setNativePromise: (NativePromise: any) => noop, + getMode: () => 'default' +}; diff --git a/test/zone-spec/fake-async-test.spec.ts b/test/zone-spec/fake-async-test.spec.ts index 21a2be440..44748e54d 100644 --- a/test/zone-spec/fake-async-test.spec.ts +++ b/test/zone-spec/fake-async-test.spec.ts @@ -12,7 +12,7 @@ import '../../lib/rxjs/rxjs-fake-async'; import {Observable} from 'rxjs/Observable'; import {isNode, patchMacroTask} from '../../lib/common/utils'; -import {ifEnvSupports} from '../test-util'; +import {ifEnvSupports, testApi} from '../test-util'; function supportNode() { return isNode; @@ -812,7 +812,7 @@ describe('FakeAsyncTestZoneSpec', () => { patchMacroTask( TestClass.prototype, 'myTimeout', (self: any, args: any[]) => - ({name: 'TestClass.myTimeout', target: self, cbIdx: 0, args: args})); + ({name: 'TestClass.myTimeout', target: self, cbIdx: 0, args: args}), testApi); const testClass = new TestClass(); testClass.myTimeout(function(callbackArgs: any) { @@ -838,7 +838,7 @@ describe('FakeAsyncTestZoneSpec', () => { patchMacroTask( TestClass.prototype, 'myTimeout', (self: any, args: any[]) => - ({name: 'TestClass.myTimeout', target: self, cbIdx: 0, args: args})); + ({name: 'TestClass.myTimeout', target: self, cbIdx: 0, args: args}), testApi); const testClass = new TestClass(); testClass.myTimeout(() => { @@ -867,7 +867,7 @@ describe('FakeAsyncTestZoneSpec', () => { patchMacroTask( TestClass.prototype, 'myInterval', (self: any, args: any[]) => - ({name: 'TestClass.myInterval', target: self, cbIdx: 0, args: args})); + ({name: 'TestClass.myInterval', target: self, cbIdx: 0, args: args}), testApi); const testClass = new TestClass(); const id = testClass.myInterval(() => { From 07d7a8c042443720edc82f8a22ffc18ccddf2659 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Mon, 20 Aug 2018 09:29:21 +0900 Subject: [PATCH 2/2] feat(core): add scoped zone.js support --- karma-base.conf.js | 2 +- karma-build-outside-jasmine.conf.js | 5 ++ lib/browser/define-property.ts | 6 +- lib/browser/property-descriptor.ts | 18 ++++-- lib/browser/register-element.ts | 2 +- lib/common/events.ts | 12 ++-- lib/common/fetch.ts | 8 +-- lib/common/promise.ts | 2 +- lib/common/timers.ts | 3 +- lib/common/utils.ts | 27 +++++---- lib/node/node.ts | 6 +- lib/zone.ts | 48 ++++++++------- package.json | 2 + test/browser-outside-zone-setup.ts | 8 +++ test/browser/browser.outside.spec.ts | 74 ++++++++++++++++++++++++ test/browser/browser.spec.ts | 3 +- test/browser_outside_zone_entry_point.ts | 10 ++++ test/common/util.spec.ts | 10 ++-- test/main.ts | 10 +++- test/test-util.ts | 6 +- test/zone-spec/fake-async-test.spec.ts | 9 ++- 21 files changed, 201 insertions(+), 70 deletions(-) create mode 100644 karma-build-outside-jasmine.conf.js create mode 100644 test/browser-outside-zone-setup.ts create mode 100644 test/browser/browser.outside.spec.ts create mode 100644 test/browser_outside_zone_entry_point.ts diff --git a/karma-base.conf.js b/karma-base.conf.js index 32f447cff..e3934bcac 100644 --- a/karma-base.conf.js +++ b/karma-base.conf.js @@ -9,7 +9,7 @@ module.exports = function(config) { config.set({ basePath: '', - client: {errorpolicy: config.errorpolicy}, + client: {errorpolicy: config.errorpolicy, entryPoint: config.entryPoint}, files: [ 'node_modules/systemjs/dist/system-polyfills.js', 'node_modules/systemjs/dist/system.src.js', 'node_modules/whatwg-fetch/fetch.js', diff --git a/karma-build-outside-jasmine.conf.js b/karma-build-outside-jasmine.conf.js new file mode 100644 index 000000000..08db3aa94 --- /dev/null +++ b/karma-build-outside-jasmine.conf.js @@ -0,0 +1,5 @@ + +module.exports = function (config) { + require('./karma-build-jasmine.conf.js')(config); + config.files.push('build/test/browser-outside-zone-setup.js'); +}; diff --git a/lib/browser/define-property.ts b/lib/browser/define-property.ts index bdfe5913f..0945512c4 100644 --- a/lib/browser/define-property.ts +++ b/lib/browser/define-property.ts @@ -20,7 +20,7 @@ const unconfigurablesKey = zoneSymbol('unconfigurables'); export function propertyPatch(api: _ZonePrivate) { Object.defineProperty = function(obj: any, prop: string, desc: any) { - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return _defineProperty(obj, prop, desc); } if (isUnconfigurable(obj, prop)) { @@ -41,7 +41,7 @@ export function propertyPatch(api: _ZonePrivate) { }; Object.create = function(obj: any, proto: any) { - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return _create(obj, proto); } if (typeof proto === 'object' && !Object.isFrozen(proto)) { @@ -54,7 +54,7 @@ export function propertyPatch(api: _ZonePrivate) { Object.getOwnPropertyDescriptor = function(obj, prop) { const desc = _getOwnPropertyDescriptor(obj, prop); - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return desc; } if (desc && isUnconfigurable(obj, prop)) { diff --git a/lib/browser/property-descriptor.ts b/lib/browser/property-descriptor.ts index 28a372dcc..7626d24cd 100644 --- a/lib/browser/property-descriptor.ts +++ b/lib/browser/property-descriptor.ts @@ -255,7 +255,8 @@ function filterProperties( } export function patchFilteredProperties( - target: any, onProperties: string[], ignoreProperties: IgnoreProperty[], api: _ZonePrivate, prototype?: any) { + target: any, onProperties: string[], ignoreProperties: IgnoreProperty[], api: _ZonePrivate, + prototype?: any) { // check whether target is available, sometimes target will be undefined // because different browser or some 3rd party plugin. if (!target) { @@ -292,25 +293,29 @@ export function propertyDescriptorPatch(api: _ZonePrivate, _global: any) { } patchFilteredProperties(Element.prototype, eventNames, ignoreProperties, api); patchFilteredProperties(HTMLElement.prototype, eventNames, ignoreProperties, api); - patchFilteredProperties(HTMLMediaElement.prototype, mediaElementEventNames, ignoreProperties, api); + patchFilteredProperties( + HTMLMediaElement.prototype, mediaElementEventNames, ignoreProperties, api); patchFilteredProperties( HTMLFrameSetElement.prototype, windowEventNames.concat(frameSetEventNames), ignoreProperties, api); patchFilteredProperties( - HTMLBodyElement.prototype, windowEventNames.concat(frameSetEventNames), ignoreProperties, api); + HTMLBodyElement.prototype, windowEventNames.concat(frameSetEventNames), ignoreProperties, + api); patchFilteredProperties(HTMLFrameElement.prototype, frameEventNames, ignoreProperties, api); patchFilteredProperties(HTMLIFrameElement.prototype, frameEventNames, ignoreProperties, api); const HTMLMarqueeElement = internalWindow['HTMLMarqueeElement']; if (HTMLMarqueeElement) { - patchFilteredProperties(HTMLMarqueeElement.prototype, marqueeEventNames, ignoreProperties, api); + patchFilteredProperties( + HTMLMarqueeElement.prototype, marqueeEventNames, ignoreProperties, api); } const Worker = internalWindow['Worker']; if (Worker) { patchFilteredProperties(Worker.prototype, workerEventNames, ignoreProperties, api); } } - patchFilteredProperties(XMLHttpRequest.prototype, XMLHttpRequestEventNames, ignoreProperties, api); + patchFilteredProperties( + XMLHttpRequest.prototype, XMLHttpRequestEventNames, ignoreProperties, api); const XMLHttpRequestEventTarget = _global['XMLHttpRequestEventTarget']; if (XMLHttpRequestEventTarget) { patchFilteredProperties( @@ -320,7 +325,8 @@ export function propertyDescriptorPatch(api: _ZonePrivate, _global: any) { if (typeof IDBIndex !== 'undefined') { patchFilteredProperties(IDBIndex.prototype, IDBIndexEventNames, ignoreProperties, api); patchFilteredProperties(IDBRequest.prototype, IDBIndexEventNames, ignoreProperties, api); - patchFilteredProperties(IDBOpenDBRequest.prototype, IDBIndexEventNames, ignoreProperties, api); + patchFilteredProperties( + IDBOpenDBRequest.prototype, IDBIndexEventNames, ignoreProperties, api); patchFilteredProperties(IDBDatabase.prototype, IDBIndexEventNames, ignoreProperties, api); patchFilteredProperties(IDBTransaction.prototype, IDBIndexEventNames, ignoreProperties, api); patchFilteredProperties(IDBCursor.prototype, IDBIndexEventNames, ignoreProperties, api); diff --git a/lib/browser/register-element.ts b/lib/browser/register-element.ts index 29521e98a..f5c9201c6 100644 --- a/lib/browser/register-element.ts +++ b/lib/browser/register-element.ts @@ -20,7 +20,7 @@ export function registerElementPatch(_global: any, api: _ZonePrivate) { ['createdCallback', 'attachedCallback', 'detachedCallback', 'attributeChangedCallback']; (document).registerElement = function(name: any, opts: any) { - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return _registerElement.call(document, name, opts); } if (opts && opts.prototype) { diff --git a/lib/common/events.ts b/lib/common/events.ts index 724f2113d..e4b0544d4 100644 --- a/lib/common/events.ts +++ b/lib/common/events.ts @@ -329,7 +329,7 @@ export function patchEventTarget( nativeListener: any, addSource: string, customScheduleFn: any, customCancelFn: any, returnTarget = false, prepend = false) { return function() { - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return nativeListener.apply(this, arguments); } const target = this || _global; @@ -491,7 +491,7 @@ export function patchEventTarget( } proto[REMOVE_EVENT_LISTENER] = function() { - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return nativeRemoveAllListeners.apply(this, arguments); } const target = this || _global; @@ -554,7 +554,7 @@ export function patchEventTarget( }; proto[LISTENERS_EVENT_LISTENER] = function() { - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return nativeListeners && nativeListeners.apply(this, arguments); } const target = this || _global; @@ -572,7 +572,7 @@ export function patchEventTarget( }; proto[REMOVE_ALL_LISTENERS_EVENT_LISTENER] = function() { - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return nativeRemoveAllListeners && nativeRemoveAllListeners.apply(this, arguments); } const target = this || _global; @@ -669,8 +669,8 @@ export function patchEventPrototype(global: any, api: _ZonePrivate) { const Event = global['Event']; if (Event && Event.prototype) { api.patchMethod( - Event.prototype, 'stopImmediatePropagation', - (delegate: Function) => function(self: any, args: any[]) { + Event.prototype, + 'stopImmediatePropagation', (delegate: Function) => function(self: any, args: any[]) { self[IMMEDIATE_PROPAGATION_SYMBOL] = true; // we need to call the native stopImmediatePropagation // in case in some hybrid application, some part of diff --git a/lib/common/fetch.ts b/lib/common/fetch.ts index 55c90781c..9e58360a1 100644 --- a/lib/common/fetch.ts +++ b/lib/common/fetch.ts @@ -21,7 +21,7 @@ Zone.__load_patch('fetch', (global: any, Zone: ZoneType, api: _ZonePrivate) => { if (supportAbort) { global['AbortController'] = function() { const abortController = new OriginalAbortController(); - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return abortController; } const signal = abortController.signal; @@ -29,8 +29,8 @@ Zone.__load_patch('fetch', (global: any, Zone: ZoneType, api: _ZonePrivate) => { return abortController; }; abortNative = api.patchMethod( - OriginalAbortController.prototype, 'abort', - (delegate: Function) => (self: any, args: any) => { + OriginalAbortController.prototype, + 'abort', (delegate: Function) => (self: any, args: any) => { if (self.task) { return self.task.zone.cancelTask(self.task); } @@ -39,7 +39,7 @@ Zone.__load_patch('fetch', (global: any, Zone: ZoneType, api: _ZonePrivate) => { } const placeholder = function() {}; global['fetch'] = function() { - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return fetch.apply(this, arguments); } const args = Array.prototype.slice.call(arguments); diff --git a/lib/common/promise.ts b/lib/common/promise.ts index 6894836c8..c91d6c9e1 100644 --- a/lib/common/promise.ts +++ b/lib/common/promise.ts @@ -403,7 +403,7 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr desc = {configurable: true, enumerable: true}; } desc.get = function() { - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return NativePromise; } // if we already set ZoneAwarePromise, use patched one diff --git a/lib/common/timers.ts b/lib/common/timers.ts index 7fbc65415..e80458f71 100644 --- a/lib/common/timers.ts +++ b/lib/common/timers.ts @@ -19,7 +19,8 @@ interface TimerOptions extends TaskData { args: any[]; } -export function patchTimer(window: any, setName: string, cancelName: string, nameSuffix: string, api: _ZonePrivate) { +export function patchTimer( + window: any, setName: string, cancelName: string, nameSuffix: string, api: _ZonePrivate) { let setNative: Function|null = null; let clearNative: Function|null = null; setName += nameSuffix; diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 9ae07f0df..592b5aad9 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -80,7 +80,7 @@ export function patchPrototype(prototype: any, fnNames: string[], api: _ZonePriv } prototype[name] = ((delegate: Function) => { const patched: any = function() { - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return delegate.apply(this, arguments); } return delegate.apply(this, bindArguments(arguments, source + '.' + name)); @@ -200,7 +200,7 @@ export function patchProperty(obj: any, prop: string, api: _ZonePrivate, prototy } desc.set = function(newValue) { - if (api.getMode() === 'native' && originalDescSet) { + if (api.getCurrentScope() === 'outside' && originalDescSet) { return originalDescSet.call(this, newValue); } // in some of windows's onproperty callback, this is undefined @@ -234,7 +234,7 @@ export function patchProperty(obj: any, prop: string, api: _ZonePrivate, prototy // The getter would return undefined for unassigned properties but the default value of an // unassigned property is null desc.get = function() { - if (api.getMode() === 'native' && originalDescGet) { + if (api.getCurrentScope() === 'outside' && originalDescGet) { return originalDescGet.call(this); } // in some of windows's onproperty callback, this is undefined @@ -273,7 +273,8 @@ export function patchProperty(obj: any, prop: string, api: _ZonePrivate, prototy obj[onPropPatchedSymbol] = true; } -export function patchOnProperties(obj: any, properties: string[]|null, api: _ZonePrivate, prototype?: any) { +export function patchOnProperties( + obj: any, properties: string[]|null, api: _ZonePrivate, prototype?: any) { if (properties) { for (let i = 0; i < properties.length; i++) { patchProperty(obj, 'on' + properties[i], api, prototype); @@ -301,7 +302,8 @@ export function patchClass(className: string, api: _ZonePrivate) { _global[zoneSymbol(className)] = OriginalClass; _global[className] = function() { - const a = api.getMode() === 'native' ? arguments : bindArguments(arguments, className); + const a = + api.getCurrentScope() === 'outside' ? arguments : bindArguments(arguments, className); switch (a.length) { case 0: this[originalInstanceKey] = new OriginalClass(); @@ -340,7 +342,7 @@ export function patchClass(className: string, api: _ZonePrivate) { } else { ObjectDefineProperty(_global[className].prototype, prop, { set: function(fn) { - if (typeof fn === 'function' && api.getMode() === 'default') { + if (typeof fn === 'function' && api.getCurrentScope() === 'inside') { this[originalInstanceKey][prop] = wrapWithCurrentZone(fn, className + '.' + prop); // keep callback in wrapped function so we can // use it in Function.prototype.toString to return @@ -397,8 +399,9 @@ export function setShouldCopySymbolProperties(flag: boolean) { export function patchMethod( target: any, name: string, - patchFnFactory: (delegate: Function, delegateName: string, name: string) => (self: any, args: any[]) => - any, api: _ZonePrivate): Function|null { + patchFnFactory: (delegate: Function, delegateName: string, name: string) => + (self: any, args: any[]) => any, + api: _ZonePrivate): Function|null { let proto = target; while (proto && !proto.hasOwnProperty(name)) { proto = ObjectGetPrototypeOf(proto); @@ -419,7 +422,7 @@ export function patchMethod( const patchDelegate = patchFnFactory(delegate!, delegateName, name); proto[name] = function() { // if current mode is `native`, just use `native` delegate. - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return delegate && delegate.apply(this, arguments); } return patchDelegate(this, arguments as any); @@ -442,7 +445,8 @@ export interface MacroTaskMeta extends TaskData { // TODO: @JiaLiPassion, support cancel task later if necessary export function patchMacroTask( - obj: any, funcName: string, metaCreator: (self: any, args: any[]) => MacroTaskMeta, api: _ZonePrivate) { + obj: any, funcName: string, metaCreator: (self: any, args: any[]) => MacroTaskMeta, + api: _ZonePrivate) { let setNative: Function|null = null; function scheduleTask(task: Task) { @@ -473,7 +477,8 @@ export interface MicroTaskMeta extends TaskData { } export function patchMicroTask( - obj: any, funcName: string, metaCreator: (self: any, args: any[]) => MicroTaskMeta, api: _ZonePrivate) { + obj: any, funcName: string, metaCreator: (self: any, args: any[]) => MicroTaskMeta, + api: _ZonePrivate) { let setNative: Function|null = null; function scheduleTask(task: Task) { diff --git a/lib/node/node.ts b/lib/node/node.ts index 731c78900..f0a841bfe 100644 --- a/lib/node/node.ts +++ b/lib/node/node.ts @@ -126,8 +126,8 @@ Zone.__load_patch('crypto', (global: any, _: ZoneType, api: _ZonePrivate) => { name: 'crypto.' + name, args: args, cbIdx: (args.length > 0 && typeof args[args.length - 1] === 'function') ? - args.length - 1 : - -1, + args.length - 1 : + -1, target: crypto }; }, api); @@ -142,7 +142,7 @@ Zone.__load_patch('console', (global: any, Zone: ZoneType, api: _ZonePrivate) => const originalMethod = (console as any)[Zone.__symbol__(m)] = (console as any)[m]; if (originalMethod) { (console as any)[m] = function() { - if (api.getMode() === 'native') { + if (api.getCurrentScope() === 'outside') { return originalMethod.apply(this, arguments); } const args = ArraySlice.call(arguments); diff --git a/lib/zone.ts b/lib/zone.ts index 117d62e24..cf292eec4 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -327,15 +327,17 @@ interface _ZonePrivate { patchMethod: (target: any, name: string, patchFn: (delegate: Function, delegateName: string, name: string) => - (self: any, args: any[]) => any, api: _ZonePrivate) => Function | null; + (self: any, args: any[]) => any, + api: _ZonePrivate) => Function | null; bindArguments: (args: any[], source: string) => any[]; - getMode: () => _ZoneMode; + getCurrentScope: () => _ZoneScope; } /** @internal */ interface _ZoneFrame { parent: _ZoneFrame|null; zone: Zone; + scope: _ZoneScope; } interface UncaughtPromiseError extends Error { @@ -634,7 +636,7 @@ type AmbientZone = Zone; /** @internal */ type AmbientZoneDelegate = ZoneDelegate; /** @internal */ -type _ZoneMode = 'default' | 'native'; +type _ZoneScope = 'outside'|'inside'; const Zone: ZoneType = (function(global: any) { const performance: {mark(name: string): void; measure(name: string, label: string): void;} = @@ -705,8 +707,12 @@ const Zone: ZoneType = (function(global: any) { } } - static __set_mode(zoneMode: _ZoneMode) { - _mode = zoneMode; + static __set_scope_mode(workingScope: 'global'|'scoped') { + if (workingScope === 'global') { + _currentZoneFrame.scope = 'inside'; + } else if (workingScope === 'scoped') { + _currentZoneFrame.scope = 'outside'; + } } public get parent(): AmbientZone|null { @@ -766,13 +772,14 @@ const Zone: ZoneType = (function(global: any) { public run(callback: Function, applyThis?: any, applyArgs?: any[], source?: string): any; public run( callback: (...args: any[]) => T, applyThis?: any, applyArgs?: any[], source?: string): T { - _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; - const _previous_mode = _mode; + _currentZoneFrame = { + parent: _currentZoneFrame, + zone: this, + scope: this.name !== '' ? 'inside' : _currentZoneFrame.scope + }; try { - _mode = 'default'; return this._zoneDelegate.invoke(this, callback, applyThis, applyArgs, source); } finally { - _mode = _previous_mode; _currentZoneFrame = _currentZoneFrame.parent!; } } @@ -781,14 +788,15 @@ const Zone: ZoneType = (function(global: any) { public runGuarded( callback: (...args: any[]) => T, applyThis: any = null, applyArgs?: any[], source?: string) { - _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; - const _previous_mode = _mode; + _currentZoneFrame = { + parent: _currentZoneFrame, + zone: this, + scope: this.name !== '' ? 'inside' : _currentZoneFrame.scope + }; try { try { - _mode = 'default'; return this._zoneDelegate.invoke(this, callback, applyThis, applyArgs, source); } catch (error) { - _mode = _previous_mode; if (this._zoneDelegate.handleError(this, error)) { throw error; } @@ -818,10 +826,12 @@ const Zone: ZoneType = (function(global: any) { task.runCount++; const previousTask = _currentTask; _currentTask = task; - _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; - const _previous_mode = _mode; + _currentZoneFrame = { + parent: _currentZoneFrame, + zone: this, + scope: this.name !== '' ? 'inside' : _currentZoneFrame.scope + }; try { - _mode = 'default'; if (task.type == macroTask && task.data && !task.data.isPeriodic) { task.cancelFn = undefined; } @@ -833,7 +843,6 @@ const Zone: ZoneType = (function(global: any) { } } } finally { - _mode = _previous_mode; // if the task's state is notScheduled or unknown, then it has already been cancelled // we should not reset the state to scheduled if (task.state !== notScheduled && task.state !== unknown) { @@ -1367,12 +1376,11 @@ const Zone: ZoneType = (function(global: any) { nativeMicroTaskQueuePromise = NativePromise.resolve(0); } }, - getMode: () => _mode + getCurrentScope: () => _currentZoneFrame.scope }; - let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)}; + let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null), scope: 'inside'}; let _currentTask: Task|null = null; let _numberOfNestedTaskFrames = 0; - let _mode: _ZoneMode = 'default'; function noop() {} diff --git a/package.json b/package.json index 533547247..50b22d86d 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "closure:test": "scripts/closure/closure_compiler.sh", "format": "gulp format:enforce", "karma-jasmine": "karma start karma-build-jasmine.conf.js", + "karma-jasmine:outside": "karma start karma-build-outside-jasmine.conf.js --entryPoint=browser_outside_zone_entry_point", "karma-jasmine:phantomjs": "karma start karma-build-jasmine-phantomjs.conf.js --single-run", "karma-jasmine:single": "karma start karma-build-jasmine.conf.js --single-run", "karma-jasmine:autoclose": "npm run karma-jasmine:single && npm run ws-client", @@ -38,6 +39,7 @@ "tsc:w": "tsc -w -p .", "tslint": "tslint -c tslint.json 'lib/**/*.ts'", "test": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine\"", + "test:outside": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run karma-jasmine:outside\"", "test:phantomjs": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine:phantomjs\"", "test:phantomjs-single": "npm run tsc && concurrently \"npm run ws-server\" \"npm run karma-jasmine-phantomjs:autoclose\"", "test:single": "npm run tsc && concurrently \"npm run ws-server\" \"npm run karma-jasmine:autoclose\"", diff --git a/test/browser-outside-zone-setup.ts b/test/browser-outside-zone-setup.ts new file mode 100644 index 000000000..e4d7cd178 --- /dev/null +++ b/test/browser-outside-zone-setup.ts @@ -0,0 +1,8 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +(Zone as any).__set_scope_mode('scoped'); diff --git a/test/browser/browser.outside.spec.ts b/test/browser/browser.outside.spec.ts new file mode 100644 index 000000000..36bb4655e --- /dev/null +++ b/test/browser/browser.outside.spec.ts @@ -0,0 +1,74 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +describe('Timer', () => { + let scheduleSpy: jasmine.Spy; + const noop = function() {}; + let zone: Zone; + beforeEach(() => { + scheduleSpy = spyOn((Zone as any).prototype, 'scheduleTask').and.callThrough(); + zone = Zone.current.fork({name: 'test'}); + }); + it('setTimeout outside zone.run should not schedule task', (done: DoneFn) => { + (Zone as any).__set_scope_mode('scoped'); + setTimeout(function() { + expect(scheduleSpy).not.toHaveBeenCalled(); + done(); + }); + }); + it('setTimeout inside zone.run should schedule task', (done: DoneFn) => { + (Zone as any).__set_scope_mode('scoped'); + zone.run(() => { + setTimeout(function() { + expect(scheduleSpy).toHaveBeenCalled(); + done(); + }); + }); + }); + it('setTimeout outside and inside zone.run should schedule task correctly', (done: DoneFn) => { + (Zone as any).__set_scope_mode('scoped'); + setTimeout(function() {}); + expect(scheduleSpy).not.toHaveBeenCalled(); + zone.run(() => { + setTimeout(function() { + done(); + }); + expect(scheduleSpy).toHaveBeenCalled(); + }); + }); + it('nested setTimeout inside zone.run should schedule task', (done: DoneFn) => { + (Zone as any).__set_scope_mode('scoped'); + zone.run(() => { + setTimeout(function() { + setTimeout(function() { + done(); + }); + expect(scheduleSpy.calls.count()).toBe(2); + }); + expect(scheduleSpy.calls.count()).toBe(1); + }); + }); + it('outside setTimeout after nested setTimeout inside zone.run should not schedule task', + (done: DoneFn) => { + (Zone as any).__set_scope_mode('scoped'); + zone.run(() => { + setTimeout(function() { + setTimeout(function() {}); + expect(scheduleSpy.calls.count()).toBe(2); + }); + expect(scheduleSpy.calls.count()).toBe(1); + }); + setTimeout(function() { + setTimeout(function() { + done(); + }); + expect(scheduleSpy.calls.count()).toBe(2); + }); + expect(scheduleSpy.calls.count()).toBe(1); + }); +}); diff --git a/test/browser/browser.spec.ts b/test/browser/browser.spec.ts index 3a0ad1588..d61f789b9 100644 --- a/test/browser/browser.spec.ts +++ b/test/browser/browser.spec.ts @@ -201,7 +201,8 @@ describe('Zone', function() { it('should not patch ignored on properties', function() { const TestTarget: any = (window as any)['TestTarget']; patchFilteredProperties( - TestTarget.prototype, ['prop1', 'prop2'], global['__Zone_ignore_on_properties'], testApi); + TestTarget.prototype, ['prop1', 'prop2'], global['__Zone_ignore_on_properties'], + testApi); const testTarget = new TestTarget(); Zone.current.fork({name: 'test'}).run(() => { testTarget.onprop1 = function() { diff --git a/test/browser_outside_zone_entry_point.ts b/test/browser_outside_zone_entry_point.ts new file mode 100644 index 000000000..d1fba0a91 --- /dev/null +++ b/test/browser_outside_zone_entry_point.ts @@ -0,0 +1,10 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +// List all tests here: +import './browser/browser.outside.spec'; diff --git a/test/common/util.spec.ts b/test/common/util.spec.ts index 2009095e5..6c946e54d 100644 --- a/test/common/util.spec.ts +++ b/test/common/util.spec.ts @@ -7,7 +7,7 @@ */ import {patchMethod, patchProperty, patchPrototype, zoneSymbol} from '../../lib/common/utils'; -import { testApi } from '../test-util'; +import {testApi} from '../test-util'; describe('utils', function() { describe('patchMethod', () => { @@ -269,8 +269,8 @@ describe('utils', function() { log.length = 0; patchMethod( - TestFunction.prototype, 'property2', - function(delegate: Function, delegateName: string, name: string) { + TestFunction.prototype, + 'property2', function(delegate: Function, delegateName: string, name: string) { return function(self: any, args: any) { log.push('patched property2'); }; @@ -313,8 +313,8 @@ describe('utils', function() { log.length = 0; patchMethod( - TestFunction.prototype, 'property2', - function(delegate: Function, delegateName: string, name: string) { + TestFunction.prototype, + 'property2', function(delegate: Function, delegateName: string, name: string) { return function(self: any, args: any) { log.push('patched property2'); }; diff --git a/test/main.ts b/test/main.ts index ccfedf5a8..af9398df5 100644 --- a/test/main.ts +++ b/test/main.ts @@ -14,11 +14,19 @@ declare const __karma__: { __karma__.loaded = function() {}; +let entryPoint: any; + if (typeof __karma__ !== 'undefined') { (window as any)['__Zone_Error_BlacklistedStackFrames_policy'] = (__karma__ as any).config.errorpolicy; + entryPoint = (__karma__ as any).config.entryPoint; } else if (typeof process !== 'undefined') { (window as any)['__Zone_Error_BlacklistedStackFrames_policy'] = process.env.errorpolicy; + entryPoint = process.env.entryPoint; +} + +if (!entryPoint) { + entryPoint = 'browser_entry_point'; } (window as any).global = window; @@ -51,7 +59,7 @@ browserPatchedPromise.then(() => { // Setup test environment System.import(testFrameworkPatch).then(() => { System.import('/base/build/lib/common/error-rewrite').then(() => { - System.import('/base/build/test/browser_entry_point') + System.import(`/base/build/test/${entryPoint}`) .then( () => { __karma__.start(); diff --git a/test/test-util.ts b/test/test-util.ts index 2dea6f0f7..947ac90a1 100644 --- a/test/test-util.ts +++ b/test/test-util.ts @@ -133,11 +133,11 @@ export function getEdgeVersion() { return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); } -const noop = function () { }; +const noop = function() {}; export const testApi: _ZonePrivate = { symbol: Zone.__symbol__, - currentZoneFrame: () => ({ parent: null, zone: Zone.current }), + currentZoneFrame: () => ({parent: null, zone: Zone.current, scope: 'inside'}), onUnhandledError: noop, microtaskDrainDone: noop, scheduleMicroTask: noop, @@ -148,5 +148,5 @@ export const testApi: _ZonePrivate = { bindArguments: () => [], patchThen: () => noop, setNativePromise: (NativePromise: any) => noop, - getMode: () => 'default' + getCurrentScope: () => 'inside' }; diff --git a/test/zone-spec/fake-async-test.spec.ts b/test/zone-spec/fake-async-test.spec.ts index 44748e54d..278232972 100644 --- a/test/zone-spec/fake-async-test.spec.ts +++ b/test/zone-spec/fake-async-test.spec.ts @@ -812,7 +812,8 @@ describe('FakeAsyncTestZoneSpec', () => { patchMacroTask( TestClass.prototype, 'myTimeout', (self: any, args: any[]) => - ({name: 'TestClass.myTimeout', target: self, cbIdx: 0, args: args}), testApi); + ({name: 'TestClass.myTimeout', target: self, cbIdx: 0, args: args}), + testApi); const testClass = new TestClass(); testClass.myTimeout(function(callbackArgs: any) { @@ -838,7 +839,8 @@ describe('FakeAsyncTestZoneSpec', () => { patchMacroTask( TestClass.prototype, 'myTimeout', (self: any, args: any[]) => - ({name: 'TestClass.myTimeout', target: self, cbIdx: 0, args: args}), testApi); + ({name: 'TestClass.myTimeout', target: self, cbIdx: 0, args: args}), + testApi); const testClass = new TestClass(); testClass.myTimeout(() => { @@ -867,7 +869,8 @@ describe('FakeAsyncTestZoneSpec', () => { patchMacroTask( TestClass.prototype, 'myInterval', (self: any, args: any[]) => - ({name: 'TestClass.myInterval', target: self, cbIdx: 0, args: args}), testApi); + ({name: 'TestClass.myInterval', target: self, cbIdx: 0, args: args}), + testApi); const testClass = new TestClass(); const id = testClass.myInterval(() => {