From 5483cb94e7a0e7c56f807e704bf276d3782f3432 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Fri, 14 Nov 2025 21:06:14 +0900 Subject: [PATCH 1/9] =?UTF-8?q?=E6=95=B4=E7=90=86=20inject=20&=20content?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/content/content.ts | 37 ++++++++++------------ src/app/service/content/inject.ts | 24 ++++++++------ src/app/service/content/script_executor.ts | 10 +++--- src/inject.ts | 18 ++++++----- 4 files changed, 46 insertions(+), 43 deletions(-) diff --git a/src/app/service/content/content.ts b/src/app/service/content/content.ts index 314d8c1e3..20b5791e4 100644 --- a/src/app/service/content/content.ts +++ b/src/app/service/content/content.ts @@ -11,20 +11,20 @@ import { RuntimeClient } from "../service_worker/client"; // content页的处理 export default class ContentRuntime { // 运行在content页面的脚本 - contentScript: Map = new Map(); + private readonly contentScriptSet: Set = new Set(); constructor( // 监听来自service_worker的消息 - private extServer: Server, + private readonly extServer: Server, // 监听来自inject的消息 - private server: Server, + private readonly server: Server, // 发送给扩展service_worker的通信接口 - private senderToExt: MessageSend, + private readonly senderToExt: MessageSend, // 发送给inject的消息接口 - private senderToInject: CustomEventMessage, + private readonly senderToInject: CustomEventMessage, // 脚本执行器消息接口 - private scriptExecutorMsg: CustomEventMessage, - private scriptExecutor: ScriptExecutor + private readonly scriptExecutorMsg: CustomEventMessage, + private readonly scriptExecutor: ScriptExecutor ) {} init() { @@ -76,7 +76,7 @@ export default class ContentRuntime { let parentNode: EventTarget | undefined; // 判断是不是content脚本发过来的 let msg: CustomEventMessage; - if (this.contentScript.has(data.uuid) || this.scriptExecutor.execMap.has(data.uuid)) { + if (this.contentScriptSet.has(data.uuid) || this.scriptExecutor.execMap.has(data.uuid)) { msg = this.scriptExecutorMsg; } else { msg = this.senderToInject; @@ -140,24 +140,21 @@ export default class ContentRuntime { // 启动脚本 const client = new Client(this.senderToInject, "inject"); // 根据@inject-into content过滤脚本 - const injectScript: ScriptLoadInfo[] = []; - const contentScript: ScriptLoadInfo[] = []; + const injectScriptList: ScriptLoadInfo[] = []; + const contentScriptList: ScriptLoadInfo[] = []; for (const script of scripts) { - if (isInjectIntoContent(script.metadata)) { - contentScript.push(script); - continue; - } - injectScript.push(script); + const list = isInjectIntoContent(script.metadata) ? contentScriptList : injectScriptList; + list.push(script); } - client.do("pageLoad", { scripts: injectScript, envInfo }); + client.do("pageLoad", { scripts: injectScriptList, envInfo }); // 处理注入到content环境的脚本 - for (const script of contentScript) { - this.contentScript.set(script.uuid, script); + for (const script of contentScriptList) { + this.contentScriptSet.add(script.uuid); } // 监听事件 - this.scriptExecutor.init(envInfo); + this.scriptExecutor.setEnvInfo(envInfo); // 启动脚本 - this.scriptExecutor.start(contentScript); + this.scriptExecutor.startScripts(contentScriptList); } } diff --git a/src/app/service/content/inject.ts b/src/app/service/content/inject.ts index 916c101ad..4c079f189 100644 --- a/src/app/service/content/inject.ts +++ b/src/app/service/content/inject.ts @@ -8,14 +8,12 @@ import type { GMInfoEnv, ValueUpdateDataEncoded } from "./types"; export class InjectRuntime { constructor( - private server: Server, - private msg: Message, - private scriptExecutor: ScriptExecutor + private readonly server: Server, + private readonly msg: Message, + private readonly scriptExecutor: ScriptExecutor ) {} - init(envInfo: GMInfoEnv) { - this.scriptExecutor.init(envInfo); - + init() { this.server.on("runtime/emitEvent", (data: EmitEventRequest) => { // 转发给脚本 this.scriptExecutor.emitEvent(data); @@ -23,13 +21,19 @@ export class InjectRuntime { this.server.on("runtime/valueUpdate", (data: ValueUpdateDataEncoded) => { this.scriptExecutor.valueUpdate(data); }); + } - // 注入允许外部调用 - this.externalMessage(); + setEnvInfo(envInfo: GMInfoEnv) { + this.scriptExecutor.setEnvInfo(envInfo); } - start(scripts: ScriptLoadInfo[]) { - this.scriptExecutor.start(scripts); + startScripts(scripts: ScriptLoadInfo[]) { + this.scriptExecutor.startScripts(scripts); + } + + onInjectPageLoaded() { + // 注入允许外部调用 + this.externalMessage(); } externalMessage() { diff --git a/src/app/service/content/script_executor.ts b/src/app/service/content/script_executor.ts index c353d314c..be4e10f9f 100644 --- a/src/app/service/content/script_executor.ts +++ b/src/app/service/content/script_executor.ts @@ -21,7 +21,7 @@ export class ScriptExecutor { constructor(private msg: Message) {} - init(envInfo: GMInfoEnv) { + setEnvInfo(envInfo: GMInfoEnv) { this.envInfo = envInfo; } @@ -42,7 +42,7 @@ export class ScriptExecutor { } } - start(scripts: ScriptLoadInfo[]) { + startScripts(scripts: ScriptLoadInfo[]) { const loadExec = (script: ScriptLoadInfo, scriptFunc: any) => { this.execScriptEntry({ scriptLoadInfo: script, @@ -76,16 +76,16 @@ export class ScriptExecutor { // 适用于此「通知环境加载完成」代码执行后的脚本加载 window.addEventListener(`${eventNamePrefix}${messageFlags.scriptLoadComplete}`, (event) => { if (event instanceof CustomEvent) { - if (typeof event.detail.scriptFlag === "string") { + const scriptFlag = event.detail?.scriptFlag; + if (typeof scriptFlag === "string") { event.preventDefault(); // dispatchEvent 会回传 false -> 分离环境也能得知环境加载代码已执行 - const scriptFlag = event.detail.scriptFlag; this.execEarlyScript(scriptFlag); } } }); // 通知 环境 加载完成 // 适用于此「通知环境加载完成」代码执行前的脚本加载 - const ev = new CustomEvent(eventNamePrefix + messageFlags.envLoadComplete); + const ev = new CustomEvent(`${eventNamePrefix}${messageFlags.envLoadComplete}`); window.dispatchEvent(ev); } diff --git a/src/inject.ts b/src/inject.ts index 04aed52e7..b109f7a31 100644 --- a/src/inject.ts +++ b/src/inject.ts @@ -12,21 +12,23 @@ import type { Message } from "@Packages/message/types"; const msg: Message = new CustomEventMessage(MessageFlags, false); -// 加载logger组件 -const logger = new LoggerCore({ - writer: new MessageWriter(msg), - labels: { env: "inject", href: window.location.href }, -}); - const server = new Server("inject", msg); const scriptExecutor = new ScriptExecutor(msg); const runtime = new InjectRuntime(server, msg, scriptExecutor); +runtime.init(); // 检查early-start的脚本 scriptExecutor.checkEarlyStartScript("inject", MessageFlags); +// 加载logger组件 +const logger = new LoggerCore({ + writer: new MessageWriter(msg), + labels: { env: "inject", href: window.location.href }, +}); + server.on("pageLoad", (data: { scripts: ScriptLoadInfo[]; envInfo: GMInfoEnv }) => { logger.logger().debug("inject start"); // 监听事件 - runtime.init(data.envInfo); - runtime.start(data.scripts); + runtime.setEnvInfo(data.envInfo); + runtime.startScripts(data.scripts); + runtime.onInjectPageLoaded(); }); From 0bf3b5486a5acff519166374231f6ca502db36c5 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 15 Nov 2025 11:10:59 +0900 Subject: [PATCH 2/9] =?UTF-8?q?=E4=B8=8D=E9=9C=80=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E6=98=AF=E5=90=A6=20CustomEvent,=20=E5=91=BD=E5=90=8D=E7=AE=80?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/content/script_executor.ts | 12 +++++------- src/app/service/content/utils.ts | 8 ++++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/app/service/content/script_executor.ts b/src/app/service/content/script_executor.ts index be4e10f9f..bb12af62e 100644 --- a/src/app/service/content/script_executor.ts +++ b/src/app/service/content/script_executor.ts @@ -74,13 +74,11 @@ export class ScriptExecutor { const eventNamePrefix = env === "content" ? messageFlags.contentFlag : messageFlags.injectFlag; // 监听 脚本加载 // 适用于此「通知环境加载完成」代码执行后的脚本加载 - window.addEventListener(`${eventNamePrefix}${messageFlags.scriptLoadComplete}`, (event) => { - if (event instanceof CustomEvent) { - const scriptFlag = event.detail?.scriptFlag; - if (typeof scriptFlag === "string") { - event.preventDefault(); // dispatchEvent 会回传 false -> 分离环境也能得知环境加载代码已执行 - this.execEarlyScript(scriptFlag); - } + window.addEventListener(`${eventNamePrefix}${messageFlags.scriptLoadComplete}`, (ev) => { + const scriptFlag = (ev as CustomEvent).detail?.scriptFlag; + if (typeof scriptFlag === "string") { + ev.preventDefault(); // dispatchEvent 会回传 false -> 分离环境也能得知环境加载代码已执行 + this.execEarlyScript(scriptFlag); } }); // 通知 环境 加载完成 diff --git a/src/app/service/content/utils.ts b/src/app/service/content/utils.ts index a0c603320..202d6ce58 100644 --- a/src/app/service/content/utils.ts +++ b/src/app/service/content/utils.ts @@ -112,12 +112,12 @@ export function compilePreInjectScript( }; (() => { const f = () => { - const event = new CustomEvent('${eventNamePrefix}${messageFlags.scriptLoadComplete}', + const ev = new CustomEvent('${eventNamePrefix}${messageFlags.scriptLoadComplete}', { cancelable: true, detail: { scriptFlag: '${script.flag}' } }); - return window.dispatchEvent(event); + return window.dispatchEvent(ev); }; - const noCheckEarlyStartScript = f(); - if (noCheckEarlyStartScript) { + const noCbHooked = f(); + if (noCbHooked) { window.addEventListener('${eventNamePrefix}${messageFlags.envLoadComplete}', f, { once: true }); } })(); From 498cd960f8e87e7230085b67f9b9e0d0af83de68 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 15 Nov 2025 11:31:56 +0900 Subject: [PATCH 3/9] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20=E5=8A=A0=E8=BD=BDlogg?= =?UTF-8?q?er=E7=BB=84=E4=BB=B6=20=E6=AC=A1=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/inject.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/inject.ts b/src/inject.ts index b109f7a31..eec46460c 100644 --- a/src/inject.ts +++ b/src/inject.ts @@ -12,6 +12,12 @@ import type { Message } from "@Packages/message/types"; const msg: Message = new CustomEventMessage(MessageFlags, false); +// 加载logger组件 +const logger = new LoggerCore({ + writer: new MessageWriter(msg), + labels: { env: "inject", href: window.location.href }, +}); + const server = new Server("inject", msg); const scriptExecutor = new ScriptExecutor(msg); const runtime = new InjectRuntime(server, msg, scriptExecutor); @@ -19,12 +25,6 @@ runtime.init(); // 检查early-start的脚本 scriptExecutor.checkEarlyStartScript("inject", MessageFlags); -// 加载logger组件 -const logger = new LoggerCore({ - writer: new MessageWriter(msg), - labels: { env: "inject", href: window.location.href }, -}); - server.on("pageLoad", (data: { scripts: ScriptLoadInfo[]; envInfo: GMInfoEnv }) => { logger.logger().debug("inject start"); // 监听事件 From cf0d22cf38df06d42643b1c43bc1909c82c808b2 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 15 Nov 2025 12:35:01 +0900 Subject: [PATCH 4/9] =?UTF-8?q?=E4=BC=98=E5=8C=96EarlyStart=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/content/script_executor.ts | 11 ++++---- src/app/service/content/utils.ts | 31 +++++++++++----------- src/app/service/service_worker/runtime.ts | 15 ++++++++++- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/app/service/content/script_executor.ts b/src/app/service/content/script_executor.ts index bb12af62e..b7d62acb8 100644 --- a/src/app/service/content/script_executor.ts +++ b/src/app/service/content/script_executor.ts @@ -75,10 +75,11 @@ export class ScriptExecutor { // 监听 脚本加载 // 适用于此「通知环境加载完成」代码执行后的脚本加载 window.addEventListener(`${eventNamePrefix}${messageFlags.scriptLoadComplete}`, (ev) => { - const scriptFlag = (ev as CustomEvent).detail?.scriptFlag; + const detail = (ev as CustomEvent).detail; + const scriptFlag = detail?.scriptFlag; if (typeof scriptFlag === "string") { ev.preventDefault(); // dispatchEvent 会回传 false -> 分离环境也能得知环境加载代码已执行 - this.execEarlyScript(scriptFlag); + this.execEarlyScript(scriptFlag, detail.scriptInfo); } }); // 通知 环境 加载完成 @@ -87,11 +88,11 @@ export class ScriptExecutor { window.dispatchEvent(ev); } - execEarlyScript(flag: string) { + execEarlyScript(flag: string, scriptInfo: ScriptLoadInfo) { const scriptFunc = (window as any)[flag] as PreScriptFunc; this.execScriptEntry({ - scriptLoadInfo: scriptFunc.scriptInfo, - scriptFunc: scriptFunc.func, + scriptLoadInfo: scriptInfo, + scriptFunc: scriptFunc, scriptFlag: flag, envInfo: {}, }); diff --git a/src/app/service/content/utils.ts b/src/app/service/content/utils.ts index 202d6ce58..0f3c7a34f 100644 --- a/src/app/service/content/utils.ts +++ b/src/app/service/content/utils.ts @@ -105,22 +105,21 @@ export function compilePreInjectScript( autoDeleteMountFunction: boolean = false ): string { const eventNamePrefix = isInjectIntoContent(script.metadata) ? messageFlags.contentFlag : messageFlags.injectFlag; - const autoDeleteMountCode = autoDeleteMountFunction ? `try{delete window['${script.flag}']}catch(e){}` : ""; - return `window['${script.flag}'] = { - scriptInfo: ${JSON.stringify(script)}, - func: function(){${autoDeleteMountCode}${scriptCode}} -}; -(() => { - const f = () => { - const ev = new CustomEvent('${eventNamePrefix}${messageFlags.scriptLoadComplete}', - { cancelable: true, detail: { scriptFlag: '${script.flag}' } }); - return window.dispatchEvent(ev); - }; - const noCbHooked = f(); - if (noCbHooked) { - window.addEventListener('${eventNamePrefix}${messageFlags.envLoadComplete}', f, { once: true }); - } -})(); + const flag = `${script.flag}`; + const scriptInfo = { ...script }; // clone + // 不需要 code + scriptInfo.code = ""; + const scriptInfoJSON = `${JSON.stringify(scriptInfo)}`; + const autoDeleteMountCode = autoDeleteMountFunction ? `try{delete window['${flag}']}catch(e){}` : ""; + const evScriptLoad = `${eventNamePrefix}${messageFlags.scriptLoadComplete}`; + const evEnvLoad = `${eventNamePrefix}${messageFlags.envLoadComplete}`; + return `window['${flag}'] = function(){${autoDeleteMountCode}${scriptCode}}; +{ + let o = { cancelable: true, detail: { scriptFlag: '${flag}', scriptInfo: (${scriptInfoJSON}) } }, + f = () => window.dispatchEvent(new CustomEvent('${evScriptLoad}', o)), + needWait = f(); + if (needWait) window.addEventListener('${evEnvLoad}', f, { once: true }); +} `; } diff --git a/src/app/service/service_worker/runtime.ts b/src/app/service/service_worker/runtime.ts index 0129dd99b..fe2d35a0a 100644 --- a/src/app/service/service_worker/runtime.ts +++ b/src/app/service/service_worker/runtime.ts @@ -43,6 +43,10 @@ import type { CompiledResource, ResourceType } from "@App/app/repo/resource"; import { CompiledResourceDAO } from "@App/app/repo/resource"; import { setOnTabURLChanged } from "./url_monitor"; +// 避免使用版本号控制导致代码理解混乱 +// 用来清除 UserScript API 里的旧缓存 +const USERSCRIPTS_REGISTER_CONTROL = "0f5b5b01-eef8-4505-9a8e-b2fc416b2f63"; + const ORIGINAL_URLMATCH_SUFFIX = "{ORIGINAL}"; // 用于标记原始URLPatterns的后缀 const runtimeGlobal = { @@ -241,12 +245,21 @@ export class RuntimeService { } async waitInit() { - const [cRuntimeStartFlag, compiledResources, allScripts] = await Promise.all([ + const [registerControl, cRuntimeStartFlag, compiledResources, allScripts] = await Promise.all([ + chrome.storage.local.get("userscripts_register_control"), cacheInstance.get("runtimeStartFlag"), this.compiledResourceDAO.all(), this.scriptDAO.all(), ]); + if (registerControl?.userscripts_register_control !== USERSCRIPTS_REGISTER_CONTROL) { + await Promise.allSettled([ + chrome.userScripts.unregister(), + chrome.scripting.unregisterContentScripts(), + chrome.storage.local.set({ userscripts_register_control: USERSCRIPTS_REGISTER_CONTROL }), + ]); + } + const unregisterScriptIds = [] as string[]; // 没有 CompiledResources 表示这是 没有启用脚本 或 代码有改变需要重新安装。 // 这个情况会把所有有效脚本跟Inject&Content脚本先取消注册。后续载入时会重新以新代码注册。 From 220eee84819de4d24c6ac505a376b967de800ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sat, 15 Nov 2025 13:26:54 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E6=94=B9=E4=B8=AA=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 37327cb44..086a4bfd4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scriptcat", - "version": "1.2.0-beta.4", + "version": "1.2.0-beta.5", "description": "脚本猫,一个可以执行用户脚本的浏览器扩展,万物皆可脚本化,让你的浏览器可以做更多的事情!", "author": "CodFrm", "license": "GPLv3", diff --git a/src/manifest.json b/src/manifest.json index eb589691f..7aea65769 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "__MSG_scriptcat__", - "version": "1.2.0.1500", + "version": "1.2.0.1600", "author": "CodFrm", "description": "__MSG_scriptcat_description__", "options_ui": { From 2e58425dfc36c55be95f16ad8f0c7e98f0c11c88 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 15 Nov 2025 17:04:47 +0900 Subject: [PATCH 6/9] =?UTF-8?q?=E9=87=8D=E6=9E=84=20pageLoad=20=E5=A4=84?= =?UTF-8?q?=E7=90=86=EF=BC=8C=E5=87=8F=E5=B0=91=E4=B8=8D=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E7=9A=84=E8=B5=84=E8=AE=AF=E4=BC=A0=E9=80=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1 --- src/app/repo/scripts.ts | 41 +++++++ src/app/service/content/content.ts | 45 +++---- src/app/service/content/create_context.ts | 4 +- src/app/service/content/exec_script.ts | 6 +- src/app/service/content/gm_info.ts | 7 +- src/app/service/content/inject.ts | 7 +- src/app/service/content/script_executor.ts | 15 +-- src/app/service/content/types.ts | 3 - src/app/service/content/utils.ts | 39 +++++- src/app/service/service_worker/client.ts | 14 +-- src/app/service/service_worker/popup.ts | 53 +++----- .../service_worker/popup_scriptmenu.ts | 24 ++++ src/app/service/service_worker/runtime.ts | 116 +++++++++++++----- src/app/service/service_worker/types.ts | 15 +-- src/inject.ts | 6 +- src/pkg/utils/utils.ts | 4 +- src/types/main.d.ts | 2 + 17 files changed, 253 insertions(+), 148 deletions(-) create mode 100644 src/app/service/service_worker/popup_scriptmenu.ts diff --git a/src/app/repo/scripts.ts b/src/app/repo/scripts.ts index bb4b562cb..de32d2308 100644 --- a/src/app/repo/scripts.ts +++ b/src/app/repo/scripts.ts @@ -1,6 +1,7 @@ import { Repo } from "./repo"; import type { Resource } from "./resource"; import type { SCMetadata } from "./metadata"; +import type { GMInfoEnv } from "../service/content/types"; // 脚本模型 export type SCRIPT_TYPE = 1 | 2 | 3; @@ -101,6 +102,46 @@ export interface ScriptRunResource extends Script { originalMetadata: SCMetadata; // 原本的 Metadata (目前只需要 match, include, exclude) } +/** + * 脚本加载信息。( service_worker / sandbox / popup 环境用 ) + * 包含脚本元数据与用户配置。 + */ +export interface ScriptLoadInfo extends ScriptRunResource { + /** 脚本元数据字符串 */ + metadataStr: string; + /** 用户配置字符串 */ + userConfigStr: string; + /** 用户配置对象(可选) */ + userConfig?: UserConfig; +} + +/** + * 脚本加载信息。( Inject / Content 环境用,避免过多不必要资讯公开,减少页面加载资讯储存量 ) + * 包含脚本元数据与用户配置。 + */ +export type TScriptInfo = Override< + ScriptLoadInfo, + { + originalMetadata?: Partial>; + resource: Record; + code: "" | string; + sort?: number; + flag: string; + runStatus?: SCRIPT_RUN_STATUS; + type?: SCRIPT_TYPE; + status?: SCRIPT_STATUS; + } +>; + +export type TClientPageLoadInfo = + | { + ok: true; + injectScriptList: TScriptInfo[]; + contentScriptList: TScriptInfo[]; + envInfo: GMInfoEnv; + } + | { ok: false }; + export class ScriptDAO extends Repo