From 616aa8f38f76494c7c98c6e071678dcb2314b57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sun, 26 Oct 2025 23:55:18 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=20EarlyStart=20=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/content/content.ts | 9 +-- src/app/service/content/script_executor.ts | 42 ++++++---- src/app/service/content/utils.ts | 22 +++++- src/app/service/queue.ts | 2 +- src/app/service/service_worker/runtime.ts | 90 +++------------------- src/app/service/service_worker/script.ts | 5 +- src/app/service/service_worker/utils.ts | 4 +- src/content.ts | 3 +- src/inject.ts | 4 +- 9 files changed, 70 insertions(+), 111 deletions(-) diff --git a/src/app/service/content/content.ts b/src/app/service/content/content.ts index 741f912be..90774ceab 100644 --- a/src/app/service/content/content.ts +++ b/src/app/service/content/content.ts @@ -5,7 +5,7 @@ import type { MessageSend } from "@Packages/message/types"; import type { GMInfoEnv } from "./types"; import type { ScriptLoadInfo } from "../service_worker/types"; import type { ScriptExecutor } from "./script_executor"; -import { definePropertyListener, isInjectIntoContent } from "./utils"; +import { isInjectIntoContent } from "./utils"; import { RuntimeClient } from "../service_worker/client"; // content页的处理 @@ -126,11 +126,8 @@ export default class ContentRuntime { ); } - pageLoad() { - // 处理content EarlyScript - definePropertyListener(window, "EarlyScriptFlag", (flag: string[]) => { - this.scriptExecutor.checkEarlyStartScript(flag); - }); + pageLoad(messageFlag: string) { + this.scriptExecutor.checkEarlyStartScript("content", messageFlag); const client = new RuntimeClient(this.senderToExt); // 向service_worker请求脚本列表 diff --git a/src/app/service/content/script_executor.ts b/src/app/service/content/script_executor.ts index 82e122771..ad999b6a6 100644 --- a/src/app/service/content/script_executor.ts +++ b/src/app/service/content/script_executor.ts @@ -71,20 +71,34 @@ export class ScriptExecutor { }); } - checkEarlyStartScript(earlyStarFlag: string[]) { - this.earlyScriptFlag = earlyStarFlag; - const loadExec = (flag: string, scriptFunc: any) => { - this.execScriptEntry({ - scriptLoadInfo: scriptFunc.scriptInfo, - scriptFunc: scriptFunc.func, - scriptFlag: flag, - envInfo: {}, - }); - }; - this.earlyScriptFlag.forEach((flag) => { - definePropertyListener(window, flag, (val: PreScriptFunc) => { - loadExec(flag, val); - }); + checkEarlyStartScript(env: "content" | "inject", eventFlag: string) { + // 监听 脚本加载 + window.addEventListener(`sc${eventFlag}`, (event) => { + if (event instanceof CustomEvent && event.detail.callback) { + const flag = event.detail.callback(); + console.log("Early start script detected:", flag); + this.execEarlyScript(flag); + } + }); + // 通知 环境 加载完成 + const ev = new CustomEvent(`${env === "content" ? "ct" : "fd"}ld${eventFlag}`, { + detail: { + callback: (flag: string) => { + console.log("Notify early start script:", flag); + this.execEarlyScript(flag); + }, + }, + }); + window.dispatchEvent(ev); + } + + execEarlyScript(flag: string) { + const scriptFunc = (window as any)[flag] as PreScriptFunc; + this.execScriptEntry({ + scriptLoadInfo: scriptFunc.scriptInfo, + scriptFunc: scriptFunc.func, + scriptFlag: flag, + envInfo: {}, }); } diff --git a/src/app/service/content/utils.ts b/src/app/service/content/utils.ts index 5078e440e..f72025e2d 100644 --- a/src/app/service/content/utils.ts +++ b/src/app/service/content/utils.ts @@ -99,15 +99,35 @@ export function compileInjectScriptByFlag( * 将脚本函数编译为预注入脚本代码 */ export function compilePreInjectScript( + messageFlag: string, script: ScriptLoadInfo, scriptCode: string, autoDeleteMountFunction: boolean = false ): string { + const eventName = isInjectIntoContent(script.metadata) ? "ct" : "fd"; const autoDeleteMountCode = autoDeleteMountFunction ? `try{delete window['${script.flag}']}catch(e){}` : ""; return `window['${script.flag}'] = { scriptInfo: ${JSON.stringify(script)}, func: function(){${autoDeleteMountCode}${scriptCode}} - }`; +}; +(() => { + let eventListener = (ev) => { + if (ev.detail && ev.detail.callback && !loaded) { + ev.detail.callback('${script.flag}'); + } + } + window.addEventListener('${eventName}ld${messageFlag}', eventListener); + let loaded = false; + const callback = () => { + if (loaded) return; + loaded = true; + window.removeEventListener('${eventName}ld${messageFlag}', eventListener); + return '${script.flag}'; + } + const event = new CustomEvent('sc${messageFlag}', { detail: { callback } }); + window.dispatchEvent(event); +})(); +`; } export function addStyle(css: string): HTMLStyleElement { diff --git a/src/app/service/queue.ts b/src/app/service/queue.ts index 4a296f7a3..c9c2a2b74 100644 --- a/src/app/service/queue.ts +++ b/src/app/service/queue.ts @@ -20,7 +20,7 @@ export type TInstallScriptParams = { export type TInstallScript = { script: TInstallScriptParams; update: boolean; upsertBy?: InstallSource }; -export type TDeleteScript = { uuid: string; storageName: string; type: SCRIPT_TYPE; isEarlyStart: boolean }; +export type TDeleteScript = { uuid: string; storageName: string; type: SCRIPT_TYPE }; export type TSortedScript = { uuid: string; sort: number }; diff --git a/src/app/service/service_worker/runtime.ts b/src/app/service/service_worker/runtime.ts index 2acffc25e..bff1c5361 100644 --- a/src/app/service/service_worker/runtime.ts +++ b/src/app/service/service_worker/runtime.ts @@ -27,12 +27,7 @@ import { UrlMatch } from "@App/pkg/utils/match"; import { ExtensionContentMessageSend } from "@Packages/message/extension_message"; import { sendMessage } from "@Packages/message/client"; import type { CompileScriptCodeResource } from "../content/utils"; -import { - compileInjectScriptByFlag, - compileScriptCodeByResource, - getScriptFlag, - isEarlyStartScript, -} from "../content/utils"; +import { compileInjectScriptByFlag, compileScriptCodeByResource, isEarlyStartScript } from "../content/utils"; import LoggerCore from "@App/app/logger/core"; import PermissionVerify from "./permission_verify"; import { type SystemConfig } from "@App/pkg/config/config"; @@ -56,7 +51,6 @@ const runtimeGlobal = { }; export class RuntimeService { - earlyScriptFlags = new Set(); scriptMatchEnable: UrlMatch = new UrlMatch(); scriptMatchDisable: UrlMatch = new UrlMatch(); blackMatch: UrlMatch = new UrlMatch(); @@ -244,12 +238,6 @@ export class RuntimeService { const isNormalScript = script.type === SCRIPT_TYPE_NORMAL; const enable = script.status === SCRIPT_STATUS_ENABLE; - if (isNormalScript && enable && isEarlyStartScript(script.metadata)) { - this.earlyScriptFlags.add(uuid); - } else { - this.earlyScriptFlags.delete(uuid); - } - if (!isNormalScript || !enable) { // 确保浏览器没有残留 PageScripts if (uuid) unregisterScriptIds.push(uuid); @@ -281,7 +269,7 @@ export class RuntimeService { ); if (cleanUpPreviousRegister) { // 先反注册残留脚本 - unregisterScriptIds.push("scriptcat-early-start-flag", "scriptcat-inject", "scriptcat-content"); + unregisterScriptIds.push("scriptcat-content-flag", "scriptcat-inject", "scriptcat-content"); } if (unregisterScriptIds.length) { // 忽略 UserScripts API 无法执行 @@ -333,7 +321,6 @@ export class RuntimeService { // 监听脚本开启 this.mq.subscribe("enableScripts", async (data) => { - let needReRegisterInjectJS = false; const unregisteyUuids = [] as string[]; for (const { uuid, enable } of data) { const script = await this.scriptDAO.get(uuid); @@ -354,16 +341,6 @@ export class RuntimeService { // 如果是后台脚本, 在offscreen中进行处理 // 脚本类别不会更改 if (script.type === SCRIPT_TYPE_NORMAL) { - const isCurrentEarlyStart = this.earlyScriptFlags.has(uuid); - const isEarlyStart = isEarlyStartScript(script.metadata); - if (isEarlyStart && enable) { - this.earlyScriptFlags.add(uuid); - } else { - this.earlyScriptFlags.delete(uuid); - } - if (isEarlyStart || isCurrentEarlyStart !== isEarlyStart) { - needReRegisterInjectJS = true; - } // 加载页面脚本 if (enable) { await this.updateResourceOnScriptChange(script); @@ -373,7 +350,6 @@ export class RuntimeService { } } await this.unregistryPageScripts(unregisteyUuids); - if (needReRegisterInjectJS) await this.reRegisterInjectScript(); }); // 监听脚本安装 @@ -387,45 +363,27 @@ export class RuntimeService { } // 代码更新时脚本类别不会更改 if (script.type === SCRIPT_TYPE_NORMAL) { - const needReRegisterInjectJS = isEarlyStartScript(script.metadata); const enable = script.status === SCRIPT_STATUS_ENABLE; - if (needReRegisterInjectJS && enable) { - this.earlyScriptFlags.add(script.uuid); - } else { - this.earlyScriptFlags.delete(script.uuid); - } if (enable) { await this.updateResourceOnScriptChange(script); } else { // 还是要建立 CompiledResoure, 否则 Popup 看不到 Script await this.buildAndSaveCompiledResourceFromScript(script, false); } - // 初始化会把所有的脚本flag注入,所以只用安装和卸载时重新注入flag - // 不是 earlyStart 的不用重新注入 (没有改变) - if (needReRegisterInjectJS) await this.reRegisterInjectScript(); } }); // 监听脚本删除 this.mq.subscribe("deleteScripts", async (data) => { - let needReRegisterInjectJS = false; const unregisteyUuids = [] as string[]; - for (const { uuid, type, isEarlyStart } of data) { + for (const { uuid } of data) { unregisteyUuids.push(uuid); - this.earlyScriptFlags.delete(uuid); this.scriptMatchEnable.clearRules(uuid); this.scriptMatchEnable.clearRules(`${uuid}${ORIGINAL_URLMATCH_SUFFIX}`); this.scriptMatchDisable.clearRules(uuid); this.scriptMatchDisable.clearRules(`${uuid}${ORIGINAL_URLMATCH_SUFFIX}`); - if (type === SCRIPT_TYPE_NORMAL && isEarlyStart) { - needReRegisterInjectJS = true; - } } await this.unregistryPageScripts(unregisteyUuids); - if (needReRegisterInjectJS) { - // 初始化会把所有的脚本flag注入,所以只用安装和卸载时重新注入flag - await this.reRegisterInjectScript(); - } }); // 监听脚本排序 @@ -647,7 +605,7 @@ export class RuntimeService { let jsCode = ""; if (withCode) { - const code = compileInjectionCode(scriptRes, scriptRes.code); + const code = compileInjectionCode(this.getMessageFlag(), scriptRes, scriptRes.code); registerScript.js[0].code = jsCode = code; } @@ -690,7 +648,7 @@ export class RuntimeService { if (earlyScript) { const scriptRes = await this.script.buildScriptRunResource(script); if (!scriptRes) return ""; - return compileInjectionCode(scriptRes, scriptRes.code); + return compileInjectionCode(this.getMessageFlag(), scriptRes, scriptRes.code); } const originalCode = await this.script.scriptCodeDAO.get(result.uuid); @@ -1139,7 +1097,7 @@ export class RuntimeService { const scriptRes = scriptsWithUpdatedResources.get(targetUUID); const scriptDAOCode = scriptCodes[targetUUID]; if (scriptRes && scriptDAOCode) { - const scriptInjectCode = compileInjectionCode(scriptRes, scriptDAOCode); + const scriptInjectCode = compileInjectionCode(this.getMessageFlag(), scriptRes, scriptDAOCode); scriptRegisterInfo.js = [ { code: scriptInjectCode, @@ -1209,13 +1167,8 @@ export class RuntimeService { messageFlag: string, { excludeMatches, excludeGlobs }: { excludeMatches: string[] | undefined; excludeGlobs: string[] | undefined } ) { - // 替换ScriptFlag - // 遍历early-start的脚本 - const earlyScriptFlag = [...this.earlyScriptFlags].map((uuid) => getScriptFlag(uuid)); - const flagParam = JSON.stringify(earlyScriptFlag); - // 构建inject.js的脚本注册信息 - const code = `(function (MessageFlag, EarlyScriptFlag) {\n${injectJs}\n})('${messageFlag}', ${flagParam})`; + const code = `(function (MessageFlag) {\n${injectJs}\n})('${messageFlag}')`; const script: chrome.userScripts.RegisteredUserScript = { id: "scriptcat-inject", js: [{ code }], @@ -1227,11 +1180,11 @@ export class RuntimeService { excludeGlobs: excludeGlobs, }; - // 构建给content.js用的early-start脚本flag + // 构建content.js的脚本注册信息 return [ { - id: "scriptcat-early-start-flag", - js: [{ code: `window.EarlyScriptFlag=${flagParam};window.MessageFlag="${messageFlag}"` }], + id: "scriptcat-content-flag", + js: [{ code: `window.MessageFlag="${messageFlag}"` }], matches: [""], allFrames: true, world: "USER_SCRIPT", @@ -1243,29 +1196,6 @@ export class RuntimeService { ] as chrome.userScripts.RegisteredUserScript[]; } - // 重新注册inject.js,主要是为了更新early-start的脚本flag - async reRegisterInjectScript() { - // 若 UserScripts API 不可使用 或 ScriptCat设定为不启用脚本 则退出 - if (!this.isUserScriptsAvailable || !this.isLoadScripts) return; - const messageFlag = this.getMessageFlag(); - const [scripts, injectJs] = await Promise.all([ - chrome.userScripts.getScripts({ ids: ["scriptcat-inject"] }), - this.getInjectJsCode(), - ]); - - if (!messageFlag || !scripts?.[0] || !injectJs) { - return; - } - // 提取现有的 excludeMatches 和 excludeGlobs - const { excludeMatches, excludeGlobs } = scripts[0]; - const apiScripts = this.compileInjectUserScript(injectJs, messageFlag, { excludeMatches, excludeGlobs }); - try { - await chrome.userScripts.update(apiScripts); // 里面包括 "scriptcat-inject" 和 "scriptcat-early-start-flag" - } catch (e: any) { - this.logger.error("register inject.js error", Logger.E(e)); - } - } - scriptMatchEntry( scriptRes: ScriptRunResource, o: { diff --git a/src/app/service/service_worker/script.ts b/src/app/service/service_worker/script.ts index e946e4804..c7317bdb3 100644 --- a/src/app/service/service_worker/script.ts +++ b/src/app/service/service_worker/script.ts @@ -327,9 +327,8 @@ export class ScriptService { await this.scriptCodeDAO.delete(uuid); await this.compiledResourceDAO.delete(uuid); logger.info("delete success"); - const isEarlyStart = isEarlyStartScript(script.metadata); - const data = [{ uuid, storageName, type: script.type, isEarlyStart }]; - this.mq.publish("deleteScripts", data); + const data = [{ uuid, storageName, type: script.type }] as TDeleteScript[]; + this.mq.publish("deleteScripts", data); return true; }) .catch((e) => { diff --git a/src/app/service/service_worker/utils.ts b/src/app/service/service_worker/utils.ts index a3ae261a5..6a91ae4bf 100644 --- a/src/app/service/service_worker/utils.ts +++ b/src/app/service/service_worker/utils.ts @@ -179,12 +179,12 @@ export function parseScriptLoadInfo(script: ScriptRunResource): ScriptLoadInfo { }; } -export function compileInjectionCode(scriptRes: ScriptRunResource, scriptCode: string) { +export function compileInjectionCode(messageFlag: string, scriptRes: ScriptRunResource, scriptCode: string) { const preDocumentStartScript = isEarlyStartScript(scriptRes.metadata); let scriptInjectCode; scriptCode = compileScriptCode(scriptRes, scriptCode); if (preDocumentStartScript) { - scriptInjectCode = compilePreInjectScript(parseScriptLoadInfo(scriptRes), scriptCode); + scriptInjectCode = compilePreInjectScript(messageFlag, parseScriptLoadInfo(scriptRes), scriptCode); } else { scriptInjectCode = compileInjectScript(scriptRes, scriptCode); } diff --git a/src/content.ts b/src/content.ts index 99f83673d..25b483f76 100644 --- a/src/content.ts +++ b/src/content.ts @@ -11,7 +11,6 @@ import { definePropertyListener } from "./app/service/content/utils"; declare global { interface Window { - EarlyScriptFlag?: string[]; MessageFlag?: string; } } @@ -48,7 +47,7 @@ if (typeof chrome?.runtime?.onMessage?.addListener !== "function") { const runtime = new ContentRuntime(extServer, server, extMsgComm, msgInject, scriptExecutorMsg, scriptExecutor); runtime.init(); // 页面加载,注入脚本 - runtime.pageLoad(); + runtime.pageLoad(messageFlag); }; // 监听MessageFlag diff --git a/src/inject.ts b/src/inject.ts index 69a211383..8395af23e 100644 --- a/src/inject.ts +++ b/src/inject.ts @@ -8,7 +8,7 @@ import { InjectRuntime } from "./app/service/content/inject"; import { ScriptExecutor } from "./app/service/content/script_executor"; import type { Message } from "@Packages/message/types"; -/* global MessageFlag, EarlyScriptFlag */ +/* global MessageFlag */ const msg: Message = new CustomEventMessage(MessageFlag, false); @@ -22,7 +22,7 @@ const server = new Server("inject", msg); const scriptExecutor = new ScriptExecutor(msg); const runtime = new InjectRuntime(server, msg, scriptExecutor); // 检查early-start的脚本 -scriptExecutor.checkEarlyStartScript(EarlyScriptFlag); +scriptExecutor.checkEarlyStartScript("inject", MessageFlag); server.on("pageLoad", (data: { scripts: ScriptLoadInfo[]; envInfo: GMInfoEnv }) => { logger.logger().debug("inject start"); From 88fd8ee797c037a31b96e4dccd5623f94741b648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Mon, 27 Oct 2025 00:37:23 +0800 Subject: [PATCH 02/11] =?UTF-8?q?=E4=BF=AE=E5=A4=8DloadScriptPromise?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/tests/early_inject_content_test.js | 2 ++ src/app/service/content/script_executor.ts | 20 +++++++------------- src/app/service/service_worker/runtime.ts | 8 +++++++- src/app/service/service_worker/utils.test.ts | 10 +++++----- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/example/tests/early_inject_content_test.js b/example/tests/early_inject_content_test.js index fbd7b8dec..cc28eed18 100644 --- a/example/tests/early_inject_content_test.js +++ b/example/tests/early_inject_content_test.js @@ -155,8 +155,10 @@ console.log("\n%c--- GM 存储 API 测试 ---", "color: orange; font-weight: bold;"); test("GM_setValue - 字符串", () => { + const fristValue = GM_getValue("test_key"); GM_setValue("test_key", "content环境测试值"); const value = GM_getValue("test_key"); + assert("content环境测试值", fristValue, "应该正确保存和读取字符串"); assert("content环境测试值", value, "应该正确保存和读取字符串"); }); diff --git a/src/app/service/content/script_executor.ts b/src/app/service/content/script_executor.ts index ad999b6a6..f3c7d7dcf 100644 --- a/src/app/service/content/script_executor.ts +++ b/src/app/service/content/script_executor.ts @@ -17,7 +17,6 @@ export class ScriptExecutor { execMap: Map = new Map(); envInfo: GMInfoEnv | undefined; - earlyScriptFlag: string[] = []; constructor(private msg: Message) {} @@ -51,20 +50,17 @@ export class ScriptExecutor { envInfo: this.envInfo!, }); }; + // 处理早期脚本的沙盒环境 + for (const val of this.execMap.values()) { + val.updateEarlyScriptGMInfo(this.envInfo!); + } + // 监听脚本加载 scripts.forEach((script) => { const flag = script.flag; - // 如果是EarlyScriptFlag,处理沙盒环境 - if (this.earlyScriptFlag.includes(flag)) { - for (const val of this.execMap.values()) { - if (val.scriptRes.flag === flag) { - // 处理早期脚本的沙盒环境 - val.updateEarlyScriptGMInfo(this.envInfo!); - break; - } - } + if (this.execMap.has(script.uuid)) { + // 已经执行过的脚本跳过 return; } - definePropertyListener(window, flag, (val: ScriptFunc) => { loadExec(script, val); }); @@ -76,7 +72,6 @@ export class ScriptExecutor { window.addEventListener(`sc${eventFlag}`, (event) => { if (event instanceof CustomEvent && event.detail.callback) { const flag = event.detail.callback(); - console.log("Early start script detected:", flag); this.execEarlyScript(flag); } }); @@ -84,7 +79,6 @@ export class ScriptExecutor { const ev = new CustomEvent(`${env === "content" ? "ct" : "fd"}ld${eventFlag}`, { detail: { callback: (flag: string) => { - console.log("Notify early start script:", flag); this.execEarlyScript(flag); }, }, diff --git a/src/app/service/service_worker/runtime.ts b/src/app/service/service_worker/runtime.ts index bff1c5361..100a34b82 100644 --- a/src/app/service/service_worker/runtime.ts +++ b/src/app/service/service_worker/runtime.ts @@ -269,7 +269,13 @@ export class RuntimeService { ); if (cleanUpPreviousRegister) { // 先反注册残留脚本 - unregisterScriptIds.push("scriptcat-content-flag", "scriptcat-inject", "scriptcat-content"); + unregisterScriptIds.push( + // 兼容旧的注册ID,过渡期后可移除 + "scriptcat-early-start-flag", + "scriptcat-content-flag", + "scriptcat-inject", + "scriptcat-content" + ); } if (unregisterScriptIds.length) { // 忽略 UserScripts API 无法执行 diff --git a/src/app/service/service_worker/utils.test.ts b/src/app/service/service_worker/utils.test.ts index 3343d1a76..0c271d130 100644 --- a/src/app/service/service_worker/utils.test.ts +++ b/src/app/service/service_worker/utils.test.ts @@ -215,7 +215,7 @@ describe("getUserScriptRegister", () => { const mockScriptMatchInfo: ScriptMatchInfo = { uuid: "test-uuid", name: "Test Script", - code: "console.log('test');", + // code: "console.log('test');", metadata: { "run-at": ["document-end"], noframes: [], @@ -237,10 +237,10 @@ describe("getUserScriptRegister", () => { runStatus: "running", createtime: Date.now(), checktime: Date.now(), - value: {}, - flag: "test", - resource: {}, - originalMetadata: {}, + // value: {}, + // flag: "test", + // resource: {}, + // originalMetadata: {}, }; // const code = compileInjectionCode(mockScriptMatchInfo, mockScriptMatchInfo.code); From b0866a14bd6f988dce4502287b5a554fb5f98317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Mon, 27 Oct 2025 00:42:29 +0800 Subject: [PATCH 03/11] =?UTF-8?q?=E8=B0=83=E6=95=B4flag=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/content/script_executor.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/app/service/content/script_executor.ts b/src/app/service/content/script_executor.ts index f3c7d7dcf..0cd28ffe5 100644 --- a/src/app/service/content/script_executor.ts +++ b/src/app/service/content/script_executor.ts @@ -14,6 +14,7 @@ export type ExecScriptEntry = { // 脚本执行器 export class ScriptExecutor { + earlyScriptFlag: Set = new Set(); execMap: Map = new Map(); envInfo: GMInfoEnv | undefined; @@ -50,16 +51,18 @@ export class ScriptExecutor { envInfo: this.envInfo!, }); }; - // 处理早期脚本的沙盒环境 - for (const val of this.execMap.values()) { - val.updateEarlyScriptGMInfo(this.envInfo!); - } // 监听脚本加载 scripts.forEach((script) => { const flag = script.flag; - if (this.execMap.has(script.uuid)) { - // 已经执行过的脚本跳过 - return; + // 如果是EarlyScriptFlag,处理沙盒环境 + if (this.earlyScriptFlag.has(flag)) { + for (const val of this.execMap.values()) { + if (val.scriptRes.flag === flag) { + // 处理早期脚本的沙盒环境 + val.updateEarlyScriptGMInfo(this.envInfo!); + break; + } + } } definePropertyListener(window, flag, (val: ScriptFunc) => { loadExec(script, val); @@ -94,6 +97,7 @@ export class ScriptExecutor { scriptFlag: flag, envInfo: {}, }); + this.earlyScriptFlag.add(flag); } execScriptEntry(scriptEntry: ExecScriptEntry) { From 84540a5454d7bec8c13e9c88a286ece77dd8dff0 Mon Sep 17 00:00:00 2001 From: wangyizhi Date: Tue, 28 Oct 2025 15:22:10 +0800 Subject: [PATCH 04/11] Update src/app/service/content/utils.ts Co-authored-by: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> --- src/app/service/content/utils.ts | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/app/service/content/utils.ts b/src/app/service/content/utils.ts index f72025e2d..25ecb8658 100644 --- a/src/app/service/content/utils.ts +++ b/src/app/service/content/utils.ts @@ -111,21 +111,17 @@ export function compilePreInjectScript( func: function(){${autoDeleteMountCode}${scriptCode}} }; (() => { - let eventListener = (ev) => { - if (ev.detail && ev.detail.callback && !loaded) { - ev.detail.callback('${script.flag}'); - } - } - window.addEventListener('${eventName}ld${messageFlag}', eventListener); - let loaded = false; - const callback = () => { - if (loaded) return; - loaded = true; - window.removeEventListener('${eventName}ld${messageFlag}', eventListener); - return '${script.flag}'; + const f = () => { + const event = new CustomEvent('sc${messageFlag}', + { cancelable: true, detail: { scriptFlag: '${script.flag}' } }); + return window.dispatchEvent(event); // checkEarlyStartScript 先执行的话,这里回传 false + }; + const noCheckEarlyStartScript = f(); // checkEarlyStartScript 先执行的话,这里的 f() 会直接触发execEarlyScript; dispatchEvent 会回传 false + if (noCheckEarlyStartScript) { // checkEarlyStartScript 未执行 + // 使用 dispatchEvent 回传值判断避免注册一堆不会呼叫的 eventHandler + window.addEventListener('${eventName}ld${messageFlag}', f, { once: true }); // 如checkEarlyStartScript 先执行,这个较后的event不会被呼叫。 + // once: true 使呼叫后立即移除监听 } - const event = new CustomEvent('sc${messageFlag}', { detail: { callback } }); - window.dispatchEvent(event); })(); `; } From 469afbf06c8f366a158fe36a82c51d9f1ccfb6ef Mon Sep 17 00:00:00 2001 From: wangyizhi Date: Tue, 28 Oct 2025 15:22:18 +0800 Subject: [PATCH 05/11] Update src/app/service/content/script_executor.ts Co-authored-by: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> --- src/app/service/content/script_executor.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/app/service/content/script_executor.ts b/src/app/service/content/script_executor.ts index 0cd28ffe5..101fea015 100644 --- a/src/app/service/content/script_executor.ts +++ b/src/app/service/content/script_executor.ts @@ -72,20 +72,17 @@ export class ScriptExecutor { checkEarlyStartScript(env: "content" | "inject", eventFlag: string) { // 监听 脚本加载 + // 适用于此「通知环境加载完成」代码执行后的脚本加载 window.addEventListener(`sc${eventFlag}`, (event) => { - if (event instanceof CustomEvent && event.detail.callback) { - const flag = event.detail.callback(); - this.execEarlyScript(flag); + if (typeof event?.detail?.scriptFlag === "string") { + event.preventDefault(); // dispatchEvent 会回传 false -> 分离环境也能得知环境加载代码已执行 + const scriptFlag = event.detail.scriptFlag; + this.execEarlyScript(scriptFlag); } }); // 通知 环境 加载完成 - const ev = new CustomEvent(`${env === "content" ? "ct" : "fd"}ld${eventFlag}`, { - detail: { - callback: (flag: string) => { - this.execEarlyScript(flag); - }, - }, - }); + // 适用于此「通知环境加载完成」代码执行前的脚本加载 + const ev = new CustomEvent(`${env === "content" ? "ct" : "fd"}ld${eventFlag}`); window.dispatchEvent(ev); } From 095b9592646cc8a4fa1d9f27494599a2a3a048d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Tue, 28 Oct 2025 16:16:41 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E9=87=8D=E6=9E=84messageFlag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/message/custom_event_message.ts | 20 +++- src/app/service/content/content.ts | 4 +- src/app/service/content/script_executor.ts | 18 ++-- src/app/service/content/utils.ts | 16 ++-- src/app/service/service_worker/runtime.ts | 103 +++++++++++++-------- src/app/service/service_worker/utils.ts | 4 +- src/content.ts | 68 ++++++-------- src/inject.ts | 6 +- src/types/main.d.ts | 16 +++- 9 files changed, 148 insertions(+), 107 deletions(-) diff --git a/packages/message/custom_event_message.ts b/packages/message/custom_event_message.ts index d0f2da90f..513704c49 100644 --- a/packages/message/custom_event_message.ts +++ b/packages/message/custom_event_message.ts @@ -19,11 +19,19 @@ export class CustomEventMessage implements Message { // 关联dom目标 relatedTarget: Map = new Map(); + protected flags: { + injectFlag: string; + contentFlag: string; + messageFlag: string; + }; + constructor( - protected flag: string, + flags: typeof CustomEventMessage.prototype.flags | string, protected isContent: boolean ) { - window.addEventListener((isContent ? "ct" : "fd") + flag, (event) => { + flags = typeof flags === "string" ? { injectFlag: "inject", contentFlag: "content", messageFlag: flags } : flags; + this.flags = flags; + window.addEventListener((isContent ? flags.contentFlag : flags.injectFlag) + flags.messageFlag, (event) => { if (event instanceof MouseEvent && event.movementX && event.relatedTarget) { this.relatedTarget.set(event.movementX, event.relatedTarget!); } else if (event instanceof CustomEvent) { @@ -95,12 +103,16 @@ export class CustomEventMessage implements Message { } } - const ev = new CustomEvent((this.isContent ? "fd" : "ct") + this.flag, { + const ev = new CustomEvent(this.sendEventName(), { detail, }); window.dispatchEvent(ev); } + sendEventName(): string { + return (this.isContent ? this.flags.injectFlag : this.flags.contentFlag) + this.flags.messageFlag; + } + sendMessage(data: TMessage): Promise { return new Promise((resolve: ((value: T) => void) | null) => { const messageId = uuidv4(); @@ -148,7 +160,7 @@ export class CustomEventMessage implements Message { // 先将relatedTarget转换成id发送过去 const id = ++this.relateId; // 可以使用此种方式交互element - const ev = new MouseEvent((this.isContent ? "fd" : "ct") + this.flag, { + const ev = new MouseEvent(this.sendEventName(), { movementX: id, relatedTarget: target, }); diff --git a/src/app/service/content/content.ts b/src/app/service/content/content.ts index 90774ceab..314d8c1e3 100644 --- a/src/app/service/content/content.ts +++ b/src/app/service/content/content.ts @@ -126,8 +126,8 @@ export default class ContentRuntime { ); } - pageLoad(messageFlag: string) { - this.scriptExecutor.checkEarlyStartScript("content", messageFlag); + pageLoad(messageFlags: MessageFlags) { + this.scriptExecutor.checkEarlyStartScript("content", messageFlags); const client = new RuntimeClient(this.senderToExt); // 向service_worker请求脚本列表 diff --git a/src/app/service/content/script_executor.ts b/src/app/service/content/script_executor.ts index 101fea015..55e18dd29 100644 --- a/src/app/service/content/script_executor.ts +++ b/src/app/service/content/script_executor.ts @@ -70,24 +70,28 @@ export class ScriptExecutor { }); } - checkEarlyStartScript(env: "content" | "inject", eventFlag: string) { + checkEarlyStartScript(env: "content" | "inject", messageFlags: MessageFlags) { + const eventNamePrefix = env === "content" ? messageFlags.contentFlag : messageFlags.injectFlag; // 监听 脚本加载 // 适用于此「通知环境加载完成」代码执行后的脚本加载 - window.addEventListener(`sc${eventFlag}`, (event) => { - if (typeof event?.detail?.scriptFlag === "string") { - event.preventDefault(); // dispatchEvent 会回传 false -> 分离环境也能得知环境加载代码已执行 - const scriptFlag = event.detail.scriptFlag; - this.execEarlyScript(scriptFlag); + window.addEventListener(`${eventNamePrefix}${messageFlags.scriptLoadComplete}`, (event) => { + if (event instanceof CustomEvent) { + if (typeof event.detail.scriptFlag === "string") { + event.preventDefault(); // dispatchEvent 会回传 false -> 分离环境也能得知环境加载代码已执行 + const scriptFlag = event.detail.scriptFlag; + this.execEarlyScript(scriptFlag); + } } }); // 通知 环境 加载完成 // 适用于此「通知环境加载完成」代码执行前的脚本加载 - const ev = new CustomEvent(`${env === "content" ? "ct" : "fd"}ld${eventFlag}`); + const ev = new CustomEvent(eventNamePrefix + messageFlags.envLoadComplete); window.dispatchEvent(ev); } execEarlyScript(flag: string) { const scriptFunc = (window as any)[flag] as PreScriptFunc; + console.log("execEarlyScript", flag, scriptFunc); this.execScriptEntry({ scriptLoadInfo: scriptFunc.scriptInfo, scriptFunc: scriptFunc.func, diff --git a/src/app/service/content/utils.ts b/src/app/service/content/utils.ts index 25ecb8658..51bd30971 100644 --- a/src/app/service/content/utils.ts +++ b/src/app/service/content/utils.ts @@ -99,12 +99,12 @@ export function compileInjectScriptByFlag( * 将脚本函数编译为预注入脚本代码 */ export function compilePreInjectScript( - messageFlag: string, + messageFlags: MessageFlags, script: ScriptLoadInfo, scriptCode: string, autoDeleteMountFunction: boolean = false ): string { - const eventName = isInjectIntoContent(script.metadata) ? "ct" : "fd"; + 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)}, @@ -112,15 +112,13 @@ export function compilePreInjectScript( }; (() => { const f = () => { - const event = new CustomEvent('sc${messageFlag}', + const event = new CustomEvent('${eventNamePrefix}${messageFlags.scriptLoadComplete}', { cancelable: true, detail: { scriptFlag: '${script.flag}' } }); - return window.dispatchEvent(event); // checkEarlyStartScript 先执行的话,这里回传 false + return window.dispatchEvent(event); }; - const noCheckEarlyStartScript = f(); // checkEarlyStartScript 先执行的话,这里的 f() 会直接触发execEarlyScript; dispatchEvent 会回传 false - if (noCheckEarlyStartScript) { // checkEarlyStartScript 未执行 - // 使用 dispatchEvent 回传值判断避免注册一堆不会呼叫的 eventHandler - window.addEventListener('${eventName}ld${messageFlag}', f, { once: true }); // 如checkEarlyStartScript 先执行,这个较后的event不会被呼叫。 - // once: true 使呼叫后立即移除监听 + const noCheckEarlyStartScript = f(); + if (noCheckEarlyStartScript) { + window.addEventListener('${eventNamePrefix}${messageFlags.envLoadComplete}', f, { once: true }); } })(); `; diff --git a/src/app/service/service_worker/runtime.ts b/src/app/service/service_worker/runtime.ts index 100a34b82..aa6ceb80f 100644 --- a/src/app/service/service_worker/runtime.ts +++ b/src/app/service/service_worker/runtime.ts @@ -47,7 +47,13 @@ const ORIGINAL_URLMATCH_SUFFIX = "{ORIGINAL}"; // 用于标记原始URLPatterns const runtimeGlobal = { registered: false, - messageFlag: "PENDING", + messageFlags: { + contentFlag: "PENDING", + injectFlag: "PENDING", + messageFlag: "PENDING", + scriptLoadComplete: "PENDING", + envLoadComplete: "PENDING", + } as MessageFlags, }; export class RuntimeService { @@ -79,6 +85,7 @@ export class RuntimeService { // 获取inject.js内容时调用,需要预先调用preInject injectJsCodePromise: Promise | null = null; + contentJsCodePromise: Promise | null = null; // initReady initReady: Promise | boolean = false; @@ -106,10 +113,15 @@ export class RuntimeService { private localStorageDAO: LocalStorageDAO ) { this.loadingInitFlagPromise = this.localStorageDAO - .get("scriptInjectMessageFlag") + .get("scriptInjectMessageFlags") .then((res) => { - runtimeGlobal.messageFlag = res?.value || randomMessageFlag(); - return this.localStorageDAO.save({ key: "scriptInjectMessageFlag", value: runtimeGlobal.messageFlag }); + runtimeGlobal.messageFlags = res?.value || { + contentInject: randomMessageFlag(), + injectContent: randomMessageFlag(), + scriptLoadComplete: randomMessageFlag(), + envLoadComplete: randomMessageFlag(), + }; + return this.localStorageDAO.save({ key: "scriptInjectMessageFlags", value: runtimeGlobal.messageFlags }); }) .catch(console.error); this.logger = LoggerCore.logger({ component: "runtime" }); @@ -204,6 +216,18 @@ export class RuntimeService { return this.injectJsCodePromise; } + async getContentJsCode() { + if (!this.contentJsCodePromise) { + this.contentJsCodePromise = fetch("/src/content.js") + .then((res) => res.text()) + .catch((e) => { + console.error("Unable to fetch /src/content.js", e); + return undefined; + }); + } + return this.contentJsCodePromise; + } + createMatchInfoEntry( scriptRes: ScriptRunResource, o: { scriptUrlPatterns: URLRuleEntry[]; originalUrlPatterns: URLRuleEntry[] | null } @@ -272,7 +296,6 @@ export class RuntimeService { unregisterScriptIds.push( // 兼容旧的注册ID,过渡期后可移除 "scriptcat-early-start-flag", - "scriptcat-content-flag", "scriptcat-inject", "scriptcat-content" ); @@ -587,16 +610,27 @@ export class RuntimeService { runtimeGlobal.registered = false; // 重置 flag 避免取消注册失败 // 即使注册失败,通过重置 flag 可避免错误地呼叫已取消注册的Script - runtimeGlobal.messageFlag = randomMessageFlag(); + runtimeGlobal.messageFlags = this.generateMessageFlags(); await Promise.allSettled([ chrome.userScripts.unregister(), - this.localStorageDAO.save({ key: "scriptInjectMessageFlag", value: runtimeGlobal.messageFlag }), + this.localStorageDAO.save({ key: "scriptInjectMessageFlags", value: runtimeGlobal.messageFlags }), ]); } } - getMessageFlag() { - return runtimeGlobal.messageFlag; + // 生成messageFlags + generateMessageFlags(): typeof runtimeGlobal.messageFlags { + return { + injectFlag: randomMessageFlag(), + contentFlag: randomMessageFlag(), + messageFlag: randomMessageFlag(), + scriptLoadComplete: randomMessageFlag(), + envLoadComplete: randomMessageFlag(), + }; + } + + getMessageFlags() { + return runtimeGlobal.messageFlags; } async buildAndSaveCompiledResourceFromScript(script: Script, withCode: boolean = false) { @@ -611,7 +645,7 @@ export class RuntimeService { let jsCode = ""; if (withCode) { - const code = compileInjectionCode(this.getMessageFlag(), scriptRes, scriptRes.code); + const code = compileInjectionCode(this.getMessageFlags(), scriptRes, scriptRes.code); registerScript.js[0].code = jsCode = code; } @@ -654,7 +688,7 @@ export class RuntimeService { if (earlyScript) { const scriptRes = await this.script.buildScriptRunResource(script); if (!scriptRes) return ""; - return compileInjectionCode(this.getMessageFlag(), scriptRes, scriptRes.code); + return compileInjectionCode(this.getMessageFlags(), scriptRes, scriptRes.code); } const originalCode = await this.script.scriptCodeDAO.get(result.uuid); @@ -733,7 +767,7 @@ export class RuntimeService { excludeMatches: string[]; excludeGlobs: string[]; }) { - const messageFlag = runtimeGlobal.messageFlag; + const messageFlags = runtimeGlobal.messageFlags; // 配置脚本运行环境: 注册时前先准备 chrome.runtime 等设定 // Firefox MV3 只提供 runtime.sendMessage 及 runtime.connect // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts/WorldProperties#messaging @@ -753,21 +787,24 @@ export class RuntimeService { } } const retScript: chrome.userScripts.RegisteredUserScript[] = []; - retScript.push({ - id: "scriptcat-content", - js: [{ file: "src/content.js" }], - matches: [""], - allFrames: true, - runAt: "document_start", - world: "USER_SCRIPT", - excludeMatches, - excludeGlobs, - }); + const contentJs = await this.getContentJsCode(); + if (contentJs) { + retScript.push({ + id: "scriptcat-content", + js: [{ code: `(function (MessageFlags) {\n${contentJs}\n})(${JSON.stringify(messageFlags)})` }], + matches: [""], + allFrames: true, + runAt: "document_start", + world: "USER_SCRIPT", + excludeMatches, + excludeGlobs, + }); + } // inject.js const injectJs = await this.getInjectJsCode(); if (injectJs) { - const apiScripts = this.compileInjectUserScript(injectJs, messageFlag, { + const apiScripts = this.compileInjectUserScript(injectJs, messageFlags, { excludeMatches, excludeGlobs, }); @@ -1103,7 +1140,7 @@ export class RuntimeService { const scriptRes = scriptsWithUpdatedResources.get(targetUUID); const scriptDAOCode = scriptCodes[targetUUID]; if (scriptRes && scriptDAOCode) { - const scriptInjectCode = compileInjectionCode(this.getMessageFlag(), scriptRes, scriptDAOCode); + const scriptInjectCode = compileInjectionCode(this.getMessageFlags(), scriptRes, scriptDAOCode); scriptRegisterInfo.js = [ { code: scriptInjectCode, @@ -1170,11 +1207,11 @@ export class RuntimeService { compileInjectUserScript( injectJs: string, - messageFlag: string, + messageFlags: MessageFlags, { excludeMatches, excludeGlobs }: { excludeMatches: string[] | undefined; excludeGlobs: string[] | undefined } ) { // 构建inject.js的脚本注册信息 - const code = `(function (MessageFlag) {\n${injectJs}\n})('${messageFlag}')`; + const code = `(function (MessageFlags) {\n${injectJs}\n})(${JSON.stringify(messageFlags)})`; const script: chrome.userScripts.RegisteredUserScript = { id: "scriptcat-inject", js: [{ code }], @@ -1187,19 +1224,7 @@ export class RuntimeService { }; // 构建content.js的脚本注册信息 - return [ - { - id: "scriptcat-content-flag", - js: [{ code: `window.MessageFlag="${messageFlag}"` }], - matches: [""], - allFrames: true, - world: "USER_SCRIPT", - runAt: "document_start", - excludeMatches: excludeMatches, - excludeGlobs: excludeGlobs, - }, - script, - ] as chrome.userScripts.RegisteredUserScript[]; + return [script] as chrome.userScripts.RegisteredUserScript[]; } scriptMatchEntry( diff --git a/src/app/service/service_worker/utils.ts b/src/app/service/service_worker/utils.ts index 6a91ae4bf..61b766727 100644 --- a/src/app/service/service_worker/utils.ts +++ b/src/app/service/service_worker/utils.ts @@ -179,12 +179,12 @@ export function parseScriptLoadInfo(script: ScriptRunResource): ScriptLoadInfo { }; } -export function compileInjectionCode(messageFlag: string, scriptRes: ScriptRunResource, scriptCode: string) { +export function compileInjectionCode(messageFlags: MessageFlags, scriptRes: ScriptRunResource, scriptCode: string) { const preDocumentStartScript = isEarlyStartScript(scriptRes.metadata); let scriptInjectCode; scriptCode = compileScriptCode(scriptRes, scriptCode); if (preDocumentStartScript) { - scriptInjectCode = compilePreInjectScript(messageFlag, parseScriptLoadInfo(scriptRes), scriptCode); + scriptInjectCode = compilePreInjectScript(messageFlags, parseScriptLoadInfo(scriptRes), scriptCode); } else { scriptInjectCode = compileInjectScript(scriptRes, scriptCode); } diff --git a/src/content.ts b/src/content.ts index 25b483f76..2be28f77f 100644 --- a/src/content.ts +++ b/src/content.ts @@ -7,49 +7,39 @@ import ContentRuntime from "./app/service/content/content"; import { ScriptExecutor } from "./app/service/content/script_executor"; import { randomMessageFlag } from "./pkg/utils/utils"; import type { Message } from "@Packages/message/types"; -import { definePropertyListener } from "./app/service/content/utils"; -declare global { - interface Window { - MessageFlag?: string; - } -} +/* global MessageFlags */ if (typeof chrome?.runtime?.onMessage?.addListener !== "function") { // Firefox MV3 之类好像没有 chrome.runtime.onMessage.addListener ? console.error("chrome.runtime.onMessage.addListener is not a function"); } else { - const start = (messageFlag: string) => { - // 建立与service_worker页面的连接 - const extMsgComm: Message = new ExtensionMessage(false); - // 初始化日志组件 - const loggerCore = new LoggerCore({ - writer: new MessageWriter(extMsgComm), - labels: { env: "content" }, - }); - - loggerCore.logger().debug("content start"); - - const msgInject = new CustomEventMessage(messageFlag, true); - - // 处理scriptExecutor - const scriptExecutorFlag = randomMessageFlag(); - const scriptExecutorMsg = new CustomEventMessage(scriptExecutorFlag, true); - const scriptExecutor = new ScriptExecutor(new CustomEventMessage(scriptExecutorFlag, false)); - - const server = new Server("content", [msgInject, scriptExecutorMsg]); - - // Opera中没有chrome.runtime.onConnect,并且content也不需要chrome.runtime.onConnect - // 所以不需要处理连接,设置为false - const extServer = new Server("content", extMsgComm, false); - // scriptExecutor的消息接口 - // 初始化运行环境 - const runtime = new ContentRuntime(extServer, server, extMsgComm, msgInject, scriptExecutorMsg, scriptExecutor); - runtime.init(); - // 页面加载,注入脚本 - runtime.pageLoad(messageFlag); - }; - - // 监听MessageFlag - definePropertyListener(window, "MessageFlag", start); + // 建立与service_worker页面的连接 + const extMsgComm: Message = new ExtensionMessage(false); + // 初始化日志组件 + const loggerCore = new LoggerCore({ + writer: new MessageWriter(extMsgComm), + labels: { env: "content" }, + }); + + loggerCore.logger().debug("content start"); + + const msgInject = new CustomEventMessage(MessageFlags, true); + + // 处理scriptExecutor + const scriptExecutorFlag = randomMessageFlag(); + const scriptExecutorMsg = new CustomEventMessage(scriptExecutorFlag, true); + const scriptExecutor = new ScriptExecutor(new CustomEventMessage(scriptExecutorFlag, false)); + + const server = new Server("content", [msgInject, scriptExecutorMsg]); + + // Opera中没有chrome.runtime.onConnect,并且content也不需要chrome.runtime.onConnect + // 所以不需要处理连接,设置为false + const extServer = new Server("content", extMsgComm, false); + // scriptExecutor的消息接口 + // 初始化运行环境 + const runtime = new ContentRuntime(extServer, server, extMsgComm, msgInject, scriptExecutorMsg, scriptExecutor); + runtime.init(); + // 页面加载,注入脚本 + runtime.pageLoad(MessageFlags); } diff --git a/src/inject.ts b/src/inject.ts index 8395af23e..04aed52e7 100644 --- a/src/inject.ts +++ b/src/inject.ts @@ -8,9 +8,9 @@ import { InjectRuntime } from "./app/service/content/inject"; import { ScriptExecutor } from "./app/service/content/script_executor"; import type { Message } from "@Packages/message/types"; -/* global MessageFlag */ +/* global MessageFlags */ -const msg: Message = new CustomEventMessage(MessageFlag, false); +const msg: Message = new CustomEventMessage(MessageFlags, false); // 加载logger组件 const logger = new LoggerCore({ @@ -22,7 +22,7 @@ const server = new Server("inject", msg); const scriptExecutor = new ScriptExecutor(msg); const runtime = new InjectRuntime(server, msg, scriptExecutor); // 检查early-start的脚本 -scriptExecutor.checkEarlyStartScript("inject", MessageFlag); +scriptExecutor.checkEarlyStartScript("inject", MessageFlags); server.on("pageLoad", (data: { scripts: ScriptLoadInfo[]; envInfo: GMInfoEnv }) => { logger.logger().debug("inject start"); diff --git a/src/types/main.d.ts b/src/types/main.d.ts index 6883fb5f1..ea7685986 100644 --- a/src/types/main.d.ts +++ b/src/types/main.d.ts @@ -25,8 +25,20 @@ interface FileSystemObserverInstance { observe(handle: FileSystemFileHandle | FileSystemDirectoryHandle | FileSystemSyncAccessHandle): Promise; } -declare const MessageFlag: string; -declare const EarlyScriptFlag: string[]; +interface MessageFlags { + // inject 环境flag + injectFlag: string; + // content 环境flag + contentFlag: string; + // 通信flag + messageFlag: string; + // 脚本加载完成事件 + scriptLoadComplete: string; + // 环境加载完成事件 + envLoadComplete: string; +} + +declare const MessageFlags: MessageFlags; // 可以让content与inject环境交换携带dom的对象 declare let cloneInto: ((detail: any, view: any) => any) | undefined; From 2050627c655ffc475731538ec144e35f0d4a1b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Tue, 28 Oct 2025 16:19:42 +0800 Subject: [PATCH 07/11] =?UTF-8?q?=E9=80=9A=E8=BF=87=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/message/server.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/message/server.test.ts b/packages/message/server.test.ts index 659c17021..e18182ea2 100644 --- a/packages/message/server.test.ts +++ b/packages/message/server.test.ts @@ -21,9 +21,14 @@ describe("Server", () => { writable: true, }); + const flags = { + contentFlag: "ct", + injectFlag: "fd", + messageFlag: "test", + }; // 创建 content 和 inject 之间的消息通道 - contentMessage = new CustomEventMessage("test", true); // content 端 - injectMessage = new CustomEventMessage("test", false); // inject 端 + contentMessage = new CustomEventMessage(flags, true); // content 端 + injectMessage = new CustomEventMessage(flags, false); // inject 端 // 服务端使用 content 消息 server = new Server("api", contentMessage); From d54e0586be078987311f056e7dcd8749e612f64e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Tue, 28 Oct 2025 16:25:37 +0800 Subject: [PATCH 08/11] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/service_worker/runtime.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/app/service/service_worker/runtime.ts b/src/app/service/service_worker/runtime.ts index aa6ceb80f..dc2821e49 100644 --- a/src/app/service/service_worker/runtime.ts +++ b/src/app/service/service_worker/runtime.ts @@ -115,12 +115,7 @@ export class RuntimeService { this.loadingInitFlagPromise = this.localStorageDAO .get("scriptInjectMessageFlags") .then((res) => { - runtimeGlobal.messageFlags = res?.value || { - contentInject: randomMessageFlag(), - injectContent: randomMessageFlag(), - scriptLoadComplete: randomMessageFlag(), - envLoadComplete: randomMessageFlag(), - }; + runtimeGlobal.messageFlags = res?.value || this.generateMessageFlags(); return this.localStorageDAO.save({ key: "scriptInjectMessageFlags", value: runtimeGlobal.messageFlags }); }) .catch(console.error); From d25d0746e23286c437d95c4ed95d326b272c9aa3 Mon Sep 17 00:00:00 2001 From: wangyizhi Date: Tue, 28 Oct 2025 16:26:30 +0800 Subject: [PATCH 09/11] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- example/tests/early_inject_content_test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/tests/early_inject_content_test.js b/example/tests/early_inject_content_test.js index cc28eed18..57d252c2c 100644 --- a/example/tests/early_inject_content_test.js +++ b/example/tests/early_inject_content_test.js @@ -155,10 +155,10 @@ console.log("\n%c--- GM 存储 API 测试 ---", "color: orange; font-weight: bold;"); test("GM_setValue - 字符串", () => { - const fristValue = GM_getValue("test_key"); + const firstValue = GM_getValue("test_key"); GM_setValue("test_key", "content环境测试值"); const value = GM_getValue("test_key"); - assert("content环境测试值", fristValue, "应该正确保存和读取字符串"); + assert("content环境测试值", firstValue, "应该正确保存和读取字符串"); assert("content环境测试值", value, "应该正确保存和读取字符串"); }); From c03fdc16ff7105e9846faa91045a2c14d729a929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Tue, 28 Oct 2025 16:32:00 +0800 Subject: [PATCH 10/11] =?UTF-8?q?=E6=A0=B9=E6=8D=AEcopilot=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/content/script_executor.ts | 1 - src/app/service/service_worker/runtime.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/service/content/script_executor.ts b/src/app/service/content/script_executor.ts index 55e18dd29..3e7b5408c 100644 --- a/src/app/service/content/script_executor.ts +++ b/src/app/service/content/script_executor.ts @@ -91,7 +91,6 @@ export class ScriptExecutor { execEarlyScript(flag: string) { const scriptFunc = (window as any)[flag] as PreScriptFunc; - console.log("execEarlyScript", flag, scriptFunc); this.execScriptEntry({ scriptLoadInfo: scriptFunc.scriptInfo, scriptFunc: scriptFunc.func, diff --git a/src/app/service/service_worker/runtime.ts b/src/app/service/service_worker/runtime.ts index dc2821e49..0129dd99b 100644 --- a/src/app/service/service_worker/runtime.ts +++ b/src/app/service/service_worker/runtime.ts @@ -614,7 +614,7 @@ export class RuntimeService { } // 生成messageFlags - generateMessageFlags(): typeof runtimeGlobal.messageFlags { + generateMessageFlags(): MessageFlags { return { injectFlag: randomMessageFlag(), contentFlag: randomMessageFlag(), @@ -1218,7 +1218,6 @@ export class RuntimeService { excludeGlobs: excludeGlobs, }; - // 构建content.js的脚本注册信息 return [script] as chrome.userScripts.RegisteredUserScript[]; } From c6847efcbbc323b30faca73480ae6b8681f7e97f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Wed, 29 Oct 2025 10:24:37 +0800 Subject: [PATCH 11/11] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/tests/early_inject_content_test.js | 7 +++---- example/tests/early_test.js | 5 +++-- example/tests/inject_content_test.js | 11 ++++++----- src/app/service/content/script_executor.ts | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/example/tests/early_inject_content_test.js b/example/tests/early_inject_content_test.js index 57d252c2c..6fc0464fa 100644 --- a/example/tests/early_inject_content_test.js +++ b/example/tests/early_inject_content_test.js @@ -11,6 +11,7 @@ // @grant GM_log // @grant GM_info // @grant GM_setValue +// @grant GM.setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues @@ -154,11 +155,9 @@ // ============ GM 存储 API 测试 ============ console.log("\n%c--- GM 存储 API 测试 ---", "color: orange; font-weight: bold;"); - test("GM_setValue - 字符串", () => { - const firstValue = GM_getValue("test_key"); - GM_setValue("test_key", "content环境测试值"); + await test("GM_setValue - 字符串", async () => { + await GM.setValue("test_key", "content环境测试值"); const value = GM_getValue("test_key"); - assert("content环境测试值", firstValue, "应该正确保存和读取字符串"); assert("content环境测试值", value, "应该正确保存和读取字符串"); }); diff --git a/example/tests/early_test.js b/example/tests/early_test.js index eb6c8f390..2e835e3e5 100644 --- a/example/tests/early_test.js +++ b/example/tests/early_test.js @@ -10,6 +10,7 @@ // @grant GM_log // @grant GM_info // @grant GM_setValue +// @grant GM.setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues @@ -182,8 +183,8 @@ // ============ GM 存储 API 测试 ============ console.log("\n%c--- GM 存储 API 测试 ---", "color: orange; font-weight: bold;"); - await test("GM_setValue - 字符串", () => { - GM_setValue("test_key", "早期脚本测试值"); + await test("GM_setValue - 字符串", async () => { + await GM.setValue("test_key", "早期脚本测试值"); const value = GM_getValue("test_key"); assert("早期脚本测试值", value, "应该正确保存和读取字符串"); }); diff --git a/example/tests/inject_content_test.js b/example/tests/inject_content_test.js index 4442805f4..d3cfc74e0 100644 --- a/example/tests/inject_content_test.js +++ b/example/tests/inject_content_test.js @@ -10,13 +10,14 @@ // @grant GM_log // @grant GM_info // @grant GM_setValue +// @grant GM.setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @run-at document-start // ==/UserScript== -(function () { +(async function () { "use strict"; console.log("%c=== Content环境 GM API 测试开始 ===", "color: blue; font-size: 16px; font-weight: bold;"); @@ -28,10 +29,10 @@ }; // 测试辅助函数 - function test(name, fn) { + async function test(name, fn) { testResults.total++; try { - fn(); + await fn(); testResults.passed++; console.log(`%c✓ ${name}`, "color: green;"); return true; @@ -116,8 +117,8 @@ // ============ GM 存储 API 测试 ============ console.log("\n%c--- GM 存储 API 测试 ---", "color: orange; font-weight: bold;"); - test("GM_setValue - 字符串", () => { - GM_setValue("test_key", "content环境测试值"); + await test("GM_setValue - 字符串", async () => { + await GM.setValue("test_key", "content环境测试值"); const value = GM_getValue("test_key"); assert("content环境测试值", value, "应该正确保存和读取字符串"); }); diff --git a/src/app/service/content/script_executor.ts b/src/app/service/content/script_executor.ts index 3e7b5408c..c353d314c 100644 --- a/src/app/service/content/script_executor.ts +++ b/src/app/service/content/script_executor.ts @@ -60,7 +60,7 @@ export class ScriptExecutor { if (val.scriptRes.flag === flag) { // 处理早期脚本的沙盒环境 val.updateEarlyScriptGMInfo(this.envInfo!); - break; + return; } } }