diff --git a/src/offscreen.ts b/src/offscreen.ts index 9cc1cb42c..f8c51f8a3 100644 --- a/src/offscreen.ts +++ b/src/offscreen.ts @@ -2,6 +2,7 @@ import LoggerCore from "./app/logger/core"; import MessageWriter from "./app/logger/message_writer"; import { OffscreenManager } from "./app/service/offscreen"; import { ServiceWorkerClientMessage } from "@Packages/message/window_message"; +import { startRepetitivePing } from "./pkg/utils/wakeup-ping"; function main() { // 通过postMessage与SW通信,支持结构化克隆(Blob等) @@ -15,6 +16,7 @@ function main() { // 初始化管理器 const manager = new OffscreenManager(swPostMessage); manager.initManager(); + startRepetitivePing(); } main(); diff --git a/src/pkg/utils/wakeup-ping.ts b/src/pkg/utils/wakeup-ping.ts new file mode 100644 index 000000000..016e95e28 --- /dev/null +++ b/src/pkg/utils/wakeup-ping.ts @@ -0,0 +1,63 @@ +const PING_INTERVAL_MS_1 = 13_225; +const PING_INTERVAL_MS_2 = 17_765; + +/** + * scheduler 用于后台排程:Chrome 94+, Firefox 142+ + * @link https://developer.mozilla.org/en-US/docs/Web/API/Scheduler/postTask + */ +const nativeScheduler = + //@ts-ignore + typeof scheduler !== "undefined" && typeof scheduler?.postTask === "function" && scheduler; + +// 高效的 BroadcastChannel 通讯:service worker 和 offscreen 共用同一通道 +const channel = new BroadcastChannel("custom-ping"); + +export const startRepetitivePing = () => { + if (typeof frameElement === "object" && typeof document === "object" && document) { + let counter = 0; + let isMutationPending = false; + + const pingNode = document.createComment("0"); + + const incrementCounter = () => { + if (!isMutationPending) { + isMutationPending = true; + counter = counter > 8 ? 1 : counter + 1; + pingNode.data = `${counter}`; + } + }; + + const pingTask = async () => { + channel.postMessage({}); + 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(); + } +}; + +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}` }); + }; +}; diff --git a/src/service_worker.ts b/src/service_worker.ts index 407a27922..85d445516 100644 --- a/src/service_worker.ts +++ b/src/service_worker.ts @@ -8,6 +8,7 @@ import { MessageQueue } from "@Packages/message/message_queue"; import { ServiceWorkerMessageSend } from "@Packages/message/window_message"; import migrate, { migrateChromeStorage } from "./app/migrate"; import { cleanInvalidKeys } from "./app/repo/resource"; +import { listenWakeupPing } from "./pkg/utils/wakeup-ping"; migrate(); migrateChromeStorage(); @@ -59,6 +60,10 @@ async function setupOffscreenDocument() { } } +export const onWakeupPing = () => { + console.debug("onWakeupPing"); // 不用记录在系统日志 +}; + function main() { cleanInvalidKeys(); // 初始化管理器 @@ -77,6 +82,7 @@ function main() { manager.initManager(); // 初始化沙盒环境 setupOffscreenDocument(); + listenWakeupPing(onWakeupPing); } main();