Skip to content

Commit 647de2e

Browse files
committed
🐛 修复沙盒隔离问题 #189
1 parent 4dba268 commit 647de2e

8 files changed

Lines changed: 99 additions & 96 deletions

File tree

docs/架构设计.md

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
[TOC]
22

33
# 脚本猫架构设计
4-
> 本文档用于记录脚本猫的主要架构设计
5-
64

5+
> 本文档用于记录脚本猫的主要架构设计
76
87
## 目录结构
98

@@ -18,26 +17,19 @@ TODO
1817
- [scripts](../src/app/repo/script.ts) 用户脚本
1918
- [subscribe](../src/app/repo/subscribe.ts) 订阅脚本
2019

21-
22-
2320
## 代码架构
2421

25-
扩展中有很多操作可能是在页面中,但是实际上的操作生效是需要在background进行,甚至是有更复杂的逻辑,例如需要广播,这时候需要对这些操作逻辑进行解耦。这里我们通过[通讯机制](./通讯机制.md)去做,使用通讯机制实现一个事件,在前台页面中调用,后台页面进行操作。我们将这些代码放在[src/app/service](../src/app/service)文件夹中,每一个服务分为三个文件:
22+
扩展中有很多操作可能是在页面中,但是实际上的操作生效是需要在 background 进行,甚至是有更复杂的逻辑,例如需要广播,这时候需要对这些操作逻辑进行解耦。这里我们通过[通讯机制](./通讯机制.md)去做,使用通讯机制实现一个事件,在前台页面中调用,后台页面进行操作。我们将这些代码放在[src/app/service](../src/app/service)文件夹中,每一个服务分为三个文件:
2623

2724
- controller 控制器,用于前台页面发起操作请求,例如脚本的安装/脚本删除,也可以将页面相关的业务代码写在此处
28-
- manager 管理器,后台业务代码处理,监听事件处理,例如监听打开`.user.js`页面,打开一个新页面匹配match注入脚本
29-
- event 事件处理,依赖注入manager,例如收到脚本安装的事件后将脚本数据写入数据库、更新match缓存,主要是将事件代码与后台控制代码解耦
30-
31-
25+
- manager 管理器,后台业务代码处理,监听事件处理,例如监听打开`.user.js`页面,打开一个新页面匹配 match 注入脚本
26+
- event 事件处理,依赖注入 manager,例如收到脚本安装的事件后将脚本数据写入数据库、更新 match 缓存,主要是将事件代码与后台控制代码解耦
3227

3328
**hook**
3429

35-
> 取这个名字更多的是想和上面的event区分开来,另外hook也允许拦截操作
36-
37-
使用hook将各个操作进行解耦,例如脚本数据存储时就会涉及到:脚本执行、脚本同步、脚本状态变更通知前端页面。
38-
39-
30+
> 取这个名字更多的是想和上面的 event 区分开来,另外 hook 也允许拦截操作
4031
32+
使用 hook 将各个操作进行解耦,例如脚本数据存储时就会涉及到:脚本执行、脚本同步、脚本状态变更通知前端页面。
4133

4234
## 脚本
4335

@@ -47,21 +39,17 @@ TODO
4739

4840
#### 唯一性判断
4941

50-
唯一标志有3个:uuid、url、name+namespace。
51-
42+
唯一标志有 3 个:uuid、url、name+namespace。
5243

5344
### 定时脚本
5445

5546
#### 定时器
5647

57-
定时器使用crontab实现,增加了一个once的概念,实现x段时间内最多执行一次。定时器基于[`cron`](https://www.npmjs.com/package/cron)库实现
58-
59-
60-
48+
定时器使用 crontab 实现,增加了一个 once 的概念,实现 x 段时间内最多执行一次。定时器基于[`cron`](https://www.npmjs.com/package/cron)库实现
6149

6250
## 日志
6351

64-
为了记录扩展的运行状态与问题排查,需要实现一个日志组件,看了很多开源日志库,但大多数只适用于nodejs无法用于浏览器扩展。自己简单实现一个日志组件,实现以下功能 :
52+
为了记录扩展的运行状态与问题排查,需要实现一个日志组件,看了很多开源日志库,但大多数只适用于 nodejs 无法用于浏览器扩展。自己简单实现一个日志组件,实现以下功能 :
6553

6654
- 日志分级:可以自行控制日志的输出级别,打印到控制台,记录到数据库
6755
- 日志字段:日志可以标记字段,用字段来进行分类查询
@@ -78,7 +66,7 @@ TODO
7866

7967
**Write**
8068

81-
日志写入接口,内置了`DBWriter``MessageWriter`DBWriter用于indexedDB落库,MessageWriter用于content/sandbox页通过通讯机制去落库
69+
日志写入接口,内置了`DBWriter``MessageWriter`DBWriter 用于 indexedDB 落库,MessageWriter 用于 content/sandbox 页通过通讯机制去落库
8270

8371
```ts
8472
// 初始化日志组件
@@ -95,14 +83,33 @@ LoggerCore.getInstance().logger({ env: "background" }).info("background start");
9583

9684
为了兼容油猴脚本,必须引入测试,主要针对油猴运行时的沙盒进行测试,以保证每次修改不会破坏兼容性。使用[`jest`](https://jestjs.io/zh-Hans/)作为测试框架,编写时也需要考虑代码的一个可测试性。单元测试文件与代码放在同级目录下(\*.ts/\*.test.ts),不另外开`tests`文件夹。
9785

86+
## uuid 生成逻辑与脚本安装
9887

99-
## uuid生成逻辑与脚本安装
88+
### uuid 生成逻辑
10089

101-
### uuid生成逻辑
102-
随机生成uuid
90+
随机生成 uuid
10391

10492
### 脚本安装
105-
首先通过name+namespace搜索是否有同名脚本, 没有则生成随机uuid安装, 有则使用同名脚本的uuid进行安装
93+
94+
首先通过 name+namespace 搜索是否有同名脚本, 没有则生成随机 uuid 安装, 有则使用同名脚本的 uuid 进行安装
10695

10796
### 脚本更新
108-
传递脚本uuid, 使用uuid进行更新
97+
98+
传递脚本 uuid, 使用 uuid 进行更新
99+
100+
## 沙盒隔离
101+
102+
创建一个沙盒,隔离页面与脚本的对象
103+
104+
### typeof function
105+
106+
#### 普通 function
107+
108+
例如:setTimeout、setInterval、fetch 等,需要将函数的 this 指向 window
109+
110+
#### Map、Function、Array 等
111+
112+
这些是拥有 new 和静态方法的对象
113+
114+
#### 处理方式
115+

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
1+
/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */
22
module.exports = {
33
preset: "ts-jest",
44
testEnvironment: "jsdom",

src/app/const.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const ExtVersion = "0.12.0";
1+
export const ExtVersion = "0.13.0-beta";
22

33
export const ExtServer = "https://ext.scriptcat.org/";
44

src/linter.worker.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ self.addEventListener("message", (event) => {
8181
text: err.fix.text,
8282
};
8383
}
84-
console.log(err);
8584
return {
8685
code: {
8786
value: err.ruleId || "",

src/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 2,
33
"name": "ScriptCat",
4-
"version": "0.12.0",
4+
"version": "0.13.0.1010",
55
"author": "CodFrm",
66
"description": "脚本猫,一个用户脚本管理器,支持后台脚本、定时脚本、页面脚本,可编写脚本每天帮你自动处理事务.",
77
"options_ui": {

src/pkg/utils/monaco-editor.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,6 @@ export default function registerEditor() {
9292
const fix = eslintFix.get(
9393
`${code}|${val.startLineNumber}|${val.endLineNumber}|${val.startColumn}|${val.endColumn}`
9494
);
95-
console.log(
96-
fix,
97-
`${code}|${val.startLineNumber}|${val.endLineNumber}|${val.startColumn}|${val.endColumn}`
98-
);
9995
if (fix) {
10096
const edit: languages.IWorkspaceTextEdit = {
10197
resource: model.uri,

src/runtime/content/utils.test.ts

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,57 @@
1-
import { init, proxyContext } from "./utils";
1+
import { init, proxyContext, writables } from "./utils";
22

3-
describe('proxy context', () => {
4-
const context: any = {};
5-
const global: any = {
6-
gbok: 'gbok',
7-
onload: null,
8-
eval: () => {
9-
console.log('eval');
10-
},
11-
};
12-
init.set('onload', true);
13-
init.set('gbok', true);
14-
const _this = proxyContext(global, context);
3+
describe("proxy context", () => {
4+
const context: any = {};
5+
const global: any = {
6+
gbok: "gbok",
7+
onload: null,
8+
eval: () => {
9+
console.log("eval");
10+
},
11+
};
12+
init.set("onload", true);
13+
init.set("gbok", true);
14+
const _this = proxyContext(global, context);
1515

16-
it('set contenxt', () => {
17-
_this['md5'] = 'ok';
18-
expect(_this['md5']).toEqual('ok');
19-
expect(global['md5']).toEqual(undefined);
20-
});
16+
it("set contenxt", () => {
17+
_this["md5"] = "ok";
18+
expect(_this["md5"]).toEqual("ok");
19+
expect(global["md5"]).toEqual(undefined);
20+
});
2121

22-
it('set window null', () => {
23-
_this['onload'] = 'ok';
24-
expect(_this['onload']).toEqual('ok');
25-
expect(context['onload']).toEqual(undefined);
26-
expect(global['onload']).toEqual('ok');
27-
});
22+
it("set window null", () => {
23+
_this["onload"] = "ok";
24+
expect(_this["onload"]).toEqual("ok");
25+
expect(context["onload"]).toEqual(undefined);
26+
expect(global["onload"]).toEqual("ok");
27+
});
2828

29-
it('update', () => {
30-
_this['okk'] = 'ok';
31-
expect(_this['okk']).toEqual('ok');
32-
expect(global['okk']).toEqual(undefined);
33-
_this['okk'] = 'ok2';
34-
expect(_this['okk']).toEqual('ok2');
35-
expect(global['okk']).toEqual(undefined);
36-
});
29+
it("update", () => {
30+
_this["okk"] = "ok";
31+
expect(_this["okk"]).toEqual("ok");
32+
expect(global["okk"]).toEqual(undefined);
33+
_this["okk"] = "ok2";
34+
expect(_this["okk"]).toEqual("ok2");
35+
expect(global["okk"]).toEqual(undefined);
36+
});
3737

38-
it('访问global的对象', () => {
39-
expect(_this['gbok']).toEqual('gbok');
40-
});
38+
it("访问global的对象", () => {
39+
expect(_this["gbok"]).toEqual("gbok");
40+
});
41+
});
42+
43+
describe("兼容问题", () => {
44+
console.log("ok");
45+
const _this = proxyContext({}, {});
46+
// https://github.com/xcanwin/KeepChatGPT 环境隔离得不够干净导致的
47+
it("Uncaught TypeError: Illegal invocation #189", () => {
48+
return new Promise((resolve) => {
49+
console.log(_this.setTimeout.prototype);
50+
_this.setTimeout(resolve, 100);
51+
});
52+
});
53+
// AC-baidu-重定向优化百度搜狗谷歌必应搜索_favicon_双列
54+
it("TypeError: Object.freeze is not a function #116", () => {
55+
expect(() => _this.Object.freeze({})).not.toThrow();
56+
});
4157
});

src/runtime/content/utils.ts

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export function createContext(
111111
return <GMApi>context;
112112
}
113113

114-
const writables: { [key: string]: any } = {
114+
export const writables: { [key: string]: any } = {
115115
addEventListener: global.addEventListener,
116116
removeEventListener: global.removeEventListener,
117117
dispatchEvent: global.dispatchEvent,
@@ -125,7 +125,16 @@ const descs = Object.getOwnPropertyDescriptors(global);
125125
Object.keys(descs).forEach((key) => {
126126
const desc = descs[key];
127127
if (desc && desc.writable && !writables[key]) {
128-
writables[key] = desc.value;
128+
if (typeof desc.value === "function") {
129+
// 判断是否需要bind,例如Object、Function这些就不需要bind
130+
if (desc.value.prototype) {
131+
writables[key] = desc.value;
132+
} else {
133+
writables[key] = desc.value.bind(global);
134+
}
135+
} else {
136+
writables[key] = desc.value;
137+
}
129138
} else {
130139
init.set(key, true);
131140
}
@@ -185,21 +194,9 @@ export function proxyContext(global: any, context: any) {
185194
return context[name];
186195
}
187196
if (special[name] !== undefined) {
188-
if (
189-
typeof special[name] === "function" &&
190-
!(<{ prototype: any }>special[name]).prototype
191-
) {
192-
return (<{ bind: any }>special[name]).bind(global);
193-
}
194197
return special[name];
195198
}
196199
if (global[name] !== undefined) {
197-
if (
198-
typeof global[name] === "function" &&
199-
!(<{ prototype: any }>global[name]).prototype
200-
) {
201-
return (<{ bind: any }>global[name]).bind(global);
202-
}
203200
return global[name];
204201
}
205202
}
@@ -231,21 +228,9 @@ export function proxyContext(global: any, context: any) {
231228
return true;
232229
}
233230
if (special[name] !== undefined) {
234-
if (
235-
typeof special[name] === "function" &&
236-
!(<{ prototype: any }>special[name]).prototype
237-
) {
238-
return true;
239-
}
240231
return true;
241232
}
242233
if (global[name] !== undefined) {
243-
if (
244-
typeof global[name] === "function" &&
245-
!(<{ prototype: any }>global[name]).prototype
246-
) {
247-
return true;
248-
}
249234
return true;
250235
}
251236
}

0 commit comments

Comments
 (0)