Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/app/service/content/exec_script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
29 changes: 5 additions & 24 deletions src/app/service/content/script_executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Comment on lines +150 to 154
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

这里直接调用/传递 execScript.exec,但未处理其可能返回的 Promise。若脚本是 async 且发生 reject,会产生未处理的 unhandledrejection。建议统一把返回值保存下来:如果是 Promise 就 .catch(或在 waitBody 内部支持 async callback 并处理返回 Promise)。

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@CodFrm
对了 这个 try catch 没 await 的话其实是捕捉不到 this.scriptFunc.cal(...) 里面的 error
虽然这里这样写没问题
但Copilot 很迷惑 (明明加了 try catch 但又不加 .catch)

其他部份尽量 async 就对应 await 会比较好

// 屏蔽错误,防止脚本报错导致后续脚本无法执行
}
}
}

// 参考了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);
}
}
41 changes: 41 additions & 0 deletions src/app/service/content/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,47 @@ export type CompileScriptCodeResource = {
require: Array<{ url: string; content: string }>;
};

// 参考了tm的实现
export const waitBody = (callback: () => void) => {
// 只读取一次 document,避免重复访问 getter
Comment on lines +14 to +16
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

新增的 waitBody 没有对应单测覆盖;当前仓库对 utils.ts 有较完整的 utils.test.ts。建议补充用例覆盖:body 已存在直接执行、body 通过 DOMContentLoaded/DOMNodeInserted 后执行、以及确保只触发一次/正确移除监听器。

Copilot uses AI. Check for mistakes.
let doc: Document | null = document;

// body 已存在,直接执行回调
if (doc.body) {
try {
callback();
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

waitBody 里对 callback 只做了同步 try/catch;但这里 callback 可能返回 Promise(例如执行 async 脚本),Promise reject 时会变成未处理的 unhandledrejection。建议捕获 callback 的返回值并在是 Promise/thenable 时显式 .catch(保持吞错策略一致)。

Suggested change
callback();
const result = callback();
// 如果 callback 返回 Promise/thenable,则显式捕获其 reject,保持与同步 try/catch 一致的吞错策略
if (result && typeof (result as any).then === "function") {
(result as Promise<unknown>).catch(() => {
// 屏蔽错误,防止脚本报错导致后续脚本无法执行
});
}

Copilot uses AI. Check for mistakes.
} 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
Copy link
Member

Choose a reason for hiding this comment

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

有点夸张了,虽然无所谓

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

这个PR是在搞 bind 不 bind 看到顺便改一下.
主要是 waitBody 的执行在页面,又有一堆 document-start 的脚本在搞来搞去
所以这个东西要写得保险一点

那些 addEventListener 很常被 hack

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

对了 TM 那边好像没有用 DOMNodeInserted 了
不过我不动你原本的做法了。
只是手㾗改一下

};

// 根据ScriptRunResource获取require的资源
export function getScriptRequire(scriptRes: ScriptRunResource): CompileScriptCodeResource["require"] {
const resourceArray = new Array<{ url: string; content: string }>();
Expand Down
Loading