🐛 proxy sandbox should not overwrite read-only fields. (#2123)
This commit is contained in:
parent
3a365382da
commit
0aaf55a77f
|
|
@ -6,7 +6,6 @@ import './index.less';
|
|||
* 以下分别是 React 和 Vue 的示例,可切换尝试
|
||||
*/
|
||||
import render from './render/ReactRender';
|
||||
|
||||
// import render from './render/VueRender';
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<CallableFunction, CallableFunction>();
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
31
src/utils.ts
31
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<any, Record<PropertyKey, boolean>>();
|
||||
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<CallableFunction, boolean>();
|
||||
|
||||
export function isBoundedFunction(fn: CallableFunction) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user