diff --git a/src/app/service/content/exec_script.ts b/src/app/service/content/exec_script.ts index 9ec3c701d..4c252e5b1 100644 --- a/src/app/service/content/exec_script.ts +++ b/src/app/service/content/exec_script.ts @@ -74,12 +74,12 @@ export default class ExecScript { * @see {@link compileScriptCode} * @returns */ - exec() { + public readonly exec = () => { this.logger.debug("script start"); const sandboxContext = this.sandboxContext; this.execContext = sandboxContext ? createProxyContext(sandboxContext) : global; // this.$ 只能执行一次 return this.scriptFunc.call(this.execContext, this.named, this.scriptRes.name); - } + }; // 早期启动的脚本,处理GM API updateEarlyScriptGMInfo(envInfo: GMInfoEnv) { diff --git a/src/app/service/content/script_executor.ts b/src/app/service/content/script_executor.ts index 3631d1478..21b005450 100644 --- a/src/app/service/content/script_executor.ts +++ b/src/app/service/content/script_executor.ts @@ -3,7 +3,7 @@ import { getStorageName } from "@App/pkg/utils/utils"; import type { EmitEventRequest } from "../service_worker/types"; import ExecScript from "./exec_script"; import type { GMInfoEnv, ScriptFunc, ValueUpdateDataEncoded } from "./types"; -import { addStyleSheet, definePropertyListener } from "./utils"; +import { addStyleSheet, definePropertyListener, waitBody } from "./utils"; import type { ScriptLoadInfo, TScriptInfo } from "@App/app/repo/scripts"; import { DefinedFlags } from "../service_worker/runtime.consts"; import { pageAddEventListener, pageDispatchEvent } from "@Packages/message/common"; @@ -132,8 +132,8 @@ export class ScriptExecutor { execScriptEntry(scriptEntry: ExecScriptEntry) { const { scriptLoadInfo, scriptFunc, envInfo } = scriptEntry; - const exec = new ExecScript(scriptLoadInfo, "scripting", this.msg, scriptFunc, envInfo); - this.execScriptMap.set(scriptLoadInfo.uuid, exec); + const execScript = new ExecScript(scriptLoadInfo, "scripting", this.msg, scriptFunc, envInfo); + this.execScriptMap.set(scriptLoadInfo.uuid, execScript); const metadata = scriptLoadInfo.metadata || {}; const resource = scriptLoadInfo.resource; // 注入css @@ -147,32 +147,13 @@ export class ScriptExecutor { } if (metadata["run-at"] && metadata["run-at"][0] === "document-body") { // 等待页面加载完成 - this.waitBody(() => { - exec.exec(); - }); + waitBody(execScript.exec); } else { try { - exec.exec(); + execScript.exec(); } catch { // 屏蔽错误,防止脚本报错导致后续脚本无法执行 } } } - - // 参考了tm的实现 - waitBody(callback: () => void) { - if (document.body) { - callback(); - return; - } - const listen = () => { - document.removeEventListener("load", listen, false); - document.removeEventListener("DOMNodeInserted", listen, false); - document.removeEventListener("DOMContentLoaded", listen, false); - this.waitBody(callback); - }; - document.addEventListener("load", listen, false); - document.addEventListener("DOMNodeInserted", listen, false); - document.addEventListener("DOMContentLoaded", listen, false); - } } diff --git a/src/app/service/content/utils.ts b/src/app/service/content/utils.ts index 6fe01c634..591a100f6 100644 --- a/src/app/service/content/utils.ts +++ b/src/app/service/content/utils.ts @@ -11,6 +11,47 @@ export type CompileScriptCodeResource = { require: Array<{ url: string; content: string }>; }; +// 参考了tm的实现 +export const waitBody = (callback: () => void) => { + // 只读取一次 document,避免重复访问 getter + let doc: Document | null = document; + + // body 已存在,直接执行回调 + if (doc.body) { + try { + callback(); + } catch { + // 屏蔽错误,防止脚本报错导致后续脚本无法执行 + } + return; + } + + let handler: ((this: Document, ev: Event) => void) | null = function () { + // 通常只需等待 body 就绪 + // 兼容少数页面在加载过程中替换 document 的情况 + if (this.body || document !== this) { + // 确保只清理一次,防止因页面代码骑劫使移除失败后反复触发 + if (handler !== null) { + this.removeEventListener("load", handler, false); + this.removeEventListener("DOMNodeInserted", handler, false); + this.removeEventListener("DOMContentLoaded", handler, false); + handler = null; // 释放引用,便于 GC + + // 兼容 document 被替换时重新执行 + waitBody(callback); + } + } + }; + + // 注意:避免使用 EventListenerObject + // 某些页面会 hook 事件 API,导致EventListenerObject的监听器或会失灵 + doc.addEventListener("load", handler, false); + doc.addEventListener("DOMNodeInserted", handler, false); + doc.addEventListener("DOMContentLoaded", handler, false); + + doc = null; // 释放引用,便于 GC +}; + // 根据ScriptRunResource获取require的资源 export function getScriptRequire(scriptRes: ScriptRunResource): CompileScriptCodeResource["require"] { const resourceArray = new Array<{ url: string; content: string }>();