Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
62bab4a
✨ 新增 GM_audio 与 GM.audio
cyfung1031 Jul 6, 2026
776057c
♻️ 合并 GM_audio 状态变化的三个变量为单一对象
cyfung1031 Jul 6, 2026
0101171
🐛 修复 GM_audio 状态监听在 connect() 未完成时移除再添加导致的连接泄漏
cyfung1031 Jul 7, 2026
28be974
🐛 修复 GM_audio.getState() 在未静音时仍回传 muteReason
cyfung1031 Jul 11, 2026
4a143aa
🐛 修复 GM_audio 状态监听在断线重连、回调重复调用与监听器隔离上的问题
cyfung1031 Jul 11, 2026
7c6d7e8
fix
cyfung1031 Jul 11, 2026
c93b379
🐛 修复 GM_audio 恢复重连失败时监听器被静默清空的问题
cyfung1031 Jul 11, 2026
49ad700
🐛 修复 GM_audio everRegistered 跨注册周期残留与恢复期间新监听器被提前 resolve 的问题
cyfung1031 Jul 11, 2026
0ce1049
🐛 修复 GM_audio 恢复重连中 everRegistered 残留与 Promise 链无界增长的问题
cyfung1031 Jul 11, 2026
da4e6ff
🐛 修复 GM_audio 注册取消后 Promise 悬挂
cyfung1031 Jul 11, 2026
9929d03
Update gm_context.ts
cyfung1031 Jul 11, 2026
911771b
Merge branch 'main' into api/GM_audio
cyfung1031 Jul 11, 2026
ab02e5b
fix
cyfung1031 Jul 11, 2026
ed94bd8
Update gm_api.ts
cyfung1031 Jul 11, 2026
cde1792
🐛 preserve GM_audio listeners after reconnect failure
cyfung1031 Jul 11, 2026
460ddff
📄 add GM_audio declarations to editor template
cyfung1031 Jul 11, 2026
d55357c
Merge branch 'main' into api/GM_audio
cyfung1031 Jul 17, 2026
34d3c98
Merge remote-tracking branch 'origin/main' into api/GM_audio
cyfung1031 Jul 20, 2026
1883208
Merge branch 'main' into api/GM_audio
cyfung1031 Jul 20, 2026
fd6172b
i18n
cyfung1031 Jul 20, 2026
a340899
Merge remote-tracking branch 'origin/main' into codex/pr-1550-rework
cyfung1031 Aug 14, 2026
8c55e05
🐛 settle GM_audio lifecycle failures
cyfung1031 Aug 14, 2026
5b30f17
🐛 clean up failed GM_audio forwards
cyfung1031 Aug 14, 2026
f481446
🐛 preserve zero-valued tab identities
cyfung1031 Aug 14, 2026
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
6 changes: 6 additions & 0 deletions packages/message/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,12 @@ describe("Server", () => {
expect(extSender.documentId).toBe("doc-123");
});

it.concurrent("应该保留合法的 tabId 0,而不是当作后台上下文", async () => {
const sender = new SenderRuntime({ tab: { id: 0, windowId: 0 } } as RuntimeMessageSender);

expect(sender.getExtMessageSender()).toMatchObject({ tabId: 0, windowId: 0 });
});

it.concurrent("应该为没有 tab 的 sender 返回 -1 tabId", async () => {
let capturedSender: IGetSender;

Expand Down
8 changes: 4 additions & 4 deletions packages/message/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ export class SenderConnect {
if (this.sender instanceof ExtensionMessageConnect) {
const con = this.sender.getPort();
return {
windowId: con.sender?.tab?.windowId || -1, // -1表示后台脚本
tabId: con.sender?.tab?.id || -1, // -1表示后台脚本
windowId: con.sender?.tab?.windowId ?? -1, // -1表示后台脚本
tabId: con.sender?.tab?.id ?? -1, // -1表示后台脚本
frameId: con.sender?.frameId,
documentId: con.sender?.documentId,
};
Expand Down Expand Up @@ -97,8 +97,8 @@ export class SenderRuntime {
};
}
return {
windowId: sender.tab?.windowId || -1, // -1表示后台脚本
tabId: sender.tab?.id || -1, // -1表示后台脚本
windowId: sender.tab?.windowId ?? -1, // -1表示后台脚本
tabId: sender.tab?.id ?? -1, // -1表示后台脚本
frameId: sender.frameId,
documentId: sender.documentId,
};
Expand Down
43 changes: 43 additions & 0 deletions src/app/service/content/create_context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ describe.concurrent("createContext", () => {
expect(context.GM_cookie.delete.name).toBe("bound GM_cookie.delete");
});

it.concurrent("按 @grant 注入 GM_audio 与 GM.audio 的完整方法", () => {
const legacyContext = createTestContext(["GM_audio"]);
const promiseContext = createTestContext(["GM.audio"]);

for (const context of [legacyContext, promiseContext]) {
expect(context.GM_audio.setMute.name).toBe("bound GM_audio.setMute");
expect(context.GM_audio.getState.name).toBe("bound GM_audio.getState");
expect(context.GM_audio.addStateChangeListener.name).toBe("bound GM_audio.addStateChangeListener");
expect(context.GM_audio.removeStateChangeListener.name).toBe("bound GM_audio.removeStateChangeListener");
expect(context.GM.audio.setMute.name).toBe("bound GM.audio.setMute");
expect(context.GM.audio.getState.name).toBe("bound GM.audio.getState");
expect(context.GM.audio.addStateChangeListener.name).toBe("bound GM.audio.addStateChangeListener");
expect(context.GM.audio.removeStateChangeListener.name).toBe("bound GM.audio.removeStateChangeListener");
}
});

it.concurrent("window grant 先挂到 context.window,再由代理沙盒暴露为 window 方法", () => {
const context = createTestContext(["window.close", "window.focus"]);
const sandbox = createProxyContext(context);
Expand Down Expand Up @@ -178,6 +194,33 @@ describe.concurrent("createContext", () => {
});
expect(listener).toHaveBeenCalledTimes(1);
});

it.concurrent("音频注册尚未返回连接时上下文失效也应立即结算注册", async () => {
let resolveConnection!: (connection: unknown) => void;
const message = {
connect: vi.fn(
() =>
new Promise((resolve) => {
resolveConnection = resolve;
})
),
} as any;
const context = createContext(createScriptInfo(), {}, "vitest", message, message, new Set(["GM.audio"]));
const registration = context.GM.audio.addStateChangeListener(vi.fn());

context.setInvalidContext();
await expect(
Promise.race([
registration.then(
() => "resolved",
(error: unknown) => `rejected:${String(error)}`
),
new Promise<string>((resolve) => setTimeout(() => resolve("timeout"), 0)),
])
).resolves.toBe("resolved");

resolveConnection({ disconnect: vi.fn() });
});
});

describe.concurrent("createProxyContext", () => {
Expand Down
5 changes: 5 additions & 0 deletions src/app/service/content/create_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ export const createContext = (
setInvalidContext() {
if (invalid) return;
invalid = true;
const audioState = this.audioStateChange;
audioState?.settleRegistration?.();
audioState?.connection?.disconnect(true);
audioState?.listeners.clear();
this.audioStateChange = undefined;
this.valueChangeListener.clear();
this.EE.removeAllListeners();
this.runFlag = `${uuidv4()}(invalid)`; // 更改 uuid 防止 runFlag 相关操作
Expand Down
Loading
Loading