diff --git a/src/app/service/service_worker/dnr.ts b/src/app/service/service_worker/dnr.ts new file mode 100644 index 000000000..a312b8c3b --- /dev/null +++ b/src/app/service/service_worker/dnr.ts @@ -0,0 +1,49 @@ +/** + * scheduler 用于 Service Worker 或 Event Page, Chrome 94+, Firefox 142+ + */ +const scheduler_ = + typeof scheduler !== "undefined" && + typeof scheduler?.postTask === "function" && + typeof scheduler?.yield === "function" + ? scheduler + : null; + +// 用于扩充初始化时新增 SessionRules. FireFox 需要等一等才加,否则会失效。 +export const addSessionRules = async (rules: chrome.declarativeNetRequest.Rule[]) => { + await scheduler_?.yield?.(); + try { + await chrome.declarativeNetRequest.updateSessionRules({ + removeRuleIds: [...rules.map((rule) => rule.id)], + addRules: rules, + }); + return true; + } catch (e) { + console.error("chrome.declarativeNetRequest.updateSessionRules:", e); + return e; + } +}; + +export const sessionRuleDynamicAdd = async (rule: chrome.declarativeNetRequest.Rule): Promise => { + try { + await chrome.declarativeNetRequest.updateSessionRules({ + removeRuleIds: [rule.id], + addRules: [rule], + }); + return true; + } catch (e) { + console.error("chrome.declarativeNetRequest.updateSessionRules:", e); + return e; + } +}; + +export const sessionRuleDynamicRemove = async (ruleId: number): Promise => { + try { + await chrome.declarativeNetRequest.updateSessionRules({ + removeRuleIds: [ruleId], + }); + return true; + } catch (e) { + console.error("chrome.declarativeNetRequest.updateSessionRules:", e); + return e; + } +}; diff --git a/src/app/service/service_worker/gm_api/gm_api.ts b/src/app/service/service_worker/gm_api/gm_api.ts index 5d1bf7033..a5b0f3ca7 100644 --- a/src/app/service/service_worker/gm_api/gm_api.ts +++ b/src/app/service/service_worker/gm_api/gm_api.ts @@ -55,6 +55,7 @@ import { BgGMXhr } from "@App/pkg/utils/xhr/bg_gm_xhr"; import { mightPrepareSetClipboard, setClipboard } from "../clipboard"; import { nativePageWindowOpen } from "../../offscreen/gm_api"; import { nextSessionRuleId, removeSessionRuleIdEntry } from "./dnr_id_controller"; +import { addSessionRules, sessionRuleDynamicAdd, sessionRuleDynamicRemove } from "../dnr"; let generatedUniqueMarkerIDs = ""; let generatedUniqueMarkerIDWhen = ""; @@ -87,20 +88,11 @@ const headersSettled = (markerID: string) => { headerModifierMap.delete(markerID); } if (ruleID) { - chrome.declarativeNetRequest.updateSessionRules( - { - removeRuleIds: [ruleID], - }, - () => { - const lastError = chrome.runtime.lastError; - if (lastError) { - // removeRuleIds 失败: 浏览器里仍保留该规则,本地不释放 ruleID 避免复用 - console.error("chrome.declarativeNetRequest.updateSessionRules:", lastError); - return; - } - removeSessionRuleIdEntry(ruleID); - } - ); + sessionRuleDynamicRemove(ruleID).then((removeResult) => { + // removeRuleIds 失败: 浏览器里仍保留该规则,本地不释放 ruleID 避免复用 + if (removeResult !== true) return; + removeSessionRuleIdEntry(ruleID); + }); } }; @@ -749,16 +741,12 @@ export default class GMApi { }, } as chrome.declarativeNetRequest.Rule; headerModifierMap.set(markerID, { rule, redirectNotManual }); - try { - await chrome.declarativeNetRequest.updateSessionRules({ - removeRuleIds: [ruleId], - addRules: [rule], - }); - } catch (e) { + const addResult = await sessionRuleDynamicAdd(rule); + if (addResult !== true) { // addRules 失败: 回滚本地 headerModifierMap 关联并释放 ruleId,避免永久占位导致限额锁死 headerModifierMap.delete(markerID); removeSessionRuleIdEntry(ruleId); - throw e; + throw addResult; } } return true; @@ -1648,18 +1636,7 @@ export default class GMApi { }, }; headerModifierMap.set(markerID, { rule: newRule, redirectNotManual }); - chrome.declarativeNetRequest.updateSessionRules( - { - removeRuleIds: [rule.id], - addRules: [newRule], - }, - () => { - const lastError = chrome.runtime.lastError; - if (lastError) { - console.error("chrome.declarativeNetRequest.updateSessionRules:", lastError); - } - } - ); + sessionRuleDynamicAdd(newRule); return; } } @@ -1723,18 +1700,7 @@ export default class GMApi { tabIds: [chrome.tabs.TAB_ID_NONE], // 只限于后台 service_worker / offscreen }, } as chrome.declarativeNetRequest.Rule; - chrome.declarativeNetRequest.updateSessionRules( - { - removeRuleIds: [ruleId], - addRules: [rule], - }, - () => { - const lastError = chrome.runtime.lastError; - if (lastError) { - console.error("chrome.declarativeNetRequest.updateSessionRules:", lastError); - } - } - ); + addSessionRules([rule]); } start() { diff --git a/src/app/service/service_worker/script.ts b/src/app/service/service_worker/script.ts index f2594f8a0..156c9f93c 100644 --- a/src/app/service/service_worker/script.ts +++ b/src/app/service/service_worker/script.ts @@ -47,6 +47,7 @@ import { getSimilarityScore, ScriptUpdateCheck } from "./script_update_check"; import { LocalStorageDAO } from "@App/app/repo/localStorage"; import { CompiledResourceDAO } from "@App/app/repo/resource"; import { initRegularUpdateCheck } from "./regular_updatecheck"; +import { addSessionRules } from "./dnr"; export type TCheckScriptUpdateOption = Partial< { checkType: "user"; noUpdateCheck?: number } | ({ checkType: "system" } & Record) @@ -299,20 +300,7 @@ export class ScriptService { } } ); - chrome.declarativeNetRequest.updateSessionRules( - { - removeRuleIds: [...rules.map((rule) => rule.id)], - addRules: rules, - }, - () => { - if (chrome.runtime.lastError) { - console.error( - "chrome.runtime.lastError in chrome.declarativeNetRequest.updateSessionRules:", - chrome.runtime.lastError - ); - } - } - ); + addSessionRules(rules); } public async openInstallPageByUrl( diff --git a/src/types/main.d.ts b/src/types/main.d.ts index 0b9624821..a50ba249d 100644 --- a/src/types/main.d.ts +++ b/src/types/main.d.ts @@ -7,6 +7,20 @@ declare module "@App/app/types.d.ts"; type Override = Omit & U; type ValueOf = T[keyof T]; type ReactStateSetter = (value: T | ((prev: T) => T)) => void; +type ResolveFn = (val: T) => void; + +interface SchedulerPostTaskOptions { + delay?: number; + priority?: "user-blocking" | "user-visible" | "background"; + signal?: AbortSignal; +} + +interface Scheduler { + postTask(callback: () => T | Promise, options?: SchedulerPostTaskOptions): Promise; + yield(): Promise; +} + +declare let scheduler: Scheduler | undefined; declare const sandbox: Window;