Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
59 changes: 11 additions & 48 deletions src/app/service/content/create_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import type { TScriptInfo } from "@App/app/repo/scripts";
import { uuidv4 } from "@App/pkg/utils/uuid";
import type { Message } from "@Packages/message/types";
import EventEmitter from "eventemitter3";
import { GMContextApiGet } from "./gm_api/gm_context";
import { protect } from "./gm_api/gm_context";
import { isEarlyStartScript } from "./utils";
import { ListenerManager } from "./listener_manager";
import { createGMBase } from "./gm_api/gm_api";
import { createGMBase, createGMApis } from "./gm_api/gm_api";

// 构建沙盒上下文
export const createContext = (
Expand All @@ -27,8 +26,7 @@ export const createContext = (
loadScriptResolve = resolve;
});
}
let invalid = false;
const context = createGMBase({
const gtx = createGMBase({
prefix: envPrefix,
message,
scriptRes,
Expand All @@ -41,66 +39,31 @@ export const createContext = (
window: {
// onurlchange: null,
},
grantSet: new Set(),
loadScriptPromise,
loadScriptResolve,
setInvalidContext() {
if (invalid) return;
invalid = true;
this.valueChangeListener.clear();
this.EE.removeAllListeners();
this.runFlag = `${uuidv4()}(invalid)`; // 更改 uuid 防止 runFlag 相关操作
// 释放记忆
this.message = null;
this.scriptRes = null;
this.valueChangeListener = null;
this.EE = null;
},
isInvalidContext() {
return invalid;
},
});
const gmApis = createGMApis(gtx, scriptGrants);
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 { fnKey, api, param } of s) {
grantedAPIs[fnKey] = api.bind(context);
const depend = param?.depend;
if (depend) {
for (const grant of depend) {
__methodInject__(grant);
}
}
}
return true;
};
for (const grant of scriptGrants) {
// GM. 与 GM_ 都需要注入
__methodInject__(grant);
if (grant.startsWith("GM.")) {
__methodInject__(grant.replace("GM.", "GM_"));
} else if (grant.startsWith("GM_")) {
__methodInject__(grant.replace("GM_", "GM."));
}

for (const [key, value] of Object.entries(gmApis)) {
(gmApis as any)[key] = undefined; // 释放不需要的函数
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在第 49 行,代码尝试通过将 gmApis 的属性设置为 undefined 来"释放不需要的函数"。但这是有问题的,因为:

  1. gmApis 被声明为 as const,试图修改它的属性可能会导致 TypeScript 错误
  2. 将对象属性设置为 undefined 并不会真正释放内存,只有移除对对象的引用才能让垃圾回收器回收内存
  3. 如果目的是避免暴露内部函数(以 "_" 开头的),应该在构建 grantedAPIs 时过滤,而不是修改 gmApis

建议删除这一行,或使用其他方式实现意图的功能。

Suggested change
(gmApis as any)[key] = undefined; // 释放不需要的函数

Copilot uses AI. Check for mistakes.
if (key[0] === "_" || typeof value !== "function") continue;
grantedAPIs[key] = value;
}
// 兼容GM.Cookie.*
for (const fnKey of Object.keys(grantedAPIs)) {
const fnKeyArray = fnKey.split(".");
const m = fnKeyArray.length;
let g = context;
let g = gtx;
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;
gtx.unsafeWindow = window;
return gtx;
};

const noEval = false;
Expand Down
79 changes: 51 additions & 28 deletions src/app/service/content/gm_api/gm_api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const envInfo: GMInfoEnv = {
describe.concurrent("@grant GM", () => {
it.concurrent("GM_", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = [
"GM_getValue",
"GM_getTab",
Expand Down Expand Up @@ -67,30 +68,31 @@ describe.concurrent("@grant GM", () => {
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
// getValue
expect(ret.GM_getValue?.name).toEqual("bound GM_getValue");
expect(ret.GM_getValue?.name).toEqual("GM_getValue");
// getTab / getTabs / saveTab
expect(ret.GM_getTab?.name).toEqual("bound GM_getTab");
expect(ret.GM_getTabs?.name).toEqual("bound GM_getTabs");
expect(ret.GM_saveTab?.name).toEqual("bound GM_saveTab");
expect(ret.GM_getTab?.name).toEqual("GM_getTab");
expect(ret.GM_getTabs?.name).toEqual("GM_getTabs");
expect(ret.GM_saveTab?.name).toEqual("GM_saveTab");
// cookie
expect(ret.GM_cookie?.name).toEqual("bound GM_cookie");
expect(ret["GM_cookie.list"]?.name).toEqual("bound GM_cookie.list");
expect(ret.GM_cookie?.name).toEqual("GM_cookie");
expect(ret["GM_cookie.list"]?.name).toEqual("GM_cookie.list");
// GM_与GM.应该都在
expect(ret["GM_addElement"]?.name).toEqual("bound GM_addElement");
expect(ret["GM.addElement"]?.name).toEqual("bound GM.addElement");
expect(ret["GM_openInTab"]?.name).toEqual("bound GM_openInTab");
expect(ret["GM.openInTab"]?.name).toEqual("bound GM.openInTab");
expect(ret["GM_log"]?.name).toEqual("bound GM_log");
expect(ret["GM.log"]?.name).toEqual("bound GM.log");
expect(ret["GM_notification"]?.name).toEqual("bound GM_notification");
expect(ret["GM.notification"]?.name).toEqual("bound GM.notification");
expect(ret["GM_addElement"]?.name).toEqual("GM_addElement");
expect(ret["GM.addElement"]?.name).toEqual("GM.addElement");
expect(ret["GM_openInTab"]?.name).toEqual("GM_openInTab");
expect(ret["GM.openInTab"]?.name).toEqual("GM.openInTab");
expect(ret["GM_log"]?.name).toEqual("GM_log");
expect(ret["GM.log"]?.name).toEqual("GM.log");
expect(ret["GM_notification"]?.name).toEqual("GM_notification");
expect(ret["GM.notification"]?.name).toEqual("GM.notification");
// 没有grant应返回 nil
expect(ret["GM_xmlhttpRequest"]?.name).toEqual("nil");
expect(ret["GM.xmlhttpRequest"]?.name).toEqual("nil");
});

it.concurrent("GM.*", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = [
"GM.getValue",
"GM.getTab",
Expand Down Expand Up @@ -124,23 +126,23 @@ describe.concurrent("@grant GM", () => {
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
// getValue
expect(ret["GM.getValue"]?.name).toEqual("bound GM.getValue");
expect(ret["GM.getValue"]?.name).toEqual("GM.getValue");
// getTab / getTabs / saveTab
expect(ret["GM.getTab"]?.name).toEqual("bound GM.getTab");
expect(ret["GM.getTabs"]?.name).toEqual("bound GM.getTabs");
expect(ret["GM.saveTab"]?.name).toEqual("bound GM.saveTab");
expect(ret["GM.getTab"]?.name).toEqual("GM.getTab");
expect(ret["GM.getTabs"]?.name).toEqual("GM.getTabs");
expect(ret["GM.saveTab"]?.name).toEqual("GM.saveTab");
// cookie
expect(ret["GM.cookie"]?.name).toEqual("bound GM.cookie");
expect(ret["GM.cookie"]?.list?.name).toEqual("bound GM.cookie.list");
expect(ret["GM.cookie"]?.name).toEqual("GM.cookie");
expect(ret["GM.cookie"]?.list?.name).toEqual("GM.cookie.list");
// GM_与GM.应该都在
expect(ret["GM_addElement"]?.name).toEqual("bound GM_addElement");
expect(ret["GM.addElement"]?.name).toEqual("bound GM.addElement");
expect(ret["GM_openInTab"]?.name).toEqual("bound GM_openInTab");
expect(ret["GM.openInTab"]?.name).toEqual("bound GM.openInTab");
expect(ret["GM_log"]?.name).toEqual("bound GM_log");
expect(ret["GM.log"]?.name).toEqual("bound GM.log");
expect(ret["GM_notification"]?.name).toEqual("bound GM_notification");
expect(ret["GM.notification"]?.name).toEqual("bound GM.notification");
expect(ret["GM_addElement"]?.name).toEqual("GM_addElement");
expect(ret["GM.addElement"]?.name).toEqual("GM.addElement");
expect(ret["GM_openInTab"]?.name).toEqual("GM_openInTab");
expect(ret["GM.openInTab"]?.name).toEqual("GM.openInTab");
expect(ret["GM_log"]?.name).toEqual("GM_log");
expect(ret["GM.log"]?.name).toEqual("GM.log");
expect(ret["GM_notification"]?.name).toEqual("GM_notification");
expect(ret["GM.notification"]?.name).toEqual("GM.notification");
// 没有grant应返回 nil
expect(ret["GM_xmlhttpRequest"]?.name).toEqual("nil");
expect(ret["GM.xmlhttpRequest"]?.name).toEqual("nil");
Expand All @@ -150,6 +152,7 @@ describe.concurrent("@grant GM", () => {
describe.concurrent("window.*", () => {
it.concurrent("window.close", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["window.close"];
script.code = `return window.close;`;
// @ts-ignore
Expand All @@ -164,6 +167,7 @@ describe.concurrent("GM Api", () => {
it.concurrent("GM_getValue", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { test: "ok" };
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_getValue"];
script.code = `return GM_getValue("test");`;
// @ts-ignore
Expand All @@ -175,6 +179,7 @@ describe.concurrent("GM Api", () => {
it.concurrent("GM.getValue", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { test: "ok" };
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM.getValue"];
script.code = `return GM.getValue("test").then(v=>v+"!");`;
// @ts-ignore
Expand All @@ -187,6 +192,7 @@ describe.concurrent("GM Api", () => {
it.concurrent("GM_listValues", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { test1: "23", test2: "45", test3: "67" };
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_listValues"];
script.code = `return GM_listValues().join("-");`;
// @ts-ignore
Expand All @@ -203,6 +209,7 @@ describe.concurrent("GM Api", () => {
script.value.test2 = "70";
script.value.test3 = "75";
script.value.test1 = "40";
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_listValues"];
script.code = `return GM_listValues().join("-");`;
// @ts-ignore
Expand All @@ -215,6 +222,7 @@ describe.concurrent("GM Api", () => {
it.concurrent("GM.listValues", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { test1: "23", test2: "45", test3: "67" };
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM.listValues"];
script.code = `return GM.listValues().then(v=>v.join("-"));`;
// @ts-ignore
Expand All @@ -231,6 +239,7 @@ describe.concurrent("GM Api", () => {
script.value.test2 = "70";
script.value.test3 = "75";
script.value.test1 = "40";
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM.listValues"];
script.code = `return GM.listValues().then(v=>v.join("-"));`;
// @ts-ignore
Expand All @@ -243,6 +252,7 @@ describe.concurrent("GM Api", () => {
it.concurrent("GM_getValues", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { test1: "23", test2: 45, test3: "67" };
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_getValues"];
script.code = `return GM_getValues(["test2", "test3", "test1"]);`;
// @ts-ignore
Expand All @@ -264,6 +274,7 @@ describe.concurrent("GM Api", () => {
it.concurrent("GM.getValues", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { test1: "23", test2: 45, test3: "67" };
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM.getValues"];
script.code = `return GM.getValues(["test2", "test3", "test1"]).then(v=>v);`;
// @ts-ignore
Expand Down Expand Up @@ -309,6 +320,7 @@ describe.concurrent("early-script", () => {
describe.concurrent("GM_menu", () => {
it.concurrent("注册菜单", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_registerMenuCommand"];
script.code = `return new Promise(resolve=>{
GM_registerMenuCommand("test", ()=>resolve(123));
Expand Down Expand Up @@ -357,6 +369,7 @@ describe.concurrent("GM_menu", () => {

it.concurrent("取消注册菜单", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_registerMenuCommand", "GM_unregisterMenuCommand"];
script.code = `
let key = GM_registerMenuCommand("test", ()=>key="test");
Expand All @@ -380,6 +393,7 @@ describe.concurrent("GM_menu", () => {

it.concurrent("同id菜单,执行最后一个", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_registerMenuCommand"];
script.code = `return new Promise(resolve=>{
GM_registerMenuCommand("duplicate-menu-id", ()=>resolve(123),{id: "abc"});
Expand Down Expand Up @@ -431,6 +445,7 @@ describe.concurrent("GM_menu", () => {

it.concurrent("id生成逻辑", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_registerMenuCommand"];
script.code = `
// 自定义id
Expand Down Expand Up @@ -463,6 +478,7 @@ describe.concurrent("GM_menu", () => {
describe.concurrent("GM_value", () => {
it.concurrent("GM_setValue", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_getValue", "GM_setValue"];
script.code = `
GM_setValue("a", 123);
Expand Down Expand Up @@ -556,6 +572,7 @@ describe.concurrent("GM_value", () => {
it.concurrent("value引用问题 #1141", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = {};
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_getValue", "GM_setValue", "GM_getValues"];
script.code = `
const value1 = {
Expand Down Expand Up @@ -708,6 +725,7 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4

it.concurrent("GM_setValues", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_getValues", "GM_setValues"];
script.code = `
GM_setValues({"a":123,"b":456,"c":"789"});
Expand Down Expand Up @@ -836,6 +854,7 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4

it.concurrent("GM_deleteValue", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_getValues", "GM_setValues", "GM_deleteValue"];
script.code = `
GM_setValues({"a":123,"b":456,"c":"789"});
Expand Down Expand Up @@ -905,6 +924,7 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4

it.concurrent("GM_deleteValues", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_getValues", "GM_setValues", "GM_deleteValues"];
script.code = `
GM_setValues({"a":123,"b":456,"c":"789"});
Expand Down Expand Up @@ -980,6 +1000,7 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4

it.concurrent("GM_addValueChangeListener - remote: false", async () => {
const script = Object.assign({ uuid: uuidv4() }, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_getValue", "GM_setValue", "GM_addValueChangeListener"];
script.metadata.storageName = ["testStorage"];
script.code = `
Expand Down Expand Up @@ -1014,6 +1035,7 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4

it.concurrent("GM_addValueChangeListener - remote: true", async () => {
const script = Object.assign({ uuid: uuidv4() }, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM_getValue", "GM_setValue", "GM_addValueChangeListener"];
script.metadata.storageName = ["testStorage"];
script.code = `
Expand Down Expand Up @@ -1048,6 +1070,7 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4
});
it.concurrent("异步GM.setValue,等待回调", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.metadata = Object.assign({}, script.metadata);
script.metadata.grant = ["GM.getValue", "GM.setValue"];
script.code = `await GM.setValue("a", 123); return await GM.getValue("a");`;
const mockSendMessage = vi.fn().mockResolvedValue({ code: 0 });
Expand Down
Loading
Loading