From 3524195989d6a11550f6c518a6c30e69be7b32fe Mon Sep 17 00:00:00 2001 From: Kuitos Date: Wed, 18 Oct 2023 01:52:52 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9Bnot=20rebind=20non-native=20global?= =?UTF-8?q?=20properties=20(#2733)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .fatherrc.js | 7 +- src/sandbox/__tests__/common.test.ts | 22 +- src/sandbox/__tests__/proxySandbox.test.ts | 58 +- src/sandbox/common.ts | 30 +- src/sandbox/globals.ts | 742 ++++++++++++++++++++- src/sandbox/legacy/sandbox.ts | 4 +- src/sandbox/proxySandbox.ts | 25 +- src/utils.ts | 3 +- 8 files changed, 825 insertions(+), 66 deletions(-) diff --git a/.fatherrc.js b/.fatherrc.js index 7f53b17db..d7c46311d 100644 --- a/.fatherrc.js +++ b/.fatherrc.js @@ -13,11 +13,14 @@ writeFileSync( globalsFilePath, `// generated from https://github.com/sindresorhus/globals/blob/main/globals.json es2015 part // only init its values while Proxy is supported -export const globals = window.Proxy ? ${JSON.stringify( +export const globalsInES2015 = window.Proxy ? ${JSON.stringify( Object.keys(globals.es2015), null, 2, - )}.filter(p => /* just keep the available properties in current window context */ p in window) : [];`, + )}.filter(p => /* just keep the available properties in current window context */ p in window) : []; + +export const globalsInBrowser = ${JSON.stringify(Object.keys(globals.browser), null, 2)}; + `, ); export default { diff --git a/src/sandbox/__tests__/common.test.ts b/src/sandbox/__tests__/common.test.ts index 429e21eb6..6a105f453 100644 --- a/src/sandbox/__tests__/common.test.ts +++ b/src/sandbox/__tests__/common.test.ts @@ -3,17 +3,17 @@ * @since 2021-04-12 */ -import { getTargetValue } from '../common'; +import { rebindTarget2Fn } from '../common'; describe('getTargetValue', () => { it('should work well', () => { - const a1 = getTargetValue(window, undefined); + const a1 = rebindTarget2Fn(window, undefined); expect(a1).toEqual(undefined); - const a2 = getTargetValue(window, null); + const a2 = rebindTarget2Fn(window, null); expect(a2).toEqual(null); - const a3 = getTargetValue(window, function bindThis(this: any) { + const a3 = rebindTarget2Fn(window, function bindThis(this: any) { return this; }); const a3returns = a3(); @@ -24,7 +24,7 @@ describe('getTargetValue', () => { function prototypeAddedAfterFirstInvocation(this: any, field: string) { this.field = field; } - const notConstructableFunction = getTargetValue(window, prototypeAddedAfterFirstInvocation); + const notConstructableFunction = rebindTarget2Fn(window, prototypeAddedAfterFirstInvocation); // `this` of not constructable function will be bound automatically, and it can not be changed by calling with special `this` const result = {}; notConstructableFunction.call(result, '123'); @@ -32,7 +32,7 @@ describe('getTargetValue', () => { expect(window.field).toEqual('123'); prototypeAddedAfterFirstInvocation.prototype.addedFn = () => {}; - const constructableFunction = getTargetValue(window, prototypeAddedAfterFirstInvocation); + const constructableFunction = rebindTarget2Fn(window, prototypeAddedAfterFirstInvocation); // `this` coule be available if it be predicated as a constructable function const result2 = {}; constructableFunction.call(result2, '456'); @@ -40,7 +40,7 @@ describe('getTargetValue', () => { // window.field not be affected expect(window.field).toEqual('123'); // reference should be stable after first running - expect(constructableFunction).toBe(getTargetValue(window, prototypeAddedAfterFirstInvocation)); + expect(constructableFunction).toBe(rebindTarget2Fn(window, prototypeAddedAfterFirstInvocation)); }); it('should work well while value have a readonly prototype on its prototype chain', () => { @@ -56,7 +56,7 @@ describe('getTargetValue', () => { Object.setPrototypeOf(callableFunction, functionWithReadonlyPrototype); - const boundFn = getTargetValue(window, callableFunction); + const boundFn = rebindTarget2Fn(window, callableFunction); expect(boundFn.prototype).toBe(callableFunction.prototype); }); @@ -71,9 +71,9 @@ describe('getTargetValue', () => { }, }); - const boundFn1 = getTargetValue(window, callableFunction1); - const boundFn2 = getTargetValue(window, callableFunction2); - const boundFn3 = getTargetValue(window, callableFunction3); + const boundFn1 = rebindTarget2Fn(window, callableFunction1); + const boundFn2 = rebindTarget2Fn(window, callableFunction2); + const boundFn3 = rebindTarget2Fn(window, callableFunction3); expect(boundFn1.toString()).toBe(callableFunction1.toString()); expect(boundFn2.toString()).toBe(callableFunction2.toString()); diff --git a/src/sandbox/__tests__/proxySandbox.test.ts b/src/sandbox/__tests__/proxySandbox.test.ts index 3cd52f2d2..ac29820c3 100644 --- a/src/sandbox/__tests__/proxySandbox.test.ts +++ b/src/sandbox/__tests__/proxySandbox.test.ts @@ -293,16 +293,35 @@ it('document should work well with MutationObserver', (done) => { docProxy.document.body.innerHTML = '
'; }); -it('bounded function should not be rebounded', () => { - const proxy = new ProxySandbox('bound-fn-test').proxy as any; - const fn = () => {}; - const boundedFn = fn.bind(null); - proxy.fn1 = fn; - proxy.fn2 = boundedFn; +it('native window function calling should always be bound with window', () => { + window.mockNativeWindowFunction = function mockNativeWindowFunction(this: any) { + if (this !== undefined && this !== window) { + throw new Error('Illegal Invocation!'); + } + + return 'success'; + }; + + const { proxy } = new ProxySandbox('mustBeBoundWithWindowReference'); + expect(proxy.mockNativeWindowFunction()).toBe('success'); +}); + +it('native bounded function should not be rebounded', () => { + const proxy = new ProxySandbox('bound-native-fn-test').proxy as any; + const boundedFn = atob.bind(null); + proxy.atob = boundedFn; + + expect(proxy.atob === boundedFn).toBeTruthy(); + expect(isBoundedFunction(proxy.atob)).toBeTruthy(); +}); + +it('non-native function should not be rebounded', () => { + const proxy = new ProxySandbox('non-native-fn-bound-test').proxy as any; + function test() {} + proxy.fn = test; - expect(proxy.fn1 === fn).toBeFalsy(); - expect(proxy.fn2 === boundedFn).toBeTruthy(); - expect(isBoundedFunction(proxy.fn1)).toBeTruthy(); + expect(proxy.fn === test).toBeTruthy(); + expect(isBoundedFunction(proxy.fn)).toBeFalsy(); }); it('frozen property should not be overwrite', () => { @@ -332,12 +351,8 @@ it('frozen property should not be overwrite', () => { it('the prototype should be kept while we create a function with prototype on proxy', () => { const proxy = new ProxySandbox('new-function').proxy as any; - - function test() {} - - proxy.fn = test; - expect(proxy.fn === test).toBeFalsy(); - expect(proxy.fn.prototype).toBe(test.prototype); + proxy.CustomEvent = CustomEvent; + expect(proxy.CustomEvent.prototype).toBe(CustomEvent.prototype); }); it('some native window property was defined with getter in safari and firefox, and they will check the caller source', () => { @@ -399,19 +414,6 @@ it('should get current running sandbox proxy correctly', async () => { }); }); -it('native window function calling should always be bound with window', () => { - window.nativeWindowFunction = function nativeWindowFunction(this: any) { - if (this !== undefined && this !== window) { - throw new Error('Illegal Invocation!'); - } - - return 'success'; - }; - - const { proxy } = new ProxySandbox('mustBeBoundWithWindowReference'); - expect(proxy.nativeWindowFunction()).toBe('success'); -}); - describe('should work well while the property existed in global context before', () => { it('should not write value while the readonly property existed in global context but not in sandbox', () => { Object.defineProperty(window, 'readonlyPropertyInGlobalContext', { diff --git a/src/sandbox/common.ts b/src/sandbox/common.ts index 83530fcfd..ff60bff95 100644 --- a/src/sandbox/common.ts +++ b/src/sandbox/common.ts @@ -26,60 +26,60 @@ export function clearCurrentRunningApp() { const functionBoundedValueMap = new WeakMap(); -export function getTargetValue(target: any, value: any): any { +export function rebindTarget2Fn(target: any, fn: any): any { /* 仅绑定 isCallable && !isBoundedFunction && !isConstructable 的函数对象,如 window.console、window.atob 这类,不然微应用中调用时会抛出 Illegal invocation 异常 目前没有完美的检测方式,这里通过 prototype 中是否还有可枚举的拓展方法的方式来判断 @warning 这里不要随意替换成别的判断方式,因为可能触发一些 edge case(比如在 lodash.isFunction 在 iframe 上下文中可能由于调用了 top window 对象触发的安全异常) */ - if (isCallable(value) && !isBoundedFunction(value) && !isConstructable(value)) { - const cachedBoundFunction = functionBoundedValueMap.get(value); + if (isCallable(fn) && !isBoundedFunction(fn) && !isConstructable(fn)) { + const cachedBoundFunction = functionBoundedValueMap.get(fn); if (cachedBoundFunction) { return cachedBoundFunction; } - const boundValue = Function.prototype.bind.call(value, target); + const boundValue = Function.prototype.bind.call(fn, target); // some callable function has custom fields, we need to copy the own props to boundValue. such as moment function. - Object.getOwnPropertyNames(value).forEach((key) => { + Object.getOwnPropertyNames(fn).forEach((key) => { // boundValue might be a proxy, we need to check the key whether exist in it if (!boundValue.hasOwnProperty(key)) { - Object.defineProperty(boundValue, key, Object.getOwnPropertyDescriptor(value, key)!); + Object.defineProperty(boundValue, key, Object.getOwnPropertyDescriptor(fn, key)!); } }); // copy prototype if bound function not have but target one have // as prototype is non-enumerable mostly, we need to copy it from target function manually - if (value.hasOwnProperty('prototype') && !boundValue.hasOwnProperty('prototype')) { - // we should not use assignment operator to set boundValue prototype like `boundValue.prototype = value.prototype` + if (fn.hasOwnProperty('prototype') && !boundValue.hasOwnProperty('prototype')) { + // we should not use assignment operator to set boundValue prototype like `boundValue.prototype = fn.prototype` // as the assignment will also look up prototype chain while it hasn't own prototype property, // when the lookup succeed, the assignment will throw an TypeError like `Cannot assign to read only property 'prototype' of function` if its descriptor configured with writable false or just have a getter accessor // see https://github.com/umijs/qiankun/issues/1121 - Object.defineProperty(boundValue, 'prototype', { value: value.prototype, enumerable: false, writable: true }); + Object.defineProperty(boundValue, 'prototype', { value: fn.prototype, enumerable: false, writable: true }); } // Some util, like `function isNative() { return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) }` relies on the original `toString()` result // but bound functions will always return "function() {[native code]}" for `toString`, which is misleading - if (typeof value.toString === 'function') { - const valueHasInstanceToString = value.hasOwnProperty('toString') && !boundValue.hasOwnProperty('toString'); + if (typeof fn.toString === 'function') { + const valueHasInstanceToString = fn.hasOwnProperty('toString') && !boundValue.hasOwnProperty('toString'); const boundValueHasPrototypeToString = boundValue.toString === Function.prototype.toString; if (valueHasInstanceToString || boundValueHasPrototypeToString) { const originToStringDescriptor = Object.getOwnPropertyDescriptor( - valueHasInstanceToString ? value : Function.prototype, + valueHasInstanceToString ? fn : Function.prototype, 'toString', ); Object.defineProperty(boundValue, 'toString', { ...originToStringDescriptor, - ...(originToStringDescriptor?.get ? null : { value: () => value.toString() }), + ...(originToStringDescriptor?.get ? null : { value: () => fn.toString() }), }); } } - functionBoundedValueMap.set(value, boundValue); + functionBoundedValueMap.set(fn, boundValue); return boundValue; } - return value; + return fn; } diff --git a/src/sandbox/globals.ts b/src/sandbox/globals.ts index b0454e7e3..8eb828e3d 100644 --- a/src/sandbox/globals.ts +++ b/src/sandbox/globals.ts @@ -1,6 +1,6 @@ // generated from https://github.com/sindresorhus/globals/blob/main/globals.json es2015 part // only init its values while Proxy is supported -export const globals = window.Proxy +export const globalsInES2015 = window.Proxy ? [ 'Array', 'ArrayBuffer', @@ -61,3 +61,743 @@ export const globals = window.Proxy 'WeakSet', ].filter((p) => /* just keep the available properties in current window context */ p in window) : []; + +export const globalsInBrowser = [ + 'AbortController', + 'AbortSignal', + 'addEventListener', + 'alert', + 'AnalyserNode', + 'Animation', + 'AnimationEffectReadOnly', + 'AnimationEffectTiming', + 'AnimationEffectTimingReadOnly', + 'AnimationEvent', + 'AnimationPlaybackEvent', + 'AnimationTimeline', + 'applicationCache', + 'ApplicationCache', + 'ApplicationCacheErrorEvent', + 'atob', + 'Attr', + 'Audio', + 'AudioBuffer', + 'AudioBufferSourceNode', + 'AudioContext', + 'AudioDestinationNode', + 'AudioListener', + 'AudioNode', + 'AudioParam', + 'AudioProcessingEvent', + 'AudioScheduledSourceNode', + 'AudioWorkletGlobalScope', + 'AudioWorkletNode', + 'AudioWorkletProcessor', + 'BarProp', + 'BaseAudioContext', + 'BatteryManager', + 'BeforeUnloadEvent', + 'BiquadFilterNode', + 'Blob', + 'BlobEvent', + 'blur', + 'BroadcastChannel', + 'btoa', + 'BudgetService', + 'ByteLengthQueuingStrategy', + 'Cache', + 'caches', + 'CacheStorage', + 'cancelAnimationFrame', + 'cancelIdleCallback', + 'CanvasCaptureMediaStreamTrack', + 'CanvasGradient', + 'CanvasPattern', + 'CanvasRenderingContext2D', + 'ChannelMergerNode', + 'ChannelSplitterNode', + 'CharacterData', + 'clearInterval', + 'clearTimeout', + 'clientInformation', + 'ClipboardEvent', + 'ClipboardItem', + 'close', + 'closed', + 'CloseEvent', + 'Comment', + 'CompositionEvent', + 'CompressionStream', + 'confirm', + 'console', + 'ConstantSourceNode', + 'ConvolverNode', + 'CountQueuingStrategy', + 'createImageBitmap', + 'Credential', + 'CredentialsContainer', + 'crypto', + 'Crypto', + 'CryptoKey', + 'CSS', + 'CSSConditionRule', + 'CSSFontFaceRule', + 'CSSGroupingRule', + 'CSSImportRule', + 'CSSKeyframeRule', + 'CSSKeyframesRule', + 'CSSMatrixComponent', + 'CSSMediaRule', + 'CSSNamespaceRule', + 'CSSPageRule', + 'CSSPerspective', + 'CSSRotate', + 'CSSRule', + 'CSSRuleList', + 'CSSScale', + 'CSSSkew', + 'CSSSkewX', + 'CSSSkewY', + 'CSSStyleDeclaration', + 'CSSStyleRule', + 'CSSStyleSheet', + 'CSSSupportsRule', + 'CSSTransformValue', + 'CSSTranslate', + 'CustomElementRegistry', + 'customElements', + 'CustomEvent', + 'DataTransfer', + 'DataTransferItem', + 'DataTransferItemList', + 'DecompressionStream', + 'defaultstatus', + 'defaultStatus', + 'DelayNode', + 'DeviceMotionEvent', + 'DeviceOrientationEvent', + 'devicePixelRatio', + 'dispatchEvent', + 'document', + 'Document', + 'DocumentFragment', + 'DocumentType', + 'DOMError', + 'DOMException', + 'DOMImplementation', + 'DOMMatrix', + 'DOMMatrixReadOnly', + 'DOMParser', + 'DOMPoint', + 'DOMPointReadOnly', + 'DOMQuad', + 'DOMRect', + 'DOMRectList', + 'DOMRectReadOnly', + 'DOMStringList', + 'DOMStringMap', + 'DOMTokenList', + 'DragEvent', + 'DynamicsCompressorNode', + 'Element', + 'ErrorEvent', + 'event', + 'Event', + 'EventSource', + 'EventTarget', + 'external', + 'fetch', + 'File', + 'FileList', + 'FileReader', + 'find', + 'focus', + 'FocusEvent', + 'FontFace', + 'FontFaceSetLoadEvent', + 'FormData', + 'FormDataEvent', + 'frameElement', + 'frames', + 'GainNode', + 'Gamepad', + 'GamepadButton', + 'GamepadEvent', + 'getComputedStyle', + 'getSelection', + 'HashChangeEvent', + 'Headers', + 'history', + 'History', + 'HTMLAllCollection', + 'HTMLAnchorElement', + 'HTMLAreaElement', + 'HTMLAudioElement', + 'HTMLBaseElement', + 'HTMLBodyElement', + 'HTMLBRElement', + 'HTMLButtonElement', + 'HTMLCanvasElement', + 'HTMLCollection', + 'HTMLContentElement', + 'HTMLDataElement', + 'HTMLDataListElement', + 'HTMLDetailsElement', + 'HTMLDialogElement', + 'HTMLDirectoryElement', + 'HTMLDivElement', + 'HTMLDListElement', + 'HTMLDocument', + 'HTMLElement', + 'HTMLEmbedElement', + 'HTMLFieldSetElement', + 'HTMLFontElement', + 'HTMLFormControlsCollection', + 'HTMLFormElement', + 'HTMLFrameElement', + 'HTMLFrameSetElement', + 'HTMLHeadElement', + 'HTMLHeadingElement', + 'HTMLHRElement', + 'HTMLHtmlElement', + 'HTMLIFrameElement', + 'HTMLImageElement', + 'HTMLInputElement', + 'HTMLLabelElement', + 'HTMLLegendElement', + 'HTMLLIElement', + 'HTMLLinkElement', + 'HTMLMapElement', + 'HTMLMarqueeElement', + 'HTMLMediaElement', + 'HTMLMenuElement', + 'HTMLMetaElement', + 'HTMLMeterElement', + 'HTMLModElement', + 'HTMLObjectElement', + 'HTMLOListElement', + 'HTMLOptGroupElement', + 'HTMLOptionElement', + 'HTMLOptionsCollection', + 'HTMLOutputElement', + 'HTMLParagraphElement', + 'HTMLParamElement', + 'HTMLPictureElement', + 'HTMLPreElement', + 'HTMLProgressElement', + 'HTMLQuoteElement', + 'HTMLScriptElement', + 'HTMLSelectElement', + 'HTMLShadowElement', + 'HTMLSlotElement', + 'HTMLSourceElement', + 'HTMLSpanElement', + 'HTMLStyleElement', + 'HTMLTableCaptionElement', + 'HTMLTableCellElement', + 'HTMLTableColElement', + 'HTMLTableElement', + 'HTMLTableRowElement', + 'HTMLTableSectionElement', + 'HTMLTemplateElement', + 'HTMLTextAreaElement', + 'HTMLTimeElement', + 'HTMLTitleElement', + 'HTMLTrackElement', + 'HTMLUListElement', + 'HTMLUnknownElement', + 'HTMLVideoElement', + 'IDBCursor', + 'IDBCursorWithValue', + 'IDBDatabase', + 'IDBFactory', + 'IDBIndex', + 'IDBKeyRange', + 'IDBObjectStore', + 'IDBOpenDBRequest', + 'IDBRequest', + 'IDBTransaction', + 'IDBVersionChangeEvent', + 'IdleDeadline', + 'IIRFilterNode', + 'Image', + 'ImageBitmap', + 'ImageBitmapRenderingContext', + 'ImageCapture', + 'ImageData', + 'indexedDB', + 'innerHeight', + 'innerWidth', + 'InputEvent', + 'IntersectionObserver', + 'IntersectionObserverEntry', + 'Intl', + 'isSecureContext', + 'KeyboardEvent', + 'KeyframeEffect', + 'KeyframeEffectReadOnly', + 'length', + 'localStorage', + 'location', + 'Location', + 'locationbar', + 'matchMedia', + 'MediaDeviceInfo', + 'MediaDevices', + 'MediaElementAudioSourceNode', + 'MediaEncryptedEvent', + 'MediaError', + 'MediaKeyMessageEvent', + 'MediaKeySession', + 'MediaKeyStatusMap', + 'MediaKeySystemAccess', + 'MediaList', + 'MediaMetadata', + 'MediaQueryList', + 'MediaQueryListEvent', + 'MediaRecorder', + 'MediaSettingsRange', + 'MediaSource', + 'MediaStream', + 'MediaStreamAudioDestinationNode', + 'MediaStreamAudioSourceNode', + 'MediaStreamEvent', + 'MediaStreamTrack', + 'MediaStreamTrackEvent', + 'menubar', + 'MessageChannel', + 'MessageEvent', + 'MessagePort', + 'MIDIAccess', + 'MIDIConnectionEvent', + 'MIDIInput', + 'MIDIInputMap', + 'MIDIMessageEvent', + 'MIDIOutput', + 'MIDIOutputMap', + 'MIDIPort', + 'MimeType', + 'MimeTypeArray', + 'MouseEvent', + 'moveBy', + 'moveTo', + 'MutationEvent', + 'MutationObserver', + 'MutationRecord', + 'name', + 'NamedNodeMap', + 'NavigationPreloadManager', + 'navigator', + 'Navigator', + 'NavigatorUAData', + 'NetworkInformation', + 'Node', + 'NodeFilter', + 'NodeIterator', + 'NodeList', + 'Notification', + 'OfflineAudioCompletionEvent', + 'OfflineAudioContext', + 'offscreenBuffering', + 'OffscreenCanvas', + 'OffscreenCanvasRenderingContext2D', + 'onabort', + 'onafterprint', + 'onanimationend', + 'onanimationiteration', + 'onanimationstart', + 'onappinstalled', + 'onauxclick', + 'onbeforeinstallprompt', + 'onbeforeprint', + 'onbeforeunload', + 'onblur', + 'oncancel', + 'oncanplay', + 'oncanplaythrough', + 'onchange', + 'onclick', + 'onclose', + 'oncontextmenu', + 'oncuechange', + 'ondblclick', + 'ondevicemotion', + 'ondeviceorientation', + 'ondeviceorientationabsolute', + 'ondrag', + 'ondragend', + 'ondragenter', + 'ondragleave', + 'ondragover', + 'ondragstart', + 'ondrop', + 'ondurationchange', + 'onemptied', + 'onended', + 'onerror', + 'onfocus', + 'ongotpointercapture', + 'onhashchange', + 'oninput', + 'oninvalid', + 'onkeydown', + 'onkeypress', + 'onkeyup', + 'onlanguagechange', + 'onload', + 'onloadeddata', + 'onloadedmetadata', + 'onloadstart', + 'onlostpointercapture', + 'onmessage', + 'onmessageerror', + 'onmousedown', + 'onmouseenter', + 'onmouseleave', + 'onmousemove', + 'onmouseout', + 'onmouseover', + 'onmouseup', + 'onmousewheel', + 'onoffline', + 'ononline', + 'onpagehide', + 'onpageshow', + 'onpause', + 'onplay', + 'onplaying', + 'onpointercancel', + 'onpointerdown', + 'onpointerenter', + 'onpointerleave', + 'onpointermove', + 'onpointerout', + 'onpointerover', + 'onpointerup', + 'onpopstate', + 'onprogress', + 'onratechange', + 'onrejectionhandled', + 'onreset', + 'onresize', + 'onscroll', + 'onsearch', + 'onseeked', + 'onseeking', + 'onselect', + 'onstalled', + 'onstorage', + 'onsubmit', + 'onsuspend', + 'ontimeupdate', + 'ontoggle', + 'ontransitionend', + 'onunhandledrejection', + 'onunload', + 'onvolumechange', + 'onwaiting', + 'onwheel', + 'open', + 'openDatabase', + 'opener', + 'Option', + 'origin', + 'OscillatorNode', + 'outerHeight', + 'outerWidth', + 'OverconstrainedError', + 'PageTransitionEvent', + 'pageXOffset', + 'pageYOffset', + 'PannerNode', + 'parent', + 'Path2D', + 'PaymentAddress', + 'PaymentRequest', + 'PaymentRequestUpdateEvent', + 'PaymentResponse', + 'performance', + 'Performance', + 'PerformanceEntry', + 'PerformanceLongTaskTiming', + 'PerformanceMark', + 'PerformanceMeasure', + 'PerformanceNavigation', + 'PerformanceNavigationTiming', + 'PerformanceObserver', + 'PerformanceObserverEntryList', + 'PerformancePaintTiming', + 'PerformanceResourceTiming', + 'PerformanceTiming', + 'PeriodicWave', + 'Permissions', + 'PermissionStatus', + 'personalbar', + 'PhotoCapabilities', + 'Plugin', + 'PluginArray', + 'PointerEvent', + 'PopStateEvent', + 'postMessage', + 'Presentation', + 'PresentationAvailability', + 'PresentationConnection', + 'PresentationConnectionAvailableEvent', + 'PresentationConnectionCloseEvent', + 'PresentationConnectionList', + 'PresentationReceiver', + 'PresentationRequest', + 'print', + 'ProcessingInstruction', + 'ProgressEvent', + 'PromiseRejectionEvent', + 'prompt', + 'PushManager', + 'PushSubscription', + 'PushSubscriptionOptions', + 'queueMicrotask', + 'RadioNodeList', + 'Range', + 'ReadableByteStreamController', + 'ReadableStream', + 'ReadableStreamBYOBReader', + 'ReadableStreamBYOBRequest', + 'ReadableStreamDefaultController', + 'ReadableStreamDefaultReader', + 'registerProcessor', + 'RemotePlayback', + 'removeEventListener', + 'reportError', + 'Request', + 'requestAnimationFrame', + 'requestIdleCallback', + 'resizeBy', + 'ResizeObserver', + 'ResizeObserverEntry', + 'resizeTo', + 'Response', + 'RTCCertificate', + 'RTCDataChannel', + 'RTCDataChannelEvent', + 'RTCDtlsTransport', + 'RTCIceCandidate', + 'RTCIceGatherer', + 'RTCIceTransport', + 'RTCPeerConnection', + 'RTCPeerConnectionIceEvent', + 'RTCRtpContributingSource', + 'RTCRtpReceiver', + 'RTCRtpSender', + 'RTCSctpTransport', + 'RTCSessionDescription', + 'RTCStatsReport', + 'RTCTrackEvent', + 'screen', + 'Screen', + 'screenLeft', + 'ScreenOrientation', + 'screenTop', + 'screenX', + 'screenY', + 'ScriptProcessorNode', + 'scroll', + 'scrollbars', + 'scrollBy', + 'scrollTo', + 'scrollX', + 'scrollY', + 'SecurityPolicyViolationEvent', + 'Selection', + 'self', + 'ServiceWorker', + 'ServiceWorkerContainer', + 'ServiceWorkerRegistration', + 'sessionStorage', + 'setInterval', + 'setTimeout', + 'ShadowRoot', + 'SharedWorker', + 'SourceBuffer', + 'SourceBufferList', + 'speechSynthesis', + 'SpeechSynthesisEvent', + 'SpeechSynthesisUtterance', + 'StaticRange', + 'status', + 'statusbar', + 'StereoPannerNode', + 'stop', + 'Storage', + 'StorageEvent', + 'StorageManager', + 'structuredClone', + 'styleMedia', + 'StyleSheet', + 'StyleSheetList', + 'SubmitEvent', + 'SubtleCrypto', + 'SVGAElement', + 'SVGAngle', + 'SVGAnimatedAngle', + 'SVGAnimatedBoolean', + 'SVGAnimatedEnumeration', + 'SVGAnimatedInteger', + 'SVGAnimatedLength', + 'SVGAnimatedLengthList', + 'SVGAnimatedNumber', + 'SVGAnimatedNumberList', + 'SVGAnimatedPreserveAspectRatio', + 'SVGAnimatedRect', + 'SVGAnimatedString', + 'SVGAnimatedTransformList', + 'SVGAnimateElement', + 'SVGAnimateMotionElement', + 'SVGAnimateTransformElement', + 'SVGAnimationElement', + 'SVGCircleElement', + 'SVGClipPathElement', + 'SVGComponentTransferFunctionElement', + 'SVGDefsElement', + 'SVGDescElement', + 'SVGDiscardElement', + 'SVGElement', + 'SVGEllipseElement', + 'SVGFEBlendElement', + 'SVGFEColorMatrixElement', + 'SVGFEComponentTransferElement', + 'SVGFECompositeElement', + 'SVGFEConvolveMatrixElement', + 'SVGFEDiffuseLightingElement', + 'SVGFEDisplacementMapElement', + 'SVGFEDistantLightElement', + 'SVGFEDropShadowElement', + 'SVGFEFloodElement', + 'SVGFEFuncAElement', + 'SVGFEFuncBElement', + 'SVGFEFuncGElement', + 'SVGFEFuncRElement', + 'SVGFEGaussianBlurElement', + 'SVGFEImageElement', + 'SVGFEMergeElement', + 'SVGFEMergeNodeElement', + 'SVGFEMorphologyElement', + 'SVGFEOffsetElement', + 'SVGFEPointLightElement', + 'SVGFESpecularLightingElement', + 'SVGFESpotLightElement', + 'SVGFETileElement', + 'SVGFETurbulenceElement', + 'SVGFilterElement', + 'SVGForeignObjectElement', + 'SVGGElement', + 'SVGGeometryElement', + 'SVGGradientElement', + 'SVGGraphicsElement', + 'SVGImageElement', + 'SVGLength', + 'SVGLengthList', + 'SVGLinearGradientElement', + 'SVGLineElement', + 'SVGMarkerElement', + 'SVGMaskElement', + 'SVGMatrix', + 'SVGMetadataElement', + 'SVGMPathElement', + 'SVGNumber', + 'SVGNumberList', + 'SVGPathElement', + 'SVGPatternElement', + 'SVGPoint', + 'SVGPointList', + 'SVGPolygonElement', + 'SVGPolylineElement', + 'SVGPreserveAspectRatio', + 'SVGRadialGradientElement', + 'SVGRect', + 'SVGRectElement', + 'SVGScriptElement', + 'SVGSetElement', + 'SVGStopElement', + 'SVGStringList', + 'SVGStyleElement', + 'SVGSVGElement', + 'SVGSwitchElement', + 'SVGSymbolElement', + 'SVGTextContentElement', + 'SVGTextElement', + 'SVGTextPathElement', + 'SVGTextPositioningElement', + 'SVGTitleElement', + 'SVGTransform', + 'SVGTransformList', + 'SVGTSpanElement', + 'SVGUnitTypes', + 'SVGUseElement', + 'SVGViewElement', + 'TaskAttributionTiming', + 'Text', + 'TextDecoder', + 'TextDecoderStream', + 'TextEncoder', + 'TextEncoderStream', + 'TextEvent', + 'TextMetrics', + 'TextTrack', + 'TextTrackCue', + 'TextTrackCueList', + 'TextTrackList', + 'TimeRanges', + 'toolbar', + 'top', + 'Touch', + 'TouchEvent', + 'TouchList', + 'TrackEvent', + 'TransformStream', + 'TransformStreamDefaultController', + 'TransitionEvent', + 'TreeWalker', + 'UIEvent', + 'URL', + 'URLSearchParams', + 'ValidityState', + 'visualViewport', + 'VisualViewport', + 'VTTCue', + 'WaveShaperNode', + 'WebAssembly', + 'WebGL2RenderingContext', + 'WebGLActiveInfo', + 'WebGLBuffer', + 'WebGLContextEvent', + 'WebGLFramebuffer', + 'WebGLProgram', + 'WebGLQuery', + 'WebGLRenderbuffer', + 'WebGLRenderingContext', + 'WebGLSampler', + 'WebGLShader', + 'WebGLShaderPrecisionFormat', + 'WebGLSync', + 'WebGLTexture', + 'WebGLTransformFeedback', + 'WebGLUniformLocation', + 'WebGLVertexArrayObject', + 'WebSocket', + 'WheelEvent', + 'window', + 'Window', + 'Worker', + 'WritableStream', + 'WritableStreamDefaultController', + 'WritableStreamDefaultWriter', + 'XMLDocument', + 'XMLHttpRequest', + 'XMLHttpRequestEventTarget', + 'XMLHttpRequestUpload', + 'XMLSerializer', + 'XPathEvaluator', + 'XPathExpression', + 'XPathResult', + 'XSLTProcessor', +]; diff --git a/src/sandbox/legacy/sandbox.ts b/src/sandbox/legacy/sandbox.ts index 98aea1a52..4ab298e8b 100644 --- a/src/sandbox/legacy/sandbox.ts +++ b/src/sandbox/legacy/sandbox.ts @@ -4,7 +4,7 @@ */ import type { SandBox } from '../../interfaces'; import { SandBoxType } from '../../interfaces'; -import { getTargetValue } from '../common'; +import { rebindTarget2Fn } from '../common'; function isPropConfigurable(target: WindowProxy, prop: PropertyKey) { const descriptor = Object.getOwnPropertyDescriptor(target, prop); @@ -125,7 +125,7 @@ export default class LegacySandbox implements SandBox { } const value = (rawWindow as any)[p]; - return getTargetValue(rawWindow, value); + return rebindTarget2Fn(rawWindow, value); }, // trap in operator diff --git a/src/sandbox/proxySandbox.ts b/src/sandbox/proxySandbox.ts index e69f7a6b0..9b81cc240 100644 --- a/src/sandbox/proxySandbox.ts +++ b/src/sandbox/proxySandbox.ts @@ -1,14 +1,14 @@ /* eslint-disable no-param-reassign */ -import { without } from 'lodash'; /** * @author Kuitos * @since 2020-3-31 */ +import { without } from 'lodash'; import type { SandBox } from '../interfaces'; import { SandBoxType } from '../interfaces'; import { isPropertyFrozen, nativeGlobal, nextTask } from '../utils'; -import { clearCurrentRunningApp, getCurrentRunningApp, getTargetValue, setCurrentRunningApp } from './common'; -import { globals } from './globals'; +import { clearCurrentRunningApp, getCurrentRunningApp, rebindTarget2Fn, setCurrentRunningApp } from './common'; +import { globalsInBrowser, globalsInES2015 } from './globals'; type SymbolTarget = 'target' | 'globalContext'; @@ -24,6 +24,13 @@ function uniq(array: Array) { }, Object.create(null)); } +const cachedGlobalsInBrowser = globalsInBrowser + .concat(process.env.NODE_ENV === 'test' ? ['mockNativeWindowFunction'] : []) + .reduce>((acc, key) => ({ ...acc, [key]: true }), Object.create(null)); +function isNativeGlobalProp(prop: string): boolean { + return prop in cachedGlobalsInBrowser; +} + // zone.js will overwrite Object.defineProperty const rawObjectDefineProperty = Object.defineProperty; @@ -58,7 +65,9 @@ const mockGlobalThis = 'mockGlobalThis'; const accessingSpiedGlobals = ['document', 'top', 'parent', 'eval']; const overwrittenGlobals = ['window', 'self', 'globalThis', 'hasOwnProperty'].concat(inTest ? [mockGlobalThis] : []); export const cachedGlobals = Array.from( - new Set(without(globals.concat(overwrittenGlobals).concat('requestAnimationFrame'), ...accessingSpiedGlobals)), + new Set( + without(globalsInES2015.concat(overwrittenGlobals).concat('requestAnimationFrame'), ...accessingSpiedGlobals), + ), ); // transform cachedGlobals to object for faster element check @@ -297,14 +306,20 @@ export default class ProxySandbox implements SandBox { return value; } + // non-native property return directly to avoid rebind + if (!isNativeGlobalProp(p as string) && !useNativeWindowForBindingsProps.has(p)) { + return value; + } + /* Some dom api must be bound to native window, otherwise it would cause exception like 'TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation' See this code: const proxy = new Proxy(window, {}); + // in nest sandbox fetch will be bind to proxy rather than window in master const proxyFetch = fetch.bind(proxy); proxyFetch('https://qiankun.com'); */ const boundTarget = useNativeWindowForBindingsProps.get(p) ? nativeGlobal : globalContext; - return getTargetValue(boundTarget, value); + return rebindTarget2Fn(boundTarget, value); }, // trap in operator diff --git a/src/utils.ts b/src/utils.ts index 88e312cf3..299cf3036 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,8 +2,7 @@ * @author Kuitos * @since 2019-05-15 */ - -import { isFunction, once, snakeCase, memoize } from 'lodash'; +import { isFunction, memoize, once, snakeCase } from 'lodash'; import type { FrameworkConfiguration } from './interfaces'; import { version } from './version';