Skip to content

Commit 3d3be2c

Browse files
cyfung1031CodFrm
andauthored
⚡️ GM优化 及其他更新 (#519)
* 使用静态方法作为实际实现 * 调整GMApi文件 * 实现alias * 修复别名问题 * 修复 GM_cookie 问题 * 🧪 添加GM cookie单元测试 * TreeShaking优化 * createStubCallable & defaultFn * 注释更新 * 简单修一下,无行为改变 * 沒有CAT.* * indexOf -> includes * 修订单元测试 * 补充单元测试 * 🧪 修改单元测试 * 修复lint * typo fix * typo fix * 修正 createStubCallable * 优化 * 修改单元测试 * 修改单元测试 * 🧪 补充单元测试 * 修正 createStubCallable * 简化设计 * 修正 * 补充单元测试 * typo * 修改单元测试 * 补充API定义 (`GM.cookie`) * 处理 gm cookie * 🧪 补充单元测试 --------- Co-authored-by: 王一之 <yz@ggnb.top>
1 parent 3d49376 commit 3d3be2c

File tree

26 files changed

+610
-459
lines changed

26 files changed

+610
-459
lines changed

example/gm_cookie.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// @author You
77
// @match https://bbs.tampermonkey.net.cn/
88
// @grant GM_cookie
9+
// @grant GM.cookie
910
// @connect example.com
1011
// ==/UserScript==
1112

@@ -40,3 +41,7 @@ GM_cookie("set", {
4041
})
4142
});
4243
});
44+
45+
console.log("async GM.cookie.list", await GM.cookie.list({
46+
domain: "example.com"
47+
}));

example/grant_none.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// @description try to take over the world!
66
// @author You
77
// @match https://bbs.tampermonkey.net.cn/
8+
// @grant none
89
// ==/UserScript==
910

1011
console.log("Grant None", this, GM_info);

example/sandbox_window.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// ==UserScript==
2+
// @name New Userscript
3+
// @namespace https://bbs.tampermonkey.net.cn/
4+
// @version 0.1.0
5+
// @description try to take over the world!
6+
// @author You
7+
// @match https://bbs.tampermonkey.net.cn/
8+
// @grant window.close
9+
// ==/UserScript==
10+
11+
window.close();

packages/filesystem/onedrive/onedrive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export default class OneDriveFileSystem implements FileSystem {
7373
request(url: string, config?: RequestInit, nothen?: boolean): Promise<Response | any> {
7474
config = config || {};
7575
const headers = <Headers>config.headers || new Headers();
76-
if (url.indexOf("uploadSession") === -1) {
76+
if (!url.includes("uploadSession")) {
7777
headers.append(`Authorization`, `Bearer ${this.accessToken}`);
7878
}
7979
config.headers = headers;

src/app/service/content/create_context.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { v4 as uuidv4 } from "uuid";
33
import type { Message } from "@Packages/message/types";
44
import EventEmitter from "eventemitter3";
55
import { GMContextApiGet } from "./gm_context";
6-
import { GM_Base } from "./gm_api";
6+
import { createGMBase } from "./gm_api";
77

88
// 构建沙盒上下文
99
export function createContext(
@@ -16,7 +16,7 @@ export function createContext(
1616
// 按照GMApi构建
1717
const valueChangeListener = new Map<number, { name: string; listener: GMTypes.ValueChangeListener }>();
1818
const EE: EventEmitter = new EventEmitter();
19-
const context = GM_Base.create({
19+
const context = createGMBase({
2020
prefix: envPrefix,
2121
message,
2222
scriptRes,
@@ -31,24 +31,16 @@ export function createContext(
3131
},
3232
grantSet: new Set(),
3333
});
34+
const grantedAPIs: { [key: string]: any } = {};
3435
const __methodInject__ = (grant: string): boolean => {
3536
const grantSet: Set<string> = context.grantSet;
3637
const s = GMContextApiGet(grant);
3738
if (!s) return false; // @grant 的定义未实作,略过 (返回 false 表示 @grant 不存在)
3839
if (grantSet.has(grant)) return true; // 重覆的@grant,略过 (返回 true 表示 @grant 存在)
3940
grantSet.add(grant);
40-
for (const t of s) {
41-
const fnKeyArray = t.fnKey.split(".");
42-
const m = fnKeyArray.length - 1;
43-
let g = context;
44-
for (let i = 0; i < m; i++) {
45-
const part = fnKeyArray[i];
46-
g = g[part] || (g[part] = {});
47-
}
48-
const finalPart = fnKeyArray[m];
49-
if (g[finalPart]) continue;
50-
g[finalPart] = t.api.bind(context);
51-
const depend = t?.param?.depend;
41+
for (const {fnKey, api, param} of s) {
42+
grantedAPIs[fnKey] = api.bind(context);
43+
const depend = param?.depend;
5244
if (depend) {
5345
for (const grant of depend) {
5446
__methodInject__(grant);
@@ -60,6 +52,18 @@ export function createContext(
6052
for (const grant of scriptGrants) {
6153
__methodInject__(grant);
6254
}
55+
// 兼容GM.Cookie.*
56+
for (const fnKey of Object.keys(grantedAPIs)) {
57+
const fnKeyArray = fnKey.split(".");
58+
const m = fnKeyArray.length;
59+
let g = context;
60+
let s = '';
61+
for (let i = 0; i < m; i++) {
62+
const part = fnKeyArray[i];
63+
s += `${(i ? '.' : '')}${part}`;
64+
g = g[part] || (g[part] = (grantedAPIs[s] || {}));
65+
}
66+
}
6367
context.unsafeWindow = window;
6468
return context;
6569
}

src/app/service/content/exec_script.test.ts

Lines changed: 118 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ExtVersion } from "@App/app/const";
55
import { initTestEnv } from "@Tests/utils";
66
import { describe, expect, it } from "vitest";
77
import type { GMInfoEnv } from "./types";
8-
import { ScriptLoadInfo } from "../service_worker/types";
8+
import type { ScriptLoadInfo } from "../service_worker/types";
99

1010
initTestEnv();
1111

@@ -62,7 +62,7 @@ describe("GM_info", () => {
6262
const ret = await sandboxExec.exec();
6363
expect(ret.GM_info.version).toEqual(ExtVersion);
6464
expect(ret.GM_info.script.version).toEqual("1.0.0");
65-
expect(ret._this).toEqual(sandboxExec.proxyContent);
65+
expect(ret._this).toEqual(sandboxExec.proxyContext);
6666
});
6767
});
6868

@@ -188,7 +188,7 @@ describe("none this", () => {
188188
describe("@grant GM", () => {
189189
it("GM_", async () => {
190190
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
191-
script.metadata.grant = ["GM_getValue", "GM_getTab", "GM_saveTab"];
191+
script.metadata.grant = ["GM_getValue", "GM_getTab", "GM_saveTab", "GM_cookie"];
192192
// @ts-ignore
193193
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
194194
script.code = `return {
@@ -198,21 +198,25 @@ describe("@grant GM", () => {
198198
GM_getValue: this.GM_getValue,
199199
GM_getTab: this.GM_getTab,
200200
GM_saveTab: this.GM_saveTab,
201+
GM_cookie: this.GM_cookie,
202+
["GM_cookie.list"]: this.GM_cookie.list,
203+
["GM.cookie"]: this.GM.cookie,
201204
}`;
202205
exec.scriptFunc = compileScript(compileScriptCode(script));
203206
const ret = await exec.exec();
204-
expect(ret).toEqual({
205-
"GM.getValue": undefined,
206-
"GM.getTab": undefined,
207-
"GM.setTab": undefined,
208-
GM_getValue: expect.any(Function),
209-
GM_getTab: expect.any(Function),
210-
GM_saveTab: expect.any(Function),
211-
});
207+
expect(ret["GM.getValue"]).toBeUndefined();
208+
expect(ret["GM.getTab"]).toBeUndefined();
209+
expect(ret["GM.setTab"]).toBeUndefined();
210+
expect(ret.GM_getValue.name).toEqual("bound GM_getValue");
211+
expect(ret.GM_getTab.name).toEqual("bound GM_getTab");
212+
expect(ret.GM_saveTab.name).toEqual("bound GM_saveTab");
213+
expect(ret.GM_cookie.name).toEqual("bound GM_cookie");
214+
expect(ret["GM_cookie.list"].name).toEqual("bound GM_cookie.list");
215+
expect(ret["GM.cookie"]).toBeUndefined();
212216
});
213217
it("GM.*", async () => {
214218
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
215-
script.metadata.grant = ["GM.getValue", "GM.getTab", "GM.saveTab"];
219+
script.metadata.grant = ["GM.getValue", "GM.getTab", "GM.saveTab", "GM.cookie"];
216220
// @ts-ignore
217221
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
218222
script.code = `return {
@@ -222,16 +226,110 @@ describe("@grant GM", () => {
222226
GM_getValue: this.GM_getValue,
223227
GM_getTab: this.GM_getTab,
224228
GM_saveTab: this.GM_saveTab,
229+
GM_cookie: this.GM_cookie,
230+
["GM.cookie"]: this.GM.cookie,
225231
}`;
226232
exec.scriptFunc = compileScript(compileScriptCode(script));
227233
const ret = await exec.exec();
228-
expect(ret).toEqual({
229-
"GM.getValue": expect.any(Function),
230-
"GM.getTab": expect.any(Function),
231-
"GM.setTab": expect.any(Function),
232-
GM_getValue: undefined,
233-
GM_getTab: undefined,
234-
GM_setTab: undefined,
235-
});
234+
expect(ret["GM.getValue"].name).toEqual("bound GM.getValue");
235+
expect(ret["GM.getTab"].name).toEqual("bound GM_getTab");
236+
expect(ret["GM.saveTab"].name).toEqual("bound GM_saveTab");
237+
expect(ret.GM_getValue).toBeUndefined();
238+
expect(ret.GM_getTab).toBeUndefined();
239+
expect(ret.GM_saveTab).toBeUndefined();
240+
expect(ret.GM_cookie).toBeUndefined();
241+
expect(ret["GM.cookie"].name).toEqual("bound GM.cookie");
242+
expect(ret["GM.cookie"].list.name).toEqual("bound GM.cookie.list");
243+
});
244+
});
245+
246+
describe("window.*", () => {
247+
it("window.close", async () => {
248+
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
249+
script.metadata.grant = ["window.close"];
250+
script.code = `return window.close;`;
251+
// @ts-ignore
252+
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
253+
exec.scriptFunc = compileScript(compileScriptCode(script));
254+
const ret = await exec.exec();
255+
expect(ret).toEqual(expect.any(Function));
256+
});
257+
});
258+
259+
describe("GM Api", () => {
260+
it("GM_getValue", async () => {
261+
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
262+
script.value = { test: "ok" };
263+
script.metadata.grant = ["GM_getValue"];
264+
script.code = `return GM_getValue("test");`;
265+
// @ts-ignore
266+
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
267+
exec.scriptFunc = compileScript(compileScriptCode(script));
268+
const ret = await exec.exec();
269+
expect(ret).toEqual("ok");
270+
});
271+
it("GM.getValue", async () => {
272+
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
273+
script.value = { test: "ok" };
274+
script.metadata.grant = ["GM.getValue"];
275+
script.code = `return GM.getValue("test").then(v=>v+"!");`;
276+
// @ts-ignore
277+
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
278+
exec.scriptFunc = compileScript(compileScriptCode(script));
279+
const ret = await exec.exec();
280+
expect(ret).toEqual("ok!");
281+
});
282+
283+
284+
it("GM_listValues", async () => {
285+
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
286+
script.value = { test1: "23", test2: "45", test3: "67" };
287+
script.metadata.grant = ["GM_listValues"];
288+
script.code = `return GM_listValues().join("-");`;
289+
// @ts-ignore
290+
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
291+
exec.scriptFunc = compileScript(compileScriptCode(script));
292+
const ret = await exec.exec();
293+
expect(ret).toEqual("test1-test2-test3");
294+
});
295+
296+
it("GM.listValues", async () => {
297+
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
298+
script.value = { test1: "23", test2: "45", test3: "67" };
299+
script.metadata.grant = ["GM.listValues"];
300+
script.code = `return GM.listValues().then(v=>v.join("-"));`;
301+
// @ts-ignore
302+
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
303+
exec.scriptFunc = compileScript(compileScriptCode(script));
304+
const ret = await exec.exec();
305+
expect(ret).toEqual("test1-test2-test3");
306+
});
307+
308+
it("GM_getValues", async () => {
309+
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
310+
script.value = { test1: "23", test2: 45, test3: "67" };
311+
script.metadata.grant = ["GM_getValues"];
312+
script.code = `return GM_getValues(["test2", "test3", "test1"]);`;
313+
// @ts-ignore
314+
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
315+
exec.scriptFunc = compileScript(compileScriptCode(script));
316+
const ret = await exec.exec();
317+
expect(ret.test1).toEqual("23");
318+
expect(ret.test2).toEqual(45);
319+
expect(ret.test3).toEqual("67");
320+
});
321+
322+
it("GM.getValues", async () => {
323+
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
324+
script.value = { test1: "23", test2: 45, test3: "67" };
325+
script.metadata.grant = ["GM.getValues"];
326+
script.code = `return GM.getValues(["test2", "test3", "test1"]).then(v=>v);`;
327+
// @ts-ignore
328+
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
329+
exec.scriptFunc = compileScript(compileScriptCode(script));
330+
const ret = await exec.exec();
331+
expect(ret.test1).toEqual("23");
332+
expect(ret.test2).toEqual(45);
333+
expect(ret.test3).toEqual("67");
236334
});
237335
});

src/app/service/content/exec_script.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import LoggerCore from "@App/app/logger/core";
22
import type Logger from "@App/app/logger/logger";
33
import { createContext } from "./create_context";
44
import type { GMInfoEnv, ScriptFunc } from "./types";
5-
import { compileScript, proxyContext } from "./utils";
5+
import { compileScript, createProxyContext } from "./utils";
66
import type { Message } from "@Packages/message/types";
77
import type { ScriptLoadInfo } from "../service_worker/types";
88
import type { ValueUpdateData } from "./types";
@@ -17,9 +17,9 @@ export default class ExecScript {
1717

1818
logger: Logger;
1919

20-
proxyContent: typeof globalThis;
20+
proxyContext: typeof globalThis;
2121

22-
sandboxContent?: IGM_Base & { [key: string]: any };
22+
sandboxContext?: IGM_Base & { [key: string]: any };
2323

2424
named?: { [key: string]: any };
2525

@@ -47,27 +47,27 @@ export default class ExecScript {
4747
const grantSet = new Set(scriptRes.metadata.grant || []);
4848
if (grantSet.has("none")) {
4949
// 不注入任何GM api
50-
this.proxyContent = global;
50+
this.proxyContext = global;
5151
// ScriptCat行为:GM.info 和 GM_info 同时注入
5252
// 不改变Context情况下,以 named 传多於一个全域变量
5353
this.named = {GM: {info: GM_info}, GM_info};
5454
} else {
5555
// 构建脚本GM上下文
56-
this.sandboxContent = createContext(scriptRes, GM_info, envPrefix, message, grantSet);
56+
this.sandboxContext = createContext(scriptRes, GM_info, envPrefix, message, grantSet);
5757
if (globalInjection) {
58-
Object.assign(this.sandboxContent, globalInjection);
58+
Object.assign(this.sandboxContext, globalInjection);
5959
}
60-
this.proxyContent = proxyContext(global, this.sandboxContent);
60+
this.proxyContext = createProxyContext(global, this.sandboxContext);
6161
}
6262
}
6363

6464
emitEvent(event: string, eventId: string, data: any) {
6565
this.logger.debug("emit event", { event, eventId, data });
66-
this.sandboxContent?.emitEvent(event, eventId, data);
66+
this.sandboxContext?.emitEvent(event, eventId, data);
6767
}
6868

6969
valueUpdate(data: ValueUpdateData) {
70-
this.sandboxContent?.valueUpdate(data);
70+
this.sandboxContext?.valueUpdate(data);
7171
}
7272

7373
/**
@@ -76,7 +76,7 @@ export default class ExecScript {
7676
*/
7777
exec() {
7878
this.logger.debug("script start");
79-
const context = this.proxyContent;
79+
const context = this.proxyContext;
8080
return this.scriptFunc.call(context, this.named, this.scriptRes.name);
8181
}
8282

0 commit comments

Comments
 (0)