Skip to content

实现 WakeUpPing#1347

Open
cyfung1031 wants to merge 12 commits intorelease/v1.4from
feat/wakeUpPing
Open

实现 WakeUpPing#1347
cyfung1031 wants to merge 12 commits intorelease/v1.4from
feat/wakeUpPing

Conversation

@cyfung1031
Copy link
Copy Markdown
Collaborator

@cyfung1031 cyfung1031 commented Apr 11, 2026

Checklist / 检查清单

  • Fixes mentioned issues / 修复已提及的问题
  • Code reviewed by human / 代码通过人工检查
  • Changes tested / 已完成测试

Description / 描述

隔一段时间从 offscreen ping一下,防止 service worker 休眠

Screenshots / 截图

@CodFrm
Copy link
Copy Markdown
Member

CodFrm commented Apr 22, 2026

直接全局的ping吗?这样是不是不太好,我觉得在gm xhr中ping就好了

@cyfung1031
Copy link
Copy Markdown
Collaborator Author

不是单一解决
这是在避免MV3在闲置时关闭SW

@CodFrm
Copy link
Copy Markdown
Member

CodFrm commented Apr 22, 2026

不是单一解决 这是在避免MV3在闲置时关闭SW

我明白,不过MV3闲置时关闭SW本身就没问题

@cyfung1031
Copy link
Copy Markdown
Collaborator Author

cyfung1031 commented Apr 22, 2026

不是单一解决 这是在避免MV3在闲置时关闭SW

我明白,不过MV3闲置时关闭SW本身就没问题

  1. 应该没有负影响。本身你也是想保持MV2那种长期
  2. 做控制好难。不是发起每个xhr 都要加这个。不然发起10次 xhr 就要做10次。可能是发起xhr后10秒ping一下,然后隔15秒又ping一下, 最后又要处理各种条件把ping 停掉。
  3. 日后其他问题又要特殊处理

当然这个设计最终也有可能被Chrome砍掉吧。毕竟Chrome就是不想搞长驻
所以也不想整合到API
就独立一个东西。被砍就被砍

@CodFrm
Copy link
Copy Markdown
Member

CodFrm commented Apr 23, 2026

不是单一解决 这是在避免MV3在闲置时关闭SW

我明白,不过MV3闲置时关闭SW本身就没问题

  1. 应该没有负影响。本身你也是想保持MV2那种长期
  2. 做控制好难。不是发起每个xhr 都要加这个。不然发起10次 xhr 就要做10次。可能是发起xhr后10秒ping一下,然后隔15秒又ping一下, 最后又要处理各种条件把ping 停掉。
  3. 日后其他问题又要特殊处理

当然这个设计最终也有可能被Chrome砍掉吧。毕竟Chrome就是不想搞长驻 所以也不想整合到API 就独立一个东西。被砍就被砍

也可以接受,就是又是一个架构层面的变更,很担心影响面,留到下个版本吧,准备发布v1.4的正式版本了

@cyfung1031
Copy link
Copy Markdown
Collaborator Author

好像有规管风险。我先处理一下

@cyfung1031 cyfung1031 marked this pull request as draft April 24, 2026 02:40
@cyfung1031
Copy link
Copy Markdown
Collaborator Author

直接全局的ping吗?这样是不是不太好,我觉得在gm xhr中ping就好了

改了只在 xhr 执行时启动
已测试
在 service worker 的 console 打印 self.lastWakeupPing 就可以看到什么时候 ping 了

当 GM xhr 执行时,会延时进行ping,然后如果 GM xhr 未结束,就会再延时进行ping,一直重复

@cyfung1031 cyfung1031 marked this pull request as ready for review April 25, 2026 00:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

该 PR 为了解决 #1343 中「长时间 XHR 导致 Service Worker 休眠后无法收到响应」的问题,引入 offscreen→service worker 的定时唤醒机制(WakeUpPing),并在 GM_xmlhttpRequest 执行期间启停该机制,以降低长请求期间 SW 被回收的概率。

Changes:

  • 新增 wakeup-ping 工具:offscreen 侧周期性 ping,SW 侧通过写入 session storage 触发活动。
  • service worker 启动时注册 WakeUpPing 监听,用于调试/观察唤醒信号。
  • GM_xmlhttpRequest 开始/结束时发送 START/STOP 指令控制 offscreen ping。

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
src/service_worker.ts 注册 WakeUpPing 监听,并增加调试用的 onWakeupPing 回调
src/pkg/utils/wakeup-ping.ts 新增 WakeUpPing 实现:BroadcastChannel +(拟)storage 事件触发
src/offscreen.ts offscreen 启动时初始化 WakeUpPing(等待 SW 指令后开始 ping)
src/app/service/service_worker/gm_api/gm_api.ts GM_xmlhttpRequest 生命周期内发送 START/STOP 控制唤醒

Comment on lines +73 to +85
export const listenWakeupPing = (onWakeupPing: (...args: any) => any) => {
chrome.storage.session.onChanged.addListener((obj) => {
// 消耗 persistentWakeup
if (typeof obj.persistentWakeup !== "undefined") {
// 执行任意 callback
onWakeupPing();
}
});
channel.onmessage = (e) => {
// 触发 chrome storage onChanged 使 service worker 保持活跃
chrome.storage.session.set({ persistentWakeup: `${e.timeStamp}` });
};
};
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

AI 错

Comment on lines +804 to 824
// https://github.com/scriptscat/scriptcat/issues/1343
let wakeupTrigger = true;
wakeupPingCommand(WakeUpCommand.START);
const wakeupStop = () => {
try {
if (wakeupTrigger) {
wakeupTrigger = false;
wakeupPingCommand(WakeUpCommand.STOP);
}
} catch {
// ignored
}
};

const msgConn = sender.getConnect()!;

let isConnDisconnected = false;
msgConn.onDisconnect(() => {
isConnDisconnected = true;
wakeupStop();
});
Comment thread src/pkg/utils/wakeup-ping.ts Outdated
Comment thread src/pkg/utils/wakeup-ping.ts
Comment on lines +24 to +90
// initializeWakeupPing only execute once in offscreen
export const initializeWakeupPing = () => {
if (typeof frameElement === "object" && typeof document === "object" && document) {
let counter = 0;
let isMutationPending = false;

const pingNode = document.createComment("0");

const incrementCounter = () => {
if (startCounter >= 1 && !isMutationPending) {
isMutationPending = true;
counter = counter & 8 ? 1 : counter + 1;
pingNode.data = `${counter}`;
}
};

const pingTask = async () => {
channel.postMessage(true);
incrementCounter();
};

const mutationObserver = new MutationObserver(() => {
if (isMutationPending) {
isMutationPending = false;
const pingIntervalMs = Math.random() * (PING_INTERVAL_MS_2 - PING_INTERVAL_MS_1) + PING_INTERVAL_MS_1;
if (nativeScheduler) {
nativeScheduler.postTask(pingTask, { priority: "background", delay: pingIntervalMs });
} else {
setTimeout(pingTask, pingIntervalMs);
}
}
});
mutationObserver.observe(pingNode, { characterData: true });
// incrementCounter();

channelCommand.onmessage = (e) => {
if (e.data === WakeUpCommand.START) {
startCounter++;
if (startCounter === 1) {
incrementCounter();
}
} else if (e.data === WakeUpCommand.STOP) {
if (startCounter > 0) startCounter--;
}
};
}
};

// initializeWakeupPing only execute once in service worker
export const listenWakeupPing = (onWakeupPing: (...args: any) => any) => {
chrome.storage.session.onChanged.addListener((obj) => {
// 消耗 persistentWakeup
if (typeof obj.persistentWakeup !== "undefined") {
// 执行任意 callback
onWakeupPing();
}
});
channel.onmessage = (e) => {
// 触发 chrome storage onChanged 使 service worker 保持活跃
chrome.storage.session.set({ persistentWakeup: `${e.timeStamp}` });
};
};

// wakeupPingCommand only execute in service worker
export const wakeupPingCommand = (command: WakeUpCommand) => {
channelCommand.postMessage(command);
};
@CodFrm
Copy link
Copy Markdown
Member

CodFrm commented May 10, 2026

Code review

Found 4 issues:

  1. chrome.storage.session.onChanged does not exist — runtime bug breaks the entire wakeup mechanism

StorageArea objects do not have an onChanged property. The correct API is chrome.storage.onChanged, which fires for all storage areas and provides an areaName parameter to filter. This call throws TypeError at runtime, meaning the service worker never receives wakeup pings — defeating the entire purpose of this PR.

// initializeWakeupPing only execute once in service worker
export const listenWakeupPing = (onWakeupPing: (...args: any) => any) => {
chrome.storage.session.onChanged.addListener((obj) => {
// 消耗 persistentWakeup
if (typeof obj.persistentWakeup !== "undefined") {
// 执行任意 callback
onWakeupPing();
}
});

  1. sender.getConnect()! called after wakeupPingCommand(START) without error handling — leaks startCounter on failure

At line 818, sender.getConnect()! uses a non-null assertion outside any try/catch. If getConnect() returns undefined (it can — other methods in this file check for it explicitly), this throws before wakeupStop() is ever registered. The Offscreen startCounter increments via START but never receives STOP, causing the ping loop to run indefinitely.

let wakeupTrigger = true;
wakeupPingCommand(WakeUpCommand.START);
const wakeupStop = () => {
try {
if (wakeupTrigger) {
wakeupTrigger = false;
wakeupPingCommand(WakeUpCommand.STOP);
}
} catch {
// ignored
}
};
const msgConn = sender.getConnect()!;

  1. @ts-ignore on self.lastWakeupPing debug artifact left in production code (CLAUDE.md says "No as any / // @ts-ignore / try-catch swallow")

A debug-only assignment (self.lastWakeupPing) with //@ts-ignore is committed to the service worker entry point. The comment itself says it's only for DevTools debugging. Either extend ServiceWorkerGlobalScope properly or remove it.

export const onWakeupPing = () => {
//@ts-ignore
self.lastWakeupPing = new Date().toLocaleString("zh"); // 僅在後台DevTools debug用
};

  1. English comments in new file (CLAUDE.md says "Comments in Simplified Chinese")

Three comments in the new wakeup-ping.ts are in English: "initializeWakeupPing only execute once in offscreen", "initializeWakeupPing only execute once in service worker", and "wakeupPingCommand only execute in service worker".

// initializeWakeupPing only execute once in offscreen
export const initializeWakeupPing = () => {
if (typeof frameElement === "object" && typeof document === "object" && document) {
let counter = 0;
let isMutationPending = false;
const pingNode = document.createComment("0");
const incrementCounter = () => {
if (startCounter >= 1 && !isMutationPending) {
isMutationPending = true;
counter = counter & 8 ? 1 : counter + 1;
pingNode.data = `${counter}`;
}
};
const pingTask = async () => {
channel.postMessage(true);
incrementCounter();
};
const mutationObserver = new MutationObserver(() => {
if (isMutationPending) {
isMutationPending = false;
const pingIntervalMs = Math.random() * (PING_INTERVAL_MS_2 - PING_INTERVAL_MS_1) + PING_INTERVAL_MS_1;
if (nativeScheduler) {
nativeScheduler.postTask(pingTask, { priority: "background", delay: pingIntervalMs });
} else {
setTimeout(pingTask, pingIntervalMs);
}
}
});
mutationObserver.observe(pingNode, { characterData: true });
// incrementCounter();
channelCommand.onmessage = (e) => {
if (e.data === WakeUpCommand.START) {
startCounter++;
if (startCounter === 1) {
incrementCounter();
}
} else if (e.data === WakeUpCommand.STOP) {
if (startCounter > 0) startCounter--;
}
};
}
};
// initializeWakeupPing only execute once in service worker
export const listenWakeupPing = (onWakeupPing: (...args: any) => any) => {
chrome.storage.session.onChanged.addListener((obj) => {
// 消耗 persistentWakeup
if (typeof obj.persistentWakeup !== "undefined") {
// 执行任意 callback
onWakeupPing();
}
});
channel.onmessage = (e) => {
// 触发 chrome storage onChanged 使 service worker 保持活跃
chrome.storage.session.set({ persistentWakeup: `${e.timeStamp}` });
};
};
// wakeupPingCommand only execute in service worker

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@CodFrm
Copy link
Copy Markdown
Member

CodFrm commented May 10, 2026

我也只是一想,还没验证这个方案是否有效,我弄了一个脚本和网站,我试试

@cyfung1031
Copy link
Copy Markdown
Collaborator Author

我也只是一想,还没验证这个方案是否有效,我弄了一个脚本和网站,我试试

主要是我无法重现 #1343 的问题
你能重现到才能确认方案是否真正解决方法

cyfung1031 and others added 3 commits May 10, 2026 15:04
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] 长时间的 xhr 请求,特定时间后超时,不会返回响应

3 participants