From 0981c2bfd1f0d490d2682160aef0ef466d3d7fe6 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 31 Jan 2026 19:11:22 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20GM=20API=20=E5=BC=82?= =?UTF-8?q?=E6=AD=A5=E5=A3=B0=E6=98=8E=EF=BC=8C=E6=AD=A3=E7=A1=AE=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=20Promise?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/content/gm_api/gm_api.test.ts | 88 +++--- src/app/service/content/gm_api/gm_api.ts | 255 ++++++++++++------ .../service/service_worker/gm_api/gm_api.ts | 8 +- 3 files changed, 234 insertions(+), 117 deletions(-) diff --git a/src/app/service/content/gm_api/gm_api.test.ts b/src/app/service/content/gm_api/gm_api.test.ts index 4df89bc7d..4a69a7ee3 100644 --- a/src/app/service/content/gm_api/gm_api.test.ts +++ b/src/app/service/content/gm_api/gm_api.test.ts @@ -33,31 +33,45 @@ const envInfo: GMInfoEnv = { describe.concurrent("@grant GM", () => { it.concurrent("GM_", async () => { const script = Object.assign({}, scriptRes) as ScriptLoadInfo; - script.metadata.grant = ["GM_getValue", "GM_getTab", "GM_saveTab", "GM_cookie"]; + script.metadata.grant = ["GM_getValue", "GM_getTab", "GM_getTabs", "GM_saveTab", "GM_cookie"]; // @ts-ignore const exec = new ExecScript(script, undefined, undefined, nilFn, envInfo); script.code = `return { - ["GM.getValue"]: GM.getValue, - ["GM.getTab"]: GM.getTab, - ["GM.setTab"]: GM.setTab, GM_getValue: this.GM_getValue, GM_getTab: this.GM_getTab, + GM_getTabs: this.GM_getTabs, GM_saveTab: this.GM_saveTab, GM_cookie: this.GM_cookie, ["GM_cookie.list"]: this.GM_cookie.list, - ["GM.cookie"]: this.GM.cookie, + ["GM_addElement"]: this.GM_addElement || function nil(){}, + ["GM.addElement"]: this["GM.addElement"] || function nil(){}, + ["GM_openInTab"]: this.GM_openInTab || function nil(){}, + ["GM.openInTab"]: this.GM.openInTab || function nil(){}, + ["GM_log"]: this.GM_log || function nil(){}, + ["GM.log"]: this.GM.log || function nil(){}, + ["GM_notification"]: this.GM_notification || function nil(){}, + ["GM.notification"]: this.GM.notification || function nil(){}, }`; exec.scriptFunc = compileScript(compileScriptCode(script)); const ret = await exec.exec(); - 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(); + // getValue + expect(ret.GM_getValue?.name).toEqual("bound 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"); + // cookie + expect(ret.GM_cookie?.name).toEqual("bound GM_cookie"); + expect(ret["GM_cookie.list"]?.name).toEqual("bound GM_cookie.list"); + // 没有grant应返回 nil + expect(ret["GM_addElement"]?.name).toEqual("nil"); + expect(ret["GM.addElement"]?.name).toEqual("nil"); + expect(ret["GM_openInTab"]?.name).toEqual("nil"); + expect(ret["GM.openInTab"]?.name).toEqual("nil"); + expect(ret["GM_log"]?.name).toEqual("nil"); + expect(ret["GM.log"]?.name).toEqual("nil"); + expect(ret["GM_notification"]?.name).toEqual("nil"); + expect(ret["GM.notification"]?.name).toEqual("nil"); }); it.concurrent("GM.*", async () => { @@ -70,26 +84,36 @@ describe.concurrent("@grant GM", () => { ["GM.getTab"]: GM.getTab, ["GM.getTabs"]: GM.getTabs, ["GM.saveTab"]: GM.saveTab, - GM_getValue: this.GM_getValue, - GM_getTab: this.GM_getTab, - GM_getTabs: this.GM_getTabs, - GM_saveTab: this.GM_saveTab, - GM_cookie: this.GM_cookie, ["GM.cookie"]: this.GM.cookie, + ["GM_addElement"]: this.GM_addElement || function nil(){}, + ["GM.addElement"]: this["GM.addElement"] || function nil(){}, + ["GM_openInTab"]: this.GM_openInTab || function nil(){}, + ["GM.openInTab"]: this.GM.openInTab || function nil(){}, + ["GM_log"]: this.GM_log || function nil(){}, + ["GM.log"]: this.GM.log || function nil(){}, + ["GM_notification"]: this.GM_notification || function nil(){}, + ["GM.notification"]: this.GM.notification || function nil(){}, }`; exec.scriptFunc = compileScript(compileScriptCode(script)); const ret = await exec.exec(); - expect(ret["GM.getValue"].name).toEqual("bound GM.getValue"); - 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_getValue).toBeUndefined(); - expect(ret.GM_getTab.name).toEqual("bound GM_getTab"); - expect(ret.GM_getTabs.name).toEqual("bound GM_getTabs"); - 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"); + // getValue + expect(ret["GM.getValue"]?.name).toEqual("bound 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"); + // cookie + expect(ret["GM.cookie"]?.name).toEqual("bound GM.cookie"); + expect(ret["GM.cookie"]?.list?.name).toEqual("bound GM.cookie.list"); + // 没有grant应返回 nil + expect(ret["GM_addElement"]?.name).toEqual("nil"); + expect(ret["GM.addElement"]?.name).toEqual("nil"); + expect(ret["GM_openInTab"]?.name).toEqual("nil"); + expect(ret["GM.openInTab"]?.name).toEqual("nil"); + expect(ret["GM_log"]?.name).toEqual("nil"); + expect(ret["GM.log"]?.name).toEqual("nil"); + expect(ret["GM_notification"]?.name).toEqual("nil"); + expect(ret["GM.notification"]?.name).toEqual("nil"); }); }); @@ -155,7 +179,7 @@ describe.concurrent("GM Api", () => { const exec = new ExecScript(script, undefined, undefined, nilFn, envInfo); exec.scriptFunc = compileScript(compileScriptCode(script)); const ret = await exec.exec(); - expect(ret).toEqual("test5-test2-test3-test1"); // TM也沒有sort + expect(ret).toEqual("test5-test2-test3-test1"); // TM也没有sort }); it.concurrent("GM.listValues", async () => { @@ -183,7 +207,7 @@ describe.concurrent("GM Api", () => { const exec = new ExecScript(script, undefined, undefined, nilFn, envInfo); exec.scriptFunc = compileScript(compileScriptCode(script)); const ret = await exec.exec(); - expect(ret).toEqual("test5-test2-test3-test1"); // TM也沒有sort + expect(ret).toEqual("test5-test2-test3-test1"); // TM也没有sort }); it.concurrent("GM_getValues", async () => { diff --git a/src/app/service/content/gm_api/gm_api.ts b/src/app/service/content/gm_api/gm_api.ts index 9a2463f4b..19c81e967 100644 --- a/src/app/service/content/gm_api/gm_api.ts +++ b/src/app/service/content/gm_api/gm_api.ts @@ -252,7 +252,7 @@ export default class GMApi extends GM_Base { } @GMContext.API() - public ["GM.getValue"](key: string, defaultValue?: any): Promise { + public "GM.getValue"(key: string, defaultValue?: any): Promise { // 兼容GM.getValue return new Promise((resolve) => { const ret = _GM_getValue(this, key, defaultValue); @@ -322,7 +322,7 @@ export default class GMApi extends GM_Base { } @GMContext.API() - public ["GM.setValue"](key: string, value: any): Promise { + public "GM.setValue"(key: string, value: any): Promise { // Asynchronous wrapper for GM_setValue to support GM.setValue return new Promise((resolve) => { _GM_setValue(this, resolve, key, value); @@ -335,7 +335,7 @@ export default class GMApi extends GM_Base { } @GMContext.API() - public ["GM.deleteValue"](key: string): Promise { + public "GM.deleteValue"(key: string): Promise { // Asynchronous wrapper for GM_deleteValue to support GM.deleteValue return new Promise((resolve) => { _GM_setValue(this, resolve, key, undefined); @@ -350,7 +350,7 @@ export default class GMApi extends GM_Base { } @GMContext.API() - public ["GM.listValues"](): Promise { + public "GM.listValues"(): Promise { // Asynchronous wrapper for GM_listValues to support GM.listValues return new Promise((resolve) => { if (!this.scriptRes) return resolve([]); @@ -402,7 +402,7 @@ export default class GMApi extends GM_Base { // Asynchronous wrapper for GM.getValues @GMContext.API({ depend: ["GM_getValues"] }) - public ["GM.getValues"](keysOrDefaults: TGMKeyValue | string[] | null | undefined): Promise { + public "GM.getValues"(keysOrDefaults: TGMKeyValue | string[] | null | undefined): Promise { if (!this.scriptRes) return new Promise(() => {}); return new Promise((resolve) => { const ret = this.GM_getValues(keysOrDefaults); @@ -410,8 +410,8 @@ export default class GMApi extends GM_Base { }); } - @GMContext.API({ depend: ["GM_setValues"] }) - public ["GM.setValues"](values: { [key: string]: any }): Promise { + @GMContext.API() + public "GM.setValues"(values: { [key: string]: any }): Promise { if (!this.scriptRes) return new Promise(() => {}); return new Promise((resolve) => { if (!values || typeof values !== "object") { @@ -436,8 +436,8 @@ export default class GMApi extends GM_Base { } // Asynchronous wrapper for GM.deleteValues - @GMContext.API({ depend: ["GM_deleteValues"] }) - public ["GM.deleteValues"](keys: string[]): Promise { + @GMContext.API() + public "GM.deleteValues"(keys: string[]): Promise { if (!this.scriptRes) return new Promise(() => {}); return new Promise((resolve) => { if (!Array.isArray(keys)) { @@ -452,20 +452,36 @@ export default class GMApi extends GM_Base { }); } - @GMContext.API({ alias: "GM.addValueChangeListener" }) + @GMContext.API() public GM_addValueChangeListener(name: string, listener: GMTypes.ValueChangeListener): number { if (!this.valueChangeListener) return 0; return this.valueChangeListener.add(name, listener); } - @GMContext.API({ alias: "GM.removeValueChangeListener" }) + @GMContext.API({ depend: ["GM_addValueChangeListener"] }) + public "GM.addValueChangeListener"(name: string, listener: GMTypes.ValueChangeListener): Promise { + return new Promise((resolve) => { + const ret = this.GM_addValueChangeListener(name, listener); + resolve(ret); + }); + } + + @GMContext.API() public GM_removeValueChangeListener(listenerId: number): void { if (!this.valueChangeListener) return; this.valueChangeListener.remove(listenerId); } - @GMContext.API({ alias: "GM.log" }) - GM_log(message: string, level: GMTypes.LoggerLevel = "info", ...labels: GMTypes.LoggerLabel[]) { + @GMContext.API({ depend: ["GM_removeValueChangeListener"] }) + public "GM.removeValueChangeListener"(listenerId: number): Promise { + return new Promise((resolve) => { + this.GM_removeValueChangeListener(listenerId); + resolve(); + }); + } + + @GMContext.API() + public GM_log(message: string, level: GMTypes.LoggerLevel = "info", ...labels: GMTypes.LoggerLabel[]): void { if (this.isInvalidContext()) return; if (typeof message !== "string") { message = JSON.stringify(message); @@ -473,6 +489,18 @@ export default class GMApi extends GM_Base { this.sendMessage("GM_log", [message, level, labels]); } + @GMContext.API({ depend: ["GM_log"] }) + public "GM.log"( + message: string, + level: GMTypes.LoggerLevel = "info", + ...labels: GMTypes.LoggerLabel[] + ): Promise { + return new Promise((resolve) => { + this.GM_log(message, level, ...labels); + resolve(); + }); + } + @GMContext.API() public CAT_createBlobUrl(blob: Blob): Promise { return Promise.resolve(toBlobURL(this, blob)); @@ -514,8 +542,8 @@ export default class GMApi extends GM_Base { }); } - @GMContext.API({ follow: "GM.cookie" }) - ["GM.cookie"](action: string, details: GMTypes.CookieDetails) { + @GMContext.API() + public "GM.cookie"(action: string, details: GMTypes.CookieDetails) { return new Promise((resolve, reject) => { _GM_cookie(this, action, details, (cookie, error) => { error ? reject(error) : resolve(cookie); @@ -524,7 +552,7 @@ export default class GMApi extends GM_Base { } @GMContext.API({ follow: "GM.cookie" }) - ["GM.cookie.set"](details: GMTypes.CookieDetails) { + public "GM.cookie.set"(details: GMTypes.CookieDetails) { return new Promise((resolve, reject) => { _GM_cookie(this, "set", details, (cookie, error) => { error ? reject(error) : resolve(cookie); @@ -533,7 +561,7 @@ export default class GMApi extends GM_Base { } @GMContext.API({ follow: "GM.cookie" }) - ["GM.cookie.list"](details: GMTypes.CookieDetails) { + public "GM.cookie.list"(details: GMTypes.CookieDetails) { return new Promise((resolve, reject) => { _GM_cookie(this, "list", details, (cookie, error) => { error ? reject(error) : resolve(cookie); @@ -542,7 +570,7 @@ export default class GMApi extends GM_Base { } @GMContext.API({ follow: "GM.cookie" }) - ["GM.cookie.delete"](details: GMTypes.CookieDetails) { + public "GM.cookie.delete"(details: GMTypes.CookieDetails) { return new Promise((resolve, reject) => { _GM_cookie(this, "delete", details, (cookie, error) => { error ? reject(error) : resolve(cookie); @@ -551,7 +579,7 @@ export default class GMApi extends GM_Base { } @GMContext.API({ follow: "GM_cookie" }) - ["GM_cookie.set"]( + public "GM_cookie.set"( details: GMTypes.CookieDetails, done: (cookie: GMTypes.Cookie[] | any, error: any | undefined) => void ) { @@ -559,7 +587,7 @@ export default class GMApi extends GM_Base { } @GMContext.API({ follow: "GM_cookie" }) - ["GM_cookie.list"]( + public "GM_cookie.list"( details: GMTypes.CookieDetails, done: (cookie: GMTypes.Cookie[] | any, error: any | undefined) => void ) { @@ -567,7 +595,7 @@ export default class GMApi extends GM_Base { } @GMContext.API({ follow: "GM_cookie" }) - ["GM_cookie.delete"]( + public "GM_cookie.delete"( details: GMTypes.CookieDetails, done: (cookie: GMTypes.Cookie[] | any, error: any | undefined) => void ) { @@ -575,7 +603,7 @@ export default class GMApi extends GM_Base { } @GMContext.API() - GM_cookie( + public GM_cookie( action: string, details: GMTypes.CookieDetails, done: (cookie: GMTypes.Cookie[] | any, error: any | undefined) => void @@ -600,8 +628,8 @@ export default class GMApi extends GM_Base { // (同一环境跨脚本也不一样) contentEnvKey: string | undefined; - @GMContext.API({ alias: "GM.registerMenuCommand" }) - GM_registerMenuCommand( + @GMContext.API() + public GM_registerMenuCommand( name: string, listener?: (inputValue?: any) => void, options_or_accessKey?: ScriptMenuItemOption | string @@ -666,15 +694,25 @@ export default class GMApi extends GM_Base { return ret; } - @GMContext.API({ - depend: ["GM_registerMenuCommand"], - }) - CAT_registerMenuInput(...args: Parameters): TScriptMenuItemID { + @GMContext.API({ depend: ["GM_registerMenuCommand"] }) + public "GM.registerMenuCommand"( + name: string, + listener?: (inputValue?: any) => void, + options_or_accessKey?: ScriptMenuItemOption | string + ): Promise { + return new Promise((resolve) => { + const ret = this.GM_registerMenuCommand(name, listener, options_or_accessKey); + resolve(ret); + }); + } + + @GMContext.API({ depend: ["GM_registerMenuCommand"] }) + public CAT_registerMenuInput(...args: Parameters): TScriptMenuItemID { return this.GM_registerMenuCommand(...args); } - @GMContext.API({ alias: "GM.addStyle" }) - GM_addStyle(css: string) { + @GMContext.API() + public GM_addStyle(css: string): Element | undefined { if (!this.message || !this.scriptRes) return; if (typeof css !== "string") throw new Error("The parameter 'css' of GM_addStyle shall be a string."); // 与content页的消息通讯实际是同步,此方法不需要经过background @@ -696,15 +734,23 @@ export default class GMApi extends GM_Base { if (resp.code) { throw new Error(resp.message); } - return (this.message).getAndDelRelatedTarget(resp.data); + return (this.message).getAndDelRelatedTarget(resp.data) as Element; } - @GMContext.API({ alias: "GM.addElement" }) - GM_addElement( + @GMContext.API({ depend: ["GM_addStyle"] }) + public "GM.addStyle"(css: string): Promise { + return new Promise((resolve) => { + const ret = this.GM_addStyle(css); + resolve(ret); + }); + } + + @GMContext.API() + public GM_addElement( parentNode: Node | string, tagName: string | Record, attrs: Record = {} - ) { + ): Element | undefined { if (!this.message || !this.scriptRes) return; // 与content页的消息通讯实际是同步,此方法不需要经过background // 这里直接使用同步的方式去处理, 不要有promise @@ -730,11 +776,23 @@ export default class GMApi extends GM_Base { if (resp.code) { throw new Error(resp.message); } - return (this.message).getAndDelRelatedTarget(resp.data); + return (this.message).getAndDelRelatedTarget(resp.data) as Element; } - @GMContext.API({ alias: "GM.unregisterMenuCommand" }) - GM_unregisterMenuCommand(menuId: TScriptMenuItemID): void { + @GMContext.API({ depend: ["GM_addElement"] }) + public "GM.addElement"( + parentNode: Node | string, + tagName: string | Record, + attrs: Record = {} + ): Promise { + return new Promise((resolve) => { + const ret = this.GM_addElement(parentNode, tagName, attrs); + resolve(ret); + }); + } + + @GMContext.API() + public GM_unregisterMenuCommand(menuId: TScriptMenuItemID): void { if (!this.EE) return; if (!this.contentEnvKey) { return; @@ -747,22 +805,30 @@ export default class GMApi extends GM_Base { this.sendMessage("GM_unregisterMenuCommand", [menuKey] as GMUnRegisterMenuCommandParam); } + @GMContext.API({ depend: ["GM_unregisterMenuCommand"] }) + public "GM.unregisterMenuCommand"(menuId: TScriptMenuItemID): Promise { + return new Promise((resolve) => { + this.GM_unregisterMenuCommand(menuId); + resolve(); + }); + } + @GMContext.API({ depend: ["GM_unregisterMenuCommand"], }) - CAT_unregisterMenuInput(...args: Parameters): void { + public CAT_unregisterMenuInput(...args: Parameters): void { this.GM_unregisterMenuCommand(...args); } @GMContext.API() - CAT_userConfig() { + public CAT_userConfig() { return this.sendMessage("CAT_userConfig", []); } @GMContext.API({ - depend: ["CAT_fetchBlob", "CAT_createBlobUrl"], + depend: ["CAT_fetchBlob"], }) - async CAT_fileStorage(action: "list" | "download" | "upload" | "delete" | "config", details: any) { + public async CAT_fileStorage(action: "list" | "download" | "upload" | "delete" | "config", details: any) { if (action === "config") { this.sendMessage("CAT_fileStorage", ["config"]); return; @@ -801,16 +867,14 @@ export default class GMApi extends GM_Base { } // 用于脚本跨域请求,需要@connect domain指定允许的域名 - @GMContext.API({ - depend: ["CAT_fetchBlob", "CAT_createBlobUrl", "CAT_fetchDocument"], - }) + @GMContext.API() public GM_xmlhttpRequest(details: GMTypes.XHRDetails) { const { abort } = GM_xmlhttpRequest(this, details, false); return { abort }; } - @GMContext.API({ depend: ["CAT_fetchBlob", "CAT_createBlobUrl", "CAT_fetchDocument"] }) - public ["GM.xmlHttpRequest"](details: GMTypes.XHRDetails): Promise & GMRequestHandle { + @GMContext.API() + public "GM.xmlHttpRequest"(details: GMTypes.XHRDetails): Promise & GMRequestHandle { const { retPromise, abort } = GM_xmlhttpRequest(this, details, true); const ret = retPromise as Promise & GMRequestHandle; ret.abort = abort; @@ -824,7 +888,6 @@ export default class GMApi extends GM_Base { * native: 后台xhr下载 -> 后台chrome.download API,disabled: 禁止下载,browser: 后台chrome.download API * */ - @GMContext.API({ alias: "GM.download" }) static _GM_download(a: GMApi, details: GMTypes.DownloadDetails, requirePromise: boolean) { if (a.isInvalidContext()) { return { @@ -1030,7 +1093,7 @@ export default class GMApi extends GM_Base { } @GMContext.API() - public ["GM.download"](arg1: GMTypes.DownloadDetails | string, arg2?: string) { + public "GM.download"(arg1: GMTypes.DownloadDetails | string, arg2?: string) { const details = typeof arg1 === "string" ? { url: arg1, name: arg2 } : { ...arg1 }; const { retPromise, abort } = _GM_download(this, details as GMTypes.DownloadDetails, true); const ret = retPromise as Promise & GMRequestHandle; @@ -1038,16 +1101,13 @@ export default class GMApi extends GM_Base { return ret; } - @GMContext.API({ - depend: ["GM_closeNotification", "GM_updateNotification"], - alias: "GM.notification", - }) - public async GM_notification( + @GMContext.API() + public async "GM.notification"( detail: GMTypes.NotificationDetails | string, ondone?: GMTypes.NotificationOnDone | string, image?: string, onclick?: GMTypes.NotificationOnClick - ) { + ): Promise { if (this.isInvalidContext()) return; const notificationTagMap: Map = this.notificationTagMap || (this.notificationTagMap = new Map()); this.eventId += 1; @@ -1160,17 +1220,31 @@ export default class GMApi extends GM_Base { }); } + @GMContext.API({ + depend: ["GM.notification"], + }) + public GM_notification( + detail: GMTypes.NotificationDetails | string, + ondone?: GMTypes.NotificationOnDone | string, + image?: string, + onclick?: GMTypes.NotificationOnClick + ): void { + this["GM.notification"](detail, ondone, image, onclick); + } + + // ScriptCat 額外API @GMContext.API({ alias: "GM.closeNotification" }) public GM_closeNotification(id: string): void { this.sendMessage("GM_closeNotification", [id]); } + // ScriptCat 額外API @GMContext.API({ alias: "GM.updateNotification" }) public GM_updateNotification(id: string, details: GMTypes.NotificationDetails): void { this.sendMessage("GM_updateNotification", [id, details]); } - @GMContext.API({ depend: ["GM_closeInTab"], alias: "GM.openInTab" }) + @GMContext.API({ depend: ["GM_closeInTab"] }) public GM_openInTab(url: string, param?: GMTypes.OpenTabOptions | boolean): GMTypes.Tab | undefined { if (this.isInvalidContext()) return undefined; let option = {} as GMTypes.OpenTabOptions; @@ -1234,6 +1308,15 @@ export default class GMApi extends GM_Base { return ret; } + @GMContext.API({ depend: ["GM_openInTab", "GM_closeInTab"] }) + public "GM.openInTab"(url: string, param?: GMTypes.OpenTabOptions | boolean): Promise { + return new Promise((resolve) => { + const ret = this.GM_openInTab(url, param); + resolve(ret); + }); + } + + // ScriptCat 額外API @GMContext.API({ alias: "GM.closeInTab" }) public GM_closeInTab(tabid: string) { if (this.isInvalidContext()) return; @@ -1241,50 +1324,58 @@ export default class GMApi extends GM_Base { } @GMContext.API() - GM_getTab(callback: (data: any) => void) { + public GM_getTab(callback: (tabData: object) => void) { if (this.isInvalidContext()) return; - this.sendMessage("GM_getTab", []).then((data) => { - callback(data ?? {}); + this.sendMessage("GM_getTab", []).then((tabData) => { + callback(tabData ?? {}); }); } @GMContext.API({ depend: ["GM_getTab"] }) - public ["GM.getTab"](): Promise { - return new Promise((resolve) => { + public "GM.getTab"(): Promise { + return new Promise((resolve) => { this.GM_getTab((data) => { resolve(data); }); }); } - @GMContext.API({ alias: "GM.saveTab" }) - GM_saveTab(obj: object) { + @GMContext.API() + public GM_saveTab(tabData: object): void { if (this.isInvalidContext()) return; - if (typeof obj === "object") { - obj = JSON.parse(JSON.stringify(obj)); + if (typeof tabData === "object") { + tabData = JSON.parse(JSON.stringify(tabData)); } - this.sendMessage("GM_saveTab", [obj]); + this.sendMessage("GM_saveTab", [tabData]); + } + + @GMContext.API({ depend: ["GM_saveTab"] }) + public "GM.saveTab"(tabData: object): Promise { + return new Promise((resolve) => { + this.GM_saveTab(tabData); + resolve(); + }); } @GMContext.API() - GM_getTabs(callback: (objs: { [key: string | number]: object }) => any) { + public GM_getTabs(callback: (tabsData: { [key: number]: object }) => any) { if (this.isInvalidContext()) return; - this.sendMessage("GM_getTabs", []).then((resp) => { - callback(resp); + this.sendMessage("GM_getTabs", []).then((tabsData) => { + callback(tabsData); }); } @GMContext.API({ depend: ["GM_getTabs"] }) - public ["GM.getTabs"](): Promise<{ [key: string | number]: object }> { - return new Promise<{ [key: string | number]: object }>((resolve) => { - this.GM_getTabs((data) => { - resolve(data); + public "GM.getTabs"(): Promise<{ [key: number]: object }> { + return new Promise<{ [key: number]: object }>((resolve) => { + this.GM_getTabs((tabsData) => { + resolve(tabsData); }); }); } - @GMContext.API({}) - GM_setClipboard(data: string, info?: GMTypes.GMClipboardInfo, cb?: () => void) { + @GMContext.API() + public GM_setClipboard(data: string, info?: GMTypes.GMClipboardInfo, cb?: () => void) { if (this.isInvalidContext()) return; // 物件参数意义不明。日后再检视特殊处理 // 未支持 TM4.19+ application/octet-stream @@ -1312,7 +1403,7 @@ export default class GMApi extends GM_Base { } @GMContext.API({ depend: ["GM_setClipboard"] }) - ["GM.setClipboard"](data: string, info?: string | { type?: string; mimetype?: string }): Promise { + public "GM.setClipboard"(data: string, info?: string | { type?: string; mimetype?: string }): Promise { if (this.isInvalidContext()) return new Promise(() => {}); return new Promise((resolve) => { this.GM_setClipboard(data, info, () => { @@ -1322,7 +1413,7 @@ export default class GMApi extends GM_Base { } @GMContext.API() - GM_getResourceText(name: string): string | undefined { + public GM_getResourceText(name: string): string | undefined { const r = this.scriptRes?.resource?.[name]; if (r) { return r.content; @@ -1331,7 +1422,7 @@ export default class GMApi extends GM_Base { } @GMContext.API({ depend: ["GM_getResourceText"] }) - public ["GM.getResourceText"](name: string): Promise { + public "GM.getResourceText"(name: string): Promise { // Asynchronous wrapper for GM_getResourceText to support GM.getResourceText return new Promise((resolve) => { const ret = this.GM_getResourceText(name); @@ -1340,7 +1431,7 @@ export default class GMApi extends GM_Base { } @GMContext.API() - GM_getResourceURL(name: string, isBlobUrl?: boolean): string | undefined { + public GM_getResourceURL(name: string, isBlobUrl?: boolean): string | undefined { const r = this.scriptRes?.resource?.[name]; if (r) { let base64 = r.base64; @@ -1358,7 +1449,7 @@ export default class GMApi extends GM_Base { // GM_getResourceURL的异步版本,用来兼容GM.getResourceUrl @GMContext.API({ depend: ["GM_getResourceURL"] }) - public ["GM.getResourceUrl"](name: string, isBlobUrl?: boolean): Promise { + public "GM.getResourceUrl"(name: string, isBlobUrl?: boolean): Promise { // Asynchronous wrapper for GM_getResourceURL to support GM.getResourceURL return new Promise((resolve) => { const ret = this.GM_getResourceURL(name, isBlobUrl); @@ -1367,12 +1458,12 @@ export default class GMApi extends GM_Base { } @GMContext.API() - ["window.close"]() { + public "window.close"() { return this.sendMessage("window.close", []); } @GMContext.API() - ["window.focus"]() { + public "window.focus"() { return this.sendMessage("window.focus", []); } @@ -1380,7 +1471,7 @@ export default class GMApi extends GM_Base { apiLoadPromise: Promise | undefined; @GMContext.API() - CAT_scriptLoaded() { + public CAT_scriptLoaded() { return this.loadScriptPromise; } } diff --git a/src/app/service/service_worker/gm_api/gm_api.ts b/src/app/service/service_worker/gm_api/gm_api.ts index 1e5135605..04f5c5fc5 100644 --- a/src/app/service/service_worker/gm_api/gm_api.ts +++ b/src/app/service/service_worker/gm_api/gm_api.ts @@ -1037,7 +1037,9 @@ export default class GMApi { }); } - @PermissionVerify.API({}) + @PermissionVerify.API({ + link: ["GM_closeNotification", "GM_updateNotification"], + }) async GM_notification(request: GMApiRequest<[GMTypes.NotificationDetails, string | undefined]>, sender: IGetSender) { const details: GMTypes.NotificationDetails = request.params[0]; const notificationId: string | undefined = request.params[1]; @@ -1123,7 +1125,7 @@ export default class GMApi { } @PermissionVerify.API({ - link: ["GM_notification"], + link: ["GM_notification", "GM_updateNotification"], }) GM_closeNotification(request: GMApiRequest<[string]>, _sender: IGetSender) { const notificationId = request.params[0]; @@ -1135,7 +1137,7 @@ export default class GMApi { } @PermissionVerify.API({ - link: ["GM_notification"], + link: ["GM_notification", "GM_closeNotification"], }) GM_updateNotification(request: GMApiRequest<[string, GMTypes.NotificationDetails]>, _sender: IGetSender) { if (typeof chrome.notifications?.update !== "function") { From bf4ad73b1ce58abb85cf860c4fdb041738b59191 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:41:22 +0900 Subject: [PATCH 2/4] Update src/app/service/content/gm_api/gm_api.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/app/service/content/gm_api/gm_api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/service/content/gm_api/gm_api.ts b/src/app/service/content/gm_api/gm_api.ts index 19c81e967..d29220c6d 100644 --- a/src/app/service/content/gm_api/gm_api.ts +++ b/src/app/service/content/gm_api/gm_api.ts @@ -1232,13 +1232,13 @@ export default class GMApi extends GM_Base { this["GM.notification"](detail, ondone, image, onclick); } - // ScriptCat 額外API + // ScriptCat 额外API @GMContext.API({ alias: "GM.closeNotification" }) public GM_closeNotification(id: string): void { this.sendMessage("GM_closeNotification", [id]); } - // ScriptCat 額外API + // ScriptCat 额外API @GMContext.API({ alias: "GM.updateNotification" }) public GM_updateNotification(id: string, details: GMTypes.NotificationDetails): void { this.sendMessage("GM_updateNotification", [id, details]); From eba859fad9b7dc40c705bcfa36a0ed0e2320029c Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:41:30 +0900 Subject: [PATCH 3/4] Update src/app/service/content/gm_api/gm_api.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/app/service/content/gm_api/gm_api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/service/content/gm_api/gm_api.ts b/src/app/service/content/gm_api/gm_api.ts index d29220c6d..513f9f94c 100644 --- a/src/app/service/content/gm_api/gm_api.ts +++ b/src/app/service/content/gm_api/gm_api.ts @@ -1316,7 +1316,7 @@ export default class GMApi extends GM_Base { }); } - // ScriptCat 額外API + // ScriptCat 额外API @GMContext.API({ alias: "GM.closeInTab" }) public GM_closeInTab(tabid: string) { if (this.isInvalidContext()) return; From 819b8a9a69b7c6fe0e85b6d33b1af11c2c9a437b Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:44:35 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=E9=80=80=E5=9B=9E=20`src/app/service/servi?= =?UTF-8?q?ce=5Fworker/gm=5Fapi/gm=5Fapi.ts`=20=E6=94=B9=E5=8F=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/service_worker/gm_api/gm_api.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/app/service/service_worker/gm_api/gm_api.ts b/src/app/service/service_worker/gm_api/gm_api.ts index 04f5c5fc5..1e5135605 100644 --- a/src/app/service/service_worker/gm_api/gm_api.ts +++ b/src/app/service/service_worker/gm_api/gm_api.ts @@ -1037,9 +1037,7 @@ export default class GMApi { }); } - @PermissionVerify.API({ - link: ["GM_closeNotification", "GM_updateNotification"], - }) + @PermissionVerify.API({}) async GM_notification(request: GMApiRequest<[GMTypes.NotificationDetails, string | undefined]>, sender: IGetSender) { const details: GMTypes.NotificationDetails = request.params[0]; const notificationId: string | undefined = request.params[1]; @@ -1125,7 +1123,7 @@ export default class GMApi { } @PermissionVerify.API({ - link: ["GM_notification", "GM_updateNotification"], + link: ["GM_notification"], }) GM_closeNotification(request: GMApiRequest<[string]>, _sender: IGetSender) { const notificationId = request.params[0]; @@ -1137,7 +1135,7 @@ export default class GMApi { } @PermissionVerify.API({ - link: ["GM_notification", "GM_closeNotification"], + link: ["GM_notification"], }) GM_updateNotification(request: GMApiRequest<[string, GMTypes.NotificationDetails]>, _sender: IGetSender) { if (typeof chrome.notifications?.update !== "function") {