♻️ refactor the global whitelist recovery logic and fix the unit test (#2291)

This commit is contained in:
Kuitos 2022-10-07 16:42:53 +08:00 committed by GitHub
parent c14ae3f083
commit b5ad5a4e43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 54 deletions

View File

@ -409,34 +409,50 @@ it('native window function calling should always be bound with window', () => {
expect(proxy.nativeWindowFunction()).toBe('success');
});
it('should restore window properties (primitive values) that in whitelisted variables', () => {
const original = {
iframeReady: () => {},
};
window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__ = original;
describe('variables in whitelist', () => {
it('should restore window properties (primitive values) that in whitelisted variables', () => {
const original = {
iframeReady: function t1() {},
};
window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__ = original;
const sandbox = new ProxySandbox('whitelist-variables');
const { proxy } = sandbox;
sandbox.active();
proxy.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__ = undefined;
sandbox.inactive();
proxy.expect(window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__).toBe(original);
});
it('should restore window properties (object descriptors) that in whitelisted variables', () => {
const original = {
iframeReady: () => {},
};
Object.defineProperty(window, '__REACT_ERROR_OVERLAY_GLOBAL_HOOK__', {
value: original,
const sandbox = new ProxySandbox('whitelist-variables');
const { proxy } = sandbox;
sandbox.active();
proxy.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__ = undefined;
expect(window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__).toBe(undefined);
sandbox.inactive();
expect(window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__).toBe(original);
});
const sandbox = new ProxySandbox('whitelist-variables');
const { proxy } = sandbox;
sandbox.active();
proxy.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__ = {};
sandbox.inactive();
proxy.expect(window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__).toBe(original);
it('should restore window properties (object descriptors) that in whitelisted variables', () => {
const original = {
iframeReady: function t2() {},
};
Object.defineProperty(window, '__REACT_ERROR_OVERLAY_GLOBAL_HOOK__', {
value: original,
writable: true,
});
const sandbox = new ProxySandbox('whitelist-variables');
const { proxy } = sandbox;
sandbox.active();
proxy.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__ = {};
expect(window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__).toEqual({});
sandbox.inactive();
expect(window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__).toBe(original);
});
it('should delete global context while it is undefined before sandbox start', () => {
delete window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__;
const sandbox = new ProxySandbox('whitelist-variables');
const { proxy } = sandbox;
sandbox.active();
proxy.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__ = {};
expect(window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__).toEqual({});
sandbox.inactive();
expect('__REACT_ERROR_OVERLAY_GLOBAL_HOOK__' in window).toBeFalsy();
});
});
describe('should work with nest sandbox', () => {

View File

@ -26,7 +26,7 @@ function uniq(array: Array<string | symbol>) {
const rawObjectDefineProperty = Object.defineProperty;
const variableWhiteListInDev =
process.env.NODE_ENV === 'development' || window.__QIANKUN_DEVELOPMENT__
process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development' || window.__QIANKUN_DEVELOPMENT__
? [
// for react hot reload
// see https://github.com/facebook/create-react-app/blob/66bf7dfc43350249e2f09d138a20840dae8a0a4a/packages/react-error-overlay/src/index.js#L180
@ -34,7 +34,7 @@ const variableWhiteListInDev =
]
: [];
// who could escape the sandbox
const variableWhiteList: PropertyKey[] = [
const globalVariableWhiteList: string[] = [
// FIXME System.js used a indirect call with eval, which would make it scope escape to global
// To make System.js works well, we write it back to global window temporary
// see https://github.com/systemjs/systemjs/blob/457f5b7e8af6bd120a279540477552a07d5de086/src/evaluate.js#L106
@ -132,6 +132,9 @@ export default class ProxySandbox implements SandBox {
sandboxRunning = true;
latestSetProp: PropertyKey | null = null;
// the descriptor of global variables in whitelist before it been modified
globalWhitelistPrevDescriptor: { [p in typeof globalVariableWhiteList[number]]: PropertyDescriptor | undefined } = {};
active() {
if (!this.sandboxRunning) activeSandboxCount++;
this.sandboxRunning = true;
@ -144,11 +147,15 @@ export default class ProxySandbox implements SandBox {
]);
}
if (--activeSandboxCount === 0) {
variableWhiteList.forEach((p) => {
const descriptor = this.globalWhiteList[p];
if (descriptor && descriptor.writable) {
if (process.env.NODE_ENV === 'test' || --activeSandboxCount === 0) {
// reset the global value to the prev value
globalVariableWhiteList.forEach((p) => {
const descriptor = this.globalWhitelistPrevDescriptor[p];
if (descriptor) {
Object.defineProperty(this.globalContext, p, descriptor);
} else {
// @ts-ignore
delete this.globalContext[p];
}
});
}
@ -157,16 +164,10 @@ export default class ProxySandbox implements SandBox {
}
globalContext: typeof window;
/** the whitelisted original global variables */
globalWhiteList: Record<PropertyKey, PropertyDescriptor | undefined>;
constructor(name: string, globalContext = window) {
this.name = name;
this.globalContext = globalContext;
this.globalWhiteList = variableWhiteList.reduce((acc, key) => {
acc[key] = Object.getOwnPropertyDescriptor(globalContext, key);
return acc;
}, {} as ProxySandbox['globalWhiteList']);
this.type = SandBoxType.Proxy;
const { updatedValueSet } = this;
@ -179,24 +180,18 @@ export default class ProxySandbox implements SandBox {
set: (target: FakeWindow, p: PropertyKey, value: any): boolean => {
if (this.sandboxRunning) {
this.registerRunningApp(name, proxy);
// We must kept its description while the property existed in globalContext before
// We must keep its description while the property existed in globalContext before
if (!target.hasOwnProperty(p) && globalContext.hasOwnProperty(p)) {
const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);
const { writable, configurable, enumerable } = descriptor!;
if (writable) {
Object.defineProperty(target, p, {
configurable,
enumerable,
writable,
value,
});
}
Object.defineProperty(target, p, { configurable, enumerable, writable, value });
} else {
// @ts-ignore
target[p] = value;
}
if (variableWhiteList.indexOf(p) !== -1) {
// sync the property to globalContext
if (typeof p === 'string' && globalVariableWhiteList.indexOf(p) !== -1) {
this.globalWhitelistPrevDescriptor[p] = Object.getOwnPropertyDescriptor(globalContext, p);
// @ts-ignore
globalContext[p] = value;
}

View File

@ -88,12 +88,6 @@ export function isCallable(fn: any) {
return callable;
}
/**
* isPropertyReadonly
* @param target
* @param p
* @returns boolean
*/
const frozenPropertyCacheMap = new WeakMap<any, Record<PropertyKey, boolean>>();
export function isPropertyFrozen(target: any, p?: PropertyKey): boolean {
if (!target || !p) {