diff --git a/examples/main/index.js b/examples/main/index.js index d0c5458..cc759ef 100644 --- a/examples/main/index.js +++ b/examples/main/index.js @@ -6,7 +6,6 @@ import './index.less'; * 以下分别是 React 和 Vue 的示例,可切换尝试 */ import render from './render/ReactRender'; - // import render from './render/VueRender'; /** diff --git a/src/sandbox/__tests__/proxySandbox.test.ts b/src/sandbox/__tests__/proxySandbox.test.ts index aa69e4e..f18ec9a 100644 --- a/src/sandbox/__tests__/proxySandbox.test.ts +++ b/src/sandbox/__tests__/proxySandbox.test.ts @@ -3,7 +3,7 @@ * @since 2020-03-31 */ -import { isBoundedFunction } from '../../utils'; +import { isBoundedFunction, isPropertyReadonly } from '../../utils'; import { getCurrentRunningApp } from '../common'; import ProxySandbox from '../proxySandbox'; @@ -302,6 +302,24 @@ test('bounded function should not be rebounded', () => { expect(isBoundedFunction(proxy.fn1)).toBeTruthy(); }); +test('readonly property should not be overwrite', () => { + const proxy = new ProxySandbox('bound-fn-test').proxy as any; + + proxy.normalField = 'normalFieldValue'; + + Object.defineProperties(proxy, { + readOnlyField: { + value: 'readOnlyFieldValue', + configurable: false, + enumerable: true, + writable: false, + }, + }); + + expect(isPropertyReadonly(proxy, 'normalField')).toBeFalsy(); + expect(isPropertyReadonly(proxy, 'readOnlyField')).toBeTruthy(); +}); + test('the prototype should be kept while we create a function with prototype on proxy', () => { const proxy = new ProxySandbox('new-function').proxy as any; diff --git a/src/sandbox/common.ts b/src/sandbox/common.ts index 39552b6..bdf6d02 100644 --- a/src/sandbox/common.ts +++ b/src/sandbox/common.ts @@ -3,7 +3,7 @@ * @since 2020-04-13 */ -import { isBoundedFunction, isCallable, isConstructable } from '../utils'; +import { isBoundedFunction, isCallable, isConstructable, isPropertyReadonly } from '../utils'; type AppInstance = { name: string; window: WindowProxy }; let currentRunningApp: AppInstance | null = null; @@ -21,14 +21,14 @@ export function setCurrentRunningApp(appInstance: { name: string; window: Window } const functionBoundedValueMap = new WeakMap(); - -export function getTargetValue(target: any, value: any): any { +export function getTargetValue(target: any, value: any, p?: any): any { /* - 仅绑定 isCallable && !isBoundedFunction && !isConstructable 的函数对象,如 window.console、window.atob 这类,不然微应用中调用时会抛出 Illegal invocation 异常 + 仅绑定 isCallable && !isPropertyReadonly && !isBoundedFunction && !isConstructable 的函数对象,如 window.console、window.atob 这类,不然微应用中调用时会抛出 Illegal invocation 异常 目前没有完美的检测方式,这里通过 prototype 中是否还有可枚举的拓展方法的方式来判断 @warning 这里不要随意替换成别的判断方式,因为可能触发一些 edge case(比如在 lodash.isFunction 在 iframe 上下文中可能由于调用了 top window 对象触发的安全异常) + @warning 对于configurable及writable都为false的readonly属性,proxy必须返回原值 */ - if (isCallable(value) && !isBoundedFunction(value) && !isConstructable(value)) { + if (isCallable(value) && !isPropertyReadonly(target, p) && !isBoundedFunction(value) && !isConstructable(value)) { const cachedBoundFunction = functionBoundedValueMap.get(value); if (cachedBoundFunction) { return cachedBoundFunction; diff --git a/src/sandbox/proxySandbox.ts b/src/sandbox/proxySandbox.ts index 0f89fb2..e7b9907 100644 --- a/src/sandbox/proxySandbox.ts +++ b/src/sandbox/proxySandbox.ts @@ -262,7 +262,7 @@ export default class ProxySandbox implements SandBox { proxyFetch('https://qiankun.com'); */ const boundTarget = useNativeWindowForBindingsProps.get(p) ? nativeGlobal : globalContext; - return getTargetValue(boundTarget, value); + return getTargetValue(boundTarget, value, p); }, // trap in operator diff --git a/src/utils.ts b/src/utils.ts index c81e723..2af3f85 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -88,6 +88,37 @@ export const isCallable = (fn: any) => { return callable; }; +/** + * isPropertyReadonly + * @param target + * @param p + * @returns boolean + */ +const propertyReadonlyCacheMap = new WeakMap>(); +export function isPropertyReadonly(target: any, p?: PropertyKey): boolean { + // 异常返回 + if (!target || !p) { + return false; + } + + // 取缓存 + const targetPropertiesFromCache = propertyReadonlyCacheMap.get(target) || {}; + + if (typeof targetPropertiesFromCache[p] === 'boolean') { + return targetPropertiesFromCache[p]; + } + + // 计算 + const propertyDescriptor = Object.getOwnPropertyDescriptor(target, p); + const result = propertyDescriptor?.configurable === false && propertyDescriptor?.writable === false; + + // 写缓存 + targetPropertiesFromCache[p] = result; + propertyReadonlyCacheMap.set(target, targetPropertiesFromCache); + + return result; +} + const boundedMap = new WeakMap(); export function isBoundedFunction(fn: CallableFunction) {