Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
67b1b0d
使用静态方法作为实际实现
CodFrm Jul 10, 2025
dcdc36f
调整GMApi文件
CodFrm Jul 10, 2025
a0ee0ce
实现alias
CodFrm Jul 10, 2025
b9a9621
修复别名问题
CodFrm Jul 10, 2025
13aee67
修复 GM_cookie 问题
CodFrm Jul 10, 2025
5115552
🧪 添加GM cookie单元测试
CodFrm Jul 10, 2025
587557a
TreeShaking优化
cyfung1031 Jul 10, 2025
2f05d2b
createStubCallable & defaultFn
cyfung1031 Jul 10, 2025
a139307
注释更新
cyfung1031 Jul 10, 2025
703cf02
简单修一下,无行为改变
cyfung1031 Jul 10, 2025
6c07e1e
沒有CAT.*
cyfung1031 Jul 10, 2025
ae68ef4
indexOf -> includes
cyfung1031 Jul 10, 2025
d27878d
修订单元测试
cyfung1031 Jul 10, 2025
02d20ae
补充单元测试
CodFrm Jul 11, 2025
398c5e3
🧪 修改单元测试
CodFrm Jul 11, 2025
0354c0a
修复lint
CodFrm Jul 11, 2025
ac1f827
typo fix
cyfung1031 Jul 11, 2025
1edd832
typo fix
cyfung1031 Jul 11, 2025
9cf0b93
修正 createStubCallable
cyfung1031 Jul 11, 2025
d3419d8
优化
cyfung1031 Jul 11, 2025
28db562
修改单元测试
cyfung1031 Jul 11, 2025
ed8b71b
修改单元测试
cyfung1031 Jul 11, 2025
d59553f
🧪 补充单元测试
CodFrm Jul 11, 2025
ee22110
修正 createStubCallable
cyfung1031 Jul 11, 2025
06d05ba
简化设计
cyfung1031 Jul 11, 2025
3727457
修正
cyfung1031 Jul 11, 2025
ac72916
补充单元测试
cyfung1031 Jul 11, 2025
6aaac3a
typo
cyfung1031 Jul 11, 2025
1624392
修改单元测试
cyfung1031 Jul 11, 2025
f864004
补充API定义 (`GM.cookie`)
cyfung1031 Jul 11, 2025
07d8e94
Merge branch 'main' into pr/cyfung1031/519
CodFrm Jul 11, 2025
49e64b6
处理 gm cookie
CodFrm Jul 11, 2025
560f315
🧪 补充单元测试
CodFrm Jul 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions example/gm_cookie.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// @author You
// @match https://bbs.tampermonkey.net.cn/
// @grant GM_cookie
// @grant GM.cookie
// @connect example.com
// ==/UserScript==

Expand Down Expand Up @@ -40,3 +41,7 @@ GM_cookie("set", {
})
});
});

console.log("async GM.cookie.list", await GM.cookie.list({
domain: "example.com"
}));
1 change: 1 addition & 0 deletions example/grant_none.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// @description try to take over the world!
// @author You
// @match https://bbs.tampermonkey.net.cn/
// @grant none
// ==/UserScript==

console.log("Grant None", this, GM_info);
Expand Down
11 changes: 11 additions & 0 deletions example/sandbox_window.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// ==UserScript==
// @name New Userscript
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.1.0
// @description try to take over the world!
// @author You
// @match https://bbs.tampermonkey.net.cn/
// @grant window.close
// ==/UserScript==

window.close();
2 changes: 1 addition & 1 deletion packages/filesystem/onedrive/onedrive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export default class OneDriveFileSystem implements FileSystem {
request(url: string, config?: RequestInit, nothen?: boolean): Promise<Response | any> {
config = config || {};
const headers = <Headers>config.headers || new Headers();
if (url.indexOf("uploadSession") === -1) {
if (!url.includes("uploadSession")) {
headers.append(`Authorization`, `Bearer ${this.accessToken}`);
}
config.headers = headers;
Expand Down
32 changes: 18 additions & 14 deletions src/app/service/content/create_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { v4 as uuidv4 } from "uuid";
import type { Message } from "@Packages/message/types";
import EventEmitter from "eventemitter3";
import { GMContextApiGet } from "./gm_context";
import { GM_Base } from "./gm_api";
import { createGMBase } from "./gm_api";

// 构建沙盒上下文
export function createContext(
Expand All @@ -16,7 +16,7 @@ export function createContext(
// 按照GMApi构建
const valueChangeListener = new Map<number, { name: string; listener: GMTypes.ValueChangeListener }>();
const EE: EventEmitter = new EventEmitter();
const context = GM_Base.create({
const context = createGMBase({
prefix: envPrefix,
message,
scriptRes,
Expand All @@ -31,24 +31,16 @@ export function createContext(
},
grantSet: new Set(),
});
const grantedAPIs: { [key: string]: any } = {};
const __methodInject__ = (grant: string): boolean => {
const grantSet: Set<string> = context.grantSet;
const s = GMContextApiGet(grant);
if (!s) return false; // @grant 的定义未实作,略过 (返回 false 表示 @grant 不存在)
if (grantSet.has(grant)) return true; // 重覆的@grant,略过 (返回 true 表示 @grant 存在)
grantSet.add(grant);
for (const t of s) {
const fnKeyArray = t.fnKey.split(".");
const m = fnKeyArray.length - 1;
let g = context;
for (let i = 0; i < m; i++) {
const part = fnKeyArray[i];
g = g[part] || (g[part] = {});
}
const finalPart = fnKeyArray[m];
if (g[finalPart]) continue;
g[finalPart] = t.api.bind(context);
const depend = t?.param?.depend;
for (const {fnKey, api, param} of s) {
grantedAPIs[fnKey] = api.bind(context);
const depend = param?.depend;
if (depend) {
for (const grant of depend) {
__methodInject__(grant);
Expand All @@ -60,6 +52,18 @@ export function createContext(
for (const grant of scriptGrants) {
__methodInject__(grant);
}
// 兼容GM.Cookie.*
for (const fnKey of Object.keys(grantedAPIs)) {
const fnKeyArray = fnKey.split(".");
const m = fnKeyArray.length;
let g = context;
let s = '';
for (let i = 0; i < m; i++) {
const part = fnKeyArray[i];
s += `${(i ? '.' : '')}${part}`;
g = g[part] || (g[part] = (grantedAPIs[s] || {}));
}
}
context.unsafeWindow = window;
return context;
}
138 changes: 118 additions & 20 deletions src/app/service/content/exec_script.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ExtVersion } from "@App/app/const";
import { initTestEnv } from "@Tests/utils";
import { describe, expect, it } from "vitest";
import type { GMInfoEnv } from "./types";
import { ScriptLoadInfo } from "../service_worker/types";
import type { ScriptLoadInfo } from "../service_worker/types";

initTestEnv();

Expand Down Expand Up @@ -62,7 +62,7 @@ describe("GM_info", () => {
const ret = await sandboxExec.exec();
expect(ret.GM_info.version).toEqual(ExtVersion);
expect(ret.GM_info.script.version).toEqual("1.0.0");
expect(ret._this).toEqual(sandboxExec.proxyContent);
expect(ret._this).toEqual(sandboxExec.proxyContext);
});
});

Expand Down Expand Up @@ -188,7 +188,7 @@ describe("none this", () => {
describe("@grant GM", () => {
it("GM_", async () => {
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
script.metadata.grant = ["GM_getValue", "GM_getTab", "GM_saveTab"];
script.metadata.grant = ["GM_getValue", "GM_getTab", "GM_saveTab", "GM_cookie"];
// @ts-ignore
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
script.code = `return {
Expand All @@ -198,21 +198,25 @@ describe("@grant GM", () => {
GM_getValue: this.GM_getValue,
GM_getTab: this.GM_getTab,
GM_saveTab: this.GM_saveTab,
GM_cookie: this.GM_cookie,
["GM_cookie.list"]: this.GM_cookie.list,
["GM.cookie"]: this.GM.cookie,
}`;
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
expect(ret).toEqual({
"GM.getValue": undefined,
"GM.getTab": undefined,
"GM.setTab": undefined,
GM_getValue: expect.any(Function),
GM_getTab: expect.any(Function),
GM_saveTab: expect.any(Function),
});
expect(ret["GM.getValue"]).toBeUndefined();
expect(ret["GM.getTab"]).toBeUndefined();
expect(ret["GM.setTab"]).toBeUndefined();
expect(ret.GM_getValue.name).toEqual("bound GM_getValue");
expect(ret.GM_getTab.name).toEqual("bound GM_getTab");
expect(ret.GM_saveTab.name).toEqual("bound GM_saveTab");
expect(ret.GM_cookie.name).toEqual("bound GM_cookie");
expect(ret["GM_cookie.list"].name).toEqual("bound GM_cookie.list");
expect(ret["GM.cookie"]).toBeUndefined();
});
it("GM.*", async () => {
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
script.metadata.grant = ["GM.getValue", "GM.getTab", "GM.saveTab"];
script.metadata.grant = ["GM.getValue", "GM.getTab", "GM.saveTab", "GM.cookie"];
// @ts-ignore
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
script.code = `return {
Expand All @@ -222,16 +226,110 @@ describe("@grant GM", () => {
GM_getValue: this.GM_getValue,
GM_getTab: this.GM_getTab,
GM_saveTab: this.GM_saveTab,
GM_cookie: this.GM_cookie,
["GM.cookie"]: this.GM.cookie,
}`;
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
expect(ret).toEqual({
"GM.getValue": expect.any(Function),
"GM.getTab": expect.any(Function),
"GM.setTab": expect.any(Function),
GM_getValue: undefined,
GM_getTab: undefined,
GM_setTab: undefined,
});
expect(ret["GM.getValue"].name).toEqual("bound GM.getValue");
expect(ret["GM.getTab"].name).toEqual("bound GM_getTab");
expect(ret["GM.saveTab"].name).toEqual("bound GM_saveTab");
expect(ret.GM_getValue).toBeUndefined();
expect(ret.GM_getTab).toBeUndefined();
expect(ret.GM_saveTab).toBeUndefined();
expect(ret.GM_cookie).toBeUndefined();
expect(ret["GM.cookie"].name).toEqual("bound GM.cookie");
expect(ret["GM.cookie"].list.name).toEqual("bound GM.cookie.list");
});
});

describe("window.*", () => {
it("window.close", async () => {
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
script.metadata.grant = ["window.close"];
script.code = `return window.close;`;
// @ts-ignore
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
expect(ret).toEqual(expect.any(Function));
});
});

describe("GM Api", () => {
it("GM_getValue", async () => {
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
script.value = { test: "ok" };
script.metadata.grant = ["GM_getValue"];
script.code = `return GM_getValue("test");`;
// @ts-ignore
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
expect(ret).toEqual("ok");
});
it("GM.getValue", async () => {
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
script.value = { test: "ok" };
script.metadata.grant = ["GM.getValue"];
script.code = `return GM.getValue("test").then(v=>v+"!");`;
// @ts-ignore
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
expect(ret).toEqual("ok!");
});


it("GM_listValues", async () => {
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
script.value = { test1: "23", test2: "45", test3: "67" };
script.metadata.grant = ["GM_listValues"];
script.code = `return GM_listValues().join("-");`;
// @ts-ignore
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
expect(ret).toEqual("test1-test2-test3");
});

it("GM.listValues", async () => {
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
script.value = { test1: "23", test2: "45", test3: "67" };
script.metadata.grant = ["GM.listValues"];
script.code = `return GM.listValues().then(v=>v.join("-"));`;
// @ts-ignore
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
expect(ret).toEqual("test1-test2-test3");
});

it("GM_getValues", async () => {
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
script.value = { test1: "23", test2: 45, test3: "67" };
script.metadata.grant = ["GM_getValues"];
script.code = `return GM_getValues(["test2", "test3", "test1"]);`;
// @ts-ignore
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
expect(ret.test1).toEqual("23");
expect(ret.test2).toEqual(45);
expect(ret.test3).toEqual("67");
});

it("GM.getValues", async () => {
const script = Object.assign({}, scriptRes2) as ScriptLoadInfo;
script.value = { test1: "23", test2: 45, test3: "67" };
script.metadata.grant = ["GM.getValues"];
script.code = `return GM.getValues(["test2", "test3", "test1"]).then(v=>v);`;
// @ts-ignore
const exec = new ExecScript(script, undefined, undefined, undefined, envInfo);
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
expect(ret.test1).toEqual("23");
expect(ret.test2).toEqual(45);
expect(ret.test3).toEqual("67");
});
});
20 changes: 10 additions & 10 deletions src/app/service/content/exec_script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import LoggerCore from "@App/app/logger/core";
import type Logger from "@App/app/logger/logger";
import { createContext } from "./create_context";
import type { GMInfoEnv, ScriptFunc } from "./types";
import { compileScript, proxyContext } from "./utils";
import { compileScript, createProxyContext } from "./utils";
import type { Message } from "@Packages/message/types";
import type { ScriptLoadInfo } from "../service_worker/types";
import type { ValueUpdateData } from "./types";
Expand All @@ -17,9 +17,9 @@ export default class ExecScript {

logger: Logger;

proxyContent: typeof globalThis;
proxyContext: typeof globalThis;

sandboxContent?: IGM_Base & { [key: string]: any };
sandboxContext?: IGM_Base & { [key: string]: any };

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

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

emitEvent(event: string, eventId: string, data: any) {
this.logger.debug("emit event", { event, eventId, data });
this.sandboxContent?.emitEvent(event, eventId, data);
this.sandboxContext?.emitEvent(event, eventId, data);
}

valueUpdate(data: ValueUpdateData) {
this.sandboxContent?.valueUpdate(data);
this.sandboxContext?.valueUpdate(data);
}

/**
Expand All @@ -76,7 +76,7 @@ export default class ExecScript {
*/
exec() {
this.logger.debug("script start");
const context = this.proxyContent;
const context = this.proxyContext;
return this.scriptFunc.call(context, this.named, this.scriptRes.name);
}

Expand Down
Loading