🐛 frozen property must be returned its actual value rather than bound value (#2285)
This commit is contained in:
parent
9a343b4074
commit
73a507f891
|
|
@ -5,7 +5,7 @@ import {
|
|||
getDefaultTplWrapper,
|
||||
getWrapperId,
|
||||
getXPathForElement,
|
||||
isPropertyReadonly,
|
||||
isPropertyFrozen,
|
||||
nextTask,
|
||||
sleep,
|
||||
validateExportLifecycle,
|
||||
|
|
@ -152,13 +152,13 @@ it('should genAppInstanceIdByName work well', () => {
|
|||
expect(instanceId3).toBe('hello_2');
|
||||
});
|
||||
|
||||
it('should isPropertyReadonly work well', () => {
|
||||
it('should isPropertyFrozen work well', () => {
|
||||
const a = {
|
||||
get name() {
|
||||
return 'read only';
|
||||
},
|
||||
};
|
||||
expect(isPropertyReadonly(a, 'name')).toBeTruthy();
|
||||
expect(isPropertyFrozen(a, 'name')).toBeFalsy();
|
||||
|
||||
const b = {
|
||||
get name() {
|
||||
|
|
@ -166,19 +166,28 @@ it('should isPropertyReadonly work well', () => {
|
|||
},
|
||||
set name(_) {},
|
||||
};
|
||||
expect(isPropertyReadonly(b, 'name')).toBeFalsy();
|
||||
expect(isPropertyFrozen(b, 'name')).toBeFalsy();
|
||||
|
||||
const c = {};
|
||||
Object.defineProperty(c, 'name', { writable: false });
|
||||
expect(isPropertyReadonly(c, 'name')).toBeTruthy();
|
||||
expect(isPropertyFrozen(c, 'name')).toBeTruthy();
|
||||
|
||||
const d = {};
|
||||
Object.defineProperty(d, 'name', { configurable: true });
|
||||
expect(isPropertyReadonly(d, 'name')).toBeTruthy();
|
||||
expect(isPropertyFrozen(d, 'name')).toBeFalsy();
|
||||
|
||||
const e = {};
|
||||
Object.defineProperty(e, 'name', { writable: true });
|
||||
expect(isPropertyReadonly(e, 'name')).toBeFalsy();
|
||||
Object.defineProperty(e, 'name', { configurable: false });
|
||||
expect(isPropertyFrozen(e, 'name')).toBeTruthy();
|
||||
|
||||
expect(isPropertyReadonly(undefined, 'name')).toBeFalsy();
|
||||
const f = {};
|
||||
Object.defineProperty(f, 'name', {
|
||||
get() {
|
||||
return 'test';
|
||||
},
|
||||
configurable: false,
|
||||
});
|
||||
expect(isPropertyFrozen(f, 'name')).toBeTruthy();
|
||||
|
||||
expect(isPropertyFrozen(undefined, 'name')).toBeFalsy();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* @since 2020-03-31
|
||||
*/
|
||||
|
||||
import { isBoundedFunction, isPropertyReadonly } from '../../utils';
|
||||
import { isBoundedFunction } from '../../utils';
|
||||
import { getCurrentRunningApp } from '../common';
|
||||
import ProxySandbox from '../proxySandbox';
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ beforeAll(() => {
|
|||
});
|
||||
});
|
||||
|
||||
test('iterator should be worked the same as the raw window', () => {
|
||||
it('iterator should be worked the same as the raw window', () => {
|
||||
Object.defineProperty(window, 'nonEnumerableValue', {
|
||||
enumerable: false,
|
||||
value: 1,
|
||||
|
|
@ -59,7 +59,7 @@ test('iterator should be worked the same as the raw window', () => {
|
|||
expect(Object.keys(proxy)).toEqual([...Object.keys(window), 'additionalProp']);
|
||||
});
|
||||
|
||||
test('window.self & window.window & window.top & window.parent should equals with sandbox', () => {
|
||||
it('window.self & window.window & window.top & window.parent should equals with sandbox', () => {
|
||||
const { proxy } = new ProxySandbox('unit-test');
|
||||
|
||||
expect(proxy.self).toBe(proxy);
|
||||
|
|
@ -71,12 +71,12 @@ test('window.self & window.window & window.top & window.parent should equals wit
|
|||
expect(proxy.parent).toBe(proxy);
|
||||
});
|
||||
|
||||
test('globalThis should equals with sandbox', () => {
|
||||
it('globalThis should equals with sandbox', () => {
|
||||
const { proxy } = new ProxySandbox('globalThis');
|
||||
expect(proxy.globalThis).toBe(proxy);
|
||||
});
|
||||
|
||||
test('allow window.top & window.parent to escape sandbox while in iframe', () => {
|
||||
it('allow window.top & window.parent to escape sandbox while in iframe', () => {
|
||||
// change window.parent to cheat ProxySandbox is in iframe
|
||||
Object.defineProperty(window, 'parent', { value: 'parent' });
|
||||
Object.defineProperty(window, 'top', { value: 'top' });
|
||||
|
|
@ -86,7 +86,7 @@ test('allow window.top & window.parent to escape sandbox while in iframe', () =>
|
|||
expect(proxy.parent).toBe('parent');
|
||||
});
|
||||
|
||||
test('eval should never be represented', () => {
|
||||
it('eval should never be represented', () => {
|
||||
const { proxy } = new ProxySandbox('eval-test');
|
||||
// @ts-ignore
|
||||
window.proxy = proxy;
|
||||
|
|
@ -102,7 +102,7 @@ test('eval should never be represented', () => {
|
|||
expect(window.testEval).toBeUndefined();
|
||||
});
|
||||
|
||||
test('hasOwnProperty should works well', () => {
|
||||
it('hasOwnProperty should works well', () => {
|
||||
const { proxy } = new ProxySandbox('unit-test');
|
||||
|
||||
proxy.testName = 'kuitos';
|
||||
|
|
@ -120,7 +120,7 @@ test('hasOwnProperty should works well', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('descriptor of non-configurable and non-enumerable property existed in raw window should be the same after modified in sandbox', () => {
|
||||
it('descriptor of non-configurable and non-enumerable property existed in raw window should be the same after modified in sandbox', () => {
|
||||
Object.defineProperty(window, 'nonConfigurableProp', { configurable: false, writable: true });
|
||||
// eslint-disable-next-line getter-return
|
||||
Object.defineProperty(window, 'nonConfigurablePropWithAccessor', {
|
||||
|
|
@ -161,14 +161,14 @@ test('descriptor of non-configurable and non-enumerable property existed in raw
|
|||
});
|
||||
});
|
||||
|
||||
test('A property cannot be reported as non-configurable, if it does not exists as an own property of the target object', () => {
|
||||
it('A property cannot be reported as non-configurable, if it does not exists as an own property of the target object', () => {
|
||||
const { proxy } = new ProxySandbox('non-configurable');
|
||||
Object.defineProperty(window, 'nonConfigurablePropAfterSandboxCreated', { value: 'test', configurable: false });
|
||||
const descriptor = Object.getOwnPropertyDescriptor(proxy, 'nonConfigurablePropAfterSandboxCreated');
|
||||
expect(descriptor?.configurable).toBeTruthy();
|
||||
});
|
||||
|
||||
test('property added by Object.defineProperty should works as expect', () => {
|
||||
it('property added by Object.defineProperty should works as expect', () => {
|
||||
const { proxy } = new ProxySandbox('object-define-property-test');
|
||||
|
||||
let v: any;
|
||||
|
|
@ -197,7 +197,7 @@ test('property added by Object.defineProperty should works as expect', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('defineProperty should added to the target where its descriptor from', () => {
|
||||
it('defineProperty should added to the target where its descriptor from', () => {
|
||||
Object.defineProperty(window, 'propertyInNativeWindow', {
|
||||
get(this: any) {
|
||||
// distinguish it from internal target or raw window with property length
|
||||
|
|
@ -219,7 +219,7 @@ test('defineProperty should added to the target where its descriptor from', () =
|
|||
expect(proxy.propertyInNativeWindow).toBe('ifAccessByInternalTargetWillCauseIllegalInvocation');
|
||||
});
|
||||
|
||||
test('hasOwnProperty should always returns same reference', () => {
|
||||
it('hasOwnProperty should always returns same reference', () => {
|
||||
const proxy = new ProxySandbox('hasOwnProperty-test').proxy as any;
|
||||
proxy.testA = {};
|
||||
proxy.testB = {};
|
||||
|
|
@ -227,7 +227,7 @@ test('hasOwnProperty should always returns same reference', () => {
|
|||
expect(proxy.testA.hasOwnProperty).toBe(proxy.testB.hasOwnProperty);
|
||||
});
|
||||
|
||||
test('document and eval accessing should modify the attachDocProxySymbol value every time', () => {
|
||||
it('document and eval accessing should modify the attachDocProxySymbol value every time', () => {
|
||||
const proxy1 = new ProxySandbox('doc-access-test1').proxy;
|
||||
const proxy2 = new ProxySandbox('doc-access-test2').proxy;
|
||||
const proxy3 = new ProxySandbox('eval-access-test1').proxy;
|
||||
|
|
@ -255,7 +255,7 @@ test('document and eval accessing should modify the attachDocProxySymbol value e
|
|||
expect(eval1).toBe(eval);
|
||||
});
|
||||
|
||||
test('document attachDocProxySymbol mark should be remove before next task', (done) => {
|
||||
it('document attachDocProxySymbol mark should be remove before next task', (done) => {
|
||||
const { proxy } = new ProxySandbox('doc-symbol');
|
||||
// just access
|
||||
// @ts-ignore
|
||||
|
|
@ -270,7 +270,7 @@ test('document attachDocProxySymbol mark should be remove before next task', (do
|
|||
});
|
||||
});
|
||||
|
||||
test('document should work well with MutationObserver', (done) => {
|
||||
it('document should work well with MutationObserver', (done) => {
|
||||
const docProxy = new ProxySandbox('doc').proxy;
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
|
|
@ -290,7 +290,7 @@ test('document should work well with MutationObserver', (done) => {
|
|||
docProxy.document.body.innerHTML = '<div></div>';
|
||||
});
|
||||
|
||||
test('bounded function should not be rebounded', () => {
|
||||
it('bounded function should not be rebounded', () => {
|
||||
const proxy = new ProxySandbox('bound-fn-test').proxy as any;
|
||||
const fn = () => {};
|
||||
const boundedFn = fn.bind(null);
|
||||
|
|
@ -302,25 +302,32 @@ 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;
|
||||
it('frozen property should not be overwrite', () => {
|
||||
const globalContext = window;
|
||||
const fn = () => {};
|
||||
Object.defineProperty(globalContext, 'frozenProp', {
|
||||
value: fn,
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
});
|
||||
const proxy = new ProxySandbox('bound-fn-test', globalContext).proxy as any;
|
||||
|
||||
expect(proxy.frozenProp).toBe(fn);
|
||||
|
||||
proxy.normalField = 'normalFieldValue';
|
||||
|
||||
Object.defineProperties(proxy, {
|
||||
readOnlyField: {
|
||||
value: 'readOnlyFieldValue',
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
},
|
||||
Object.defineProperty(proxy, 'frozenPropInProxy', {
|
||||
value: fn,
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
});
|
||||
|
||||
expect(isPropertyReadonly(proxy, 'normalField')).toBeFalsy();
|
||||
expect(isPropertyReadonly(proxy, 'readOnlyField')).toBeTruthy();
|
||||
expect(proxy.normalField).toBe('normalFieldValue');
|
||||
expect(proxy.frozenPropInProxy).toBe(fn);
|
||||
});
|
||||
|
||||
test('the prototype should be kept while we create a function with prototype on proxy', () => {
|
||||
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() {}
|
||||
|
|
@ -330,7 +337,7 @@ test('the prototype should be kept while we create a function with prototype on
|
|||
expect(proxy.fn.prototype).toBe(test.prototype);
|
||||
});
|
||||
|
||||
test('some native window property was defined with getter in safari and firefox, and they will check the caller source', () => {
|
||||
it('some native window property was defined with getter in safari and firefox, and they will check the caller source', () => {
|
||||
Object.defineProperty(window, 'mockSafariGetterProperty', {
|
||||
get(this: Window) {
|
||||
// distinguish it from internal target or raw window with property length
|
||||
|
|
@ -351,7 +358,7 @@ test('some native window property was defined with getter in safari and firefox,
|
|||
expect(proxy.mockSafariGetterProperty).toBe('getterPropertyInSafariWindow');
|
||||
});
|
||||
|
||||
test('falsy values should return as expected', () => {
|
||||
it('falsy values should return as expected', () => {
|
||||
const { proxy } = new ProxySandbox('falsy-value-test');
|
||||
proxy.falsevar = false;
|
||||
proxy.nullvar = null;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* @since 2020-04-13
|
||||
*/
|
||||
|
||||
import { isBoundedFunction, isCallable, isConstructable, isPropertyReadonly } from '../utils';
|
||||
import { isBoundedFunction, isCallable, isConstructable } from '../utils';
|
||||
|
||||
type AppInstance = { name: string; window: WindowProxy };
|
||||
let currentRunningApp: AppInstance | null = null;
|
||||
|
|
@ -21,14 +21,15 @@ export function setCurrentRunningApp(appInstance: { name: string; window: Window
|
|||
}
|
||||
|
||||
const functionBoundedValueMap = new WeakMap<CallableFunction, CallableFunction>();
|
||||
export function getTargetValue(target: any, value: any, p?: any): any {
|
||||
|
||||
export function getTargetValue(target: any, value: any): any {
|
||||
/*
|
||||
仅绑定 isCallable && !isPropertyReadonly && !isBoundedFunction && !isConstructable 的函数对象,如 window.console、window.atob 这类,不然微应用中调用时会抛出 Illegal invocation 异常
|
||||
仅绑定 isCallable && !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) && !isPropertyReadonly(target, p) && !isBoundedFunction(value) && !isConstructable(value)) {
|
||||
if (isCallable(value) && !isBoundedFunction(value) && !isConstructable(value)) {
|
||||
const cachedBoundFunction = functionBoundedValueMap.get(value);
|
||||
if (cachedBoundFunction) {
|
||||
return cachedBoundFunction;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
import type { SandBox } from '../interfaces';
|
||||
import { SandBoxType } from '../interfaces';
|
||||
import { nativeGlobal, nextTask } from '../utils';
|
||||
import { isPropertyFrozen, nativeGlobal, nextTask } from '../utils';
|
||||
import { getCurrentRunningApp, getTargetValue, setCurrentRunningApp, unscopedGlobals } from './common';
|
||||
|
||||
type SymbolTarget = 'target' | 'globalContext';
|
||||
|
|
@ -65,7 +65,7 @@ function createFakeWindow(globalContext: Window) {
|
|||
/*
|
||||
copy the non-configurable property of global to fakeWindow
|
||||
see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor
|
||||
> A property cannot be reported as non-configurable, if it does not exists as an own property of the target object or if it exists as a configurable own property of the target object.
|
||||
> A property cannot be reported as non-configurable, if it does not exist as an own property of the target object or if it exists as a configurable own property of the target object.
|
||||
*/
|
||||
Object.getOwnPropertyNames(globalContext)
|
||||
.filter((p) => {
|
||||
|
|
@ -250,11 +250,14 @@ export default class ProxySandbox implements SandBox {
|
|||
return eval;
|
||||
}
|
||||
|
||||
const value = propertiesWithGetter.has(p)
|
||||
? (globalContext as any)[p]
|
||||
: p in target
|
||||
? (target as any)[p]
|
||||
: (globalContext as any)[p];
|
||||
const actualTarget = propertiesWithGetter.has(p) ? globalContext : p in target ? target : globalContext;
|
||||
const value = actualTarget[p];
|
||||
|
||||
// frozen value should return directly, see https://github.com/umijs/qiankun/issues/2015
|
||||
if (isPropertyFrozen(actualTarget, 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, {});
|
||||
|
|
@ -262,7 +265,7 @@ export default class ProxySandbox implements SandBox {
|
|||
proxyFetch('https://qiankun.com');
|
||||
*/
|
||||
const boundTarget = useNativeWindowForBindingsProps.get(p) ? nativeGlobal : globalContext;
|
||||
return getTargetValue(boundTarget, value, p);
|
||||
return getTargetValue(boundTarget, value);
|
||||
},
|
||||
|
||||
// trap in operator
|
||||
|
|
|
|||
19
src/utils.ts
19
src/utils.ts
|
|
@ -76,7 +76,7 @@ export function isConstructable(fn: () => any | FunctionConstructor) {
|
|||
*/
|
||||
const naughtySafari = typeof document.all === 'function' && typeof document.all === 'undefined';
|
||||
const callableFnCacheMap = new WeakMap<CallableFunction, boolean>();
|
||||
export const isCallable = (fn: any) => {
|
||||
export function isCallable(fn: any) {
|
||||
if (callableFnCacheMap.has(fn)) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -86,7 +86,7 @@ export const isCallable = (fn: any) => {
|
|||
callableFnCacheMap.set(fn, callable);
|
||||
}
|
||||
return callable;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* isPropertyReadonly
|
||||
|
|
@ -94,28 +94,29 @@ export const isCallable = (fn: any) => {
|
|||
* @param p
|
||||
* @returns boolean
|
||||
*/
|
||||
const propertyReadonlyCacheMap = new WeakMap<any, Record<PropertyKey, boolean>>();
|
||||
export function isPropertyReadonly(target: any, p?: PropertyKey): boolean {
|
||||
const frozenPropertyCacheMap = new WeakMap<any, Record<PropertyKey, boolean>>();
|
||||
export function isPropertyFrozen(target: any, p?: PropertyKey): boolean {
|
||||
if (!target || !p) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const targetPropertiesFromCache = propertyReadonlyCacheMap.get(target) || {};
|
||||
const targetPropertiesFromCache = frozenPropertyCacheMap.get(target) || {};
|
||||
|
||||
if (targetPropertiesFromCache[p]) {
|
||||
return targetPropertiesFromCache[p];
|
||||
}
|
||||
|
||||
const propertyDescriptor = Object.getOwnPropertyDescriptor(target, p);
|
||||
const readonly = Boolean(
|
||||
const frozen = Boolean(
|
||||
propertyDescriptor &&
|
||||
propertyDescriptor.configurable === false &&
|
||||
(propertyDescriptor.writable === false || (propertyDescriptor.get && !propertyDescriptor.set)),
|
||||
);
|
||||
|
||||
targetPropertiesFromCache[p] = readonly;
|
||||
propertyReadonlyCacheMap.set(target, targetPropertiesFromCache);
|
||||
targetPropertiesFromCache[p] = frozen;
|
||||
frozenPropertyCacheMap.set(target, targetPropertiesFromCache);
|
||||
|
||||
return readonly;
|
||||
return frozen;
|
||||
}
|
||||
|
||||
const boundedMap = new WeakMap<CallableFunction, boolean>();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user