跳到主要内容

ProxySandbox

2024年03月21日
柏拉文
越努力,越幸运

一、认识


代理沙箱 ProxySandbox 主要是通过为每个沙盒创建一个 fakeWindow,然后为这个fakeWindow设置代理,通过代理来访问这个fakeWindow

二、特点


代理了一个全新的对象,这个对象是复制的 window 对象的一部分不可配置属性,所有的更改都是基于这个 fakeWindow 对象,从而保证多个实例之间属性互不影响

三、实现


3.1 思路

变量如下:

  • proxy

  • isRunning

  • fakeWindow: 基于 window 对象创建一个新对象,复制 window 对象中不可配置的属性,(例如 windowselftop 等),这些不可配置的属性在沙箱环境中经过特别处理,将 configurable 设置为 true,如果没有 get 方法,将 writable 设置为 true需要复制不可配置属性的原因: ProxySandbox 中的 Proxy 对象利用 fakeWindow 来实现更高层次的隔离和控制。沙箱的 getsethasdeleteProperty 操作都基于 fakeWindow 和全局 window 对象之间的代理关系, 从而提供了对沙箱环境的进一步保护和控制。由于 fakeWindow 只包含 window 中不可配置的属性,而不会影响全局 window 对象中的其他属性,这有助于确保沙箱环境的行为更接近真实的浏览器环境, 而且避免了在沙箱中执行的代码意外地影响全局环境。

逻辑如下:

  • createFakeWindow(globalContext, speedy): 基于 window 对象创建一个新对象,复制 window 对象中不可配置的属性,(例如 windowselftop 等),这些不可配置的属性在沙箱环境中经过特别处理,将 configurable 设置为 true,如果没有 get 方法,将 writable 设置为 true

  • constructor: 初始化沙箱实例。创建沙箱环境,设置代理对象。通过 proxy 劫持了 fakeWindowsetgethasdeleteProperty 方法。

  • active(): 激活沙箱。使沙箱进入运行状态。

  • inactive(): 停用沙箱。使沙箱停止运行,并恢复全局变量的原始状态。

  • set: 拦截属性设置操作。

  • get: 拦截属性获取操作。 在沙箱中正确返回属性值,处理特殊属性。

  • has: 拦截 in 操作符。 在沙箱中正确判断属性是否存在。

  • deleteProperty: 拦截删除属性操作。在沙箱中正确删除属性。

3.2 实现

简单实现

function createFakeWindow(globalContext) {
const fakeWindow = {};

Object.getOwnPropertyNames(globalContext)
.filter((p) => {
const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);
return !descriptor?.configurable;
})
.forEach((p) => {
const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);
if (descriptor) {
const hasGetter = Object.prototype.hasOwnProperty.call(
descriptor,
"get"
);

if (p === "top" || p === "parent" || p === "self" || p === "window") {
descriptor.configurable = true;
if (!hasGetter) {
descriptor.writable = true;
}
}

Object.defineProperty(fakeWindow, p, Object.freeze(descriptor));
}
});

return fakeWindow;
}

class ProxySandbox {
constructor() {
this.fakeWindow = createFakeWindow(window);
this.isRunning = false;
this.proxy = new Proxy(this.fakeWindow, {
get: (target, key) => {
if (key === "window" || key === "self" || key === "globalThis") {
return this.proxy;
}
return target[key] || window[key];
},
set: (target, key, value) => {
if (this.isRunning) {
target[key] = value;
return true;
}
return false;
},
has: (target, key) => {
return key in target || key in window;
},
deleteProperty: (target, key) => {
if (this.isRunning) {
delete target[key];
return true;
}
return false;
},
});
}

active() {
this.isRunning = true;
}

inactive() {
this.isRunning = false;
}
}

3.3 测试

const sandBox1 = new ProxySandbox("sandBox1");
const sandBox2 = new ProxySandbox("sandBox2");

sandBox1.active();
sandBox1.proxy.a = "哈哈";
console.log("sandBox1.proxy.a", sandBox1.proxy.a);
console.log("sandBox2.proxy.a", sandBox2.proxy.a);

sandBox2.active();
sandBox2.proxy.a = "哈哈修改";
console.log("sandBox1.proxy.a", sandBox1.proxy.a);
console.log("sandBox2.proxy.a", sandBox2.proxy.a);

参考资料


微前端框架 Qiankun 沙箱原理

【微前端】在造一个微前端轮子之前,你需要知道这些~