diff --git a/src/app/service/extension/extension_env.test.ts b/src/app/service/extension/extension_env.test.ts index 768d59724..a464a05de 100644 --- a/src/app/service/extension/extension_env.test.ts +++ b/src/app/service/extension/extension_env.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { extensionEnv, getExtensionUserAgentData } from "./extension_env"; +import { extensionEnv, getExtensionEnv, getExtensionUserAgentData } from "./extension_env"; describe("extensionEnv 常量", () => { it("读取扩展上下文状态与 manifest 的 incognito 模式", () => { @@ -10,6 +10,17 @@ describe("extensionEnv 常量", () => { // userAgentData 为可选字段,常量初始化时不应填充 expect(extensionEnv.userAgentData).toBeUndefined(); }); + + it("chrome.extension 缺失时仍能初始化环境并默认非隐身", () => { + const originalExtension = (chrome as any).extension; + try { + delete (chrome as any).extension; + expect(() => getExtensionEnv()).not.toThrow(); + expect(getExtensionEnv().inIncognitoContext).toBe(false); + } finally { + (chrome as any).extension = originalExtension; + } + }); }); describe("getExtensionUserAgentData", () => { diff --git a/src/app/service/extension/extension_env.ts b/src/app/service/extension/extension_env.ts index c033c07ce..46c8052e7 100644 --- a/src/app/service/extension/extension_env.ts +++ b/src/app/service/extension/extension_env.ts @@ -6,10 +6,12 @@ export type TExtensionEnv = { type GMUserAgentData = typeof GM_info.userAgentData; -export const extensionEnv: TExtensionEnv = { - inIncognitoContext: chrome.extension.inIncognitoContext, +export const getExtensionEnv = (): TExtensionEnv => ({ + inIncognitoContext: chrome.extension?.inIncognitoContext ?? false, incognitoMode: chrome.runtime.getManifest().incognito, -} satisfies TExtensionEnv; +}); + +export const extensionEnv: TExtensionEnv = getExtensionEnv(); export const getExtensionUserAgentData = async (): Promise => { // @ts-ignore diff --git a/src/app/service/service_worker/runtime.test.ts b/src/app/service/service_worker/runtime.test.ts index 27103b47c..337fbd137 100644 --- a/src/app/service/service_worker/runtime.test.ts +++ b/src/app/service/service_worker/runtime.test.ts @@ -1,6 +1,6 @@ import { initTestEnv } from "@Tests/utils"; import { RuntimeService } from "./runtime"; -import { vi, describe, it, expect, beforeEach, type MockedFunction } from "vitest"; +import { vi, describe, it, expect, beforeEach, afterEach, type MockedFunction } from "vitest"; import { randomUUID } from "crypto"; import type { Script, ScriptRunResource } from "@App/app/repo/scripts"; import { @@ -1192,3 +1192,63 @@ describe("MQ 事件处理效果(enableScripts / deleteScripts / sortedScripts expect((runtime as any).cachedPatterns.has(uuid)).toBe(false); }); }); + +describe("registerUserscripts 注册健康检查", () => { + let runtime: RuntimeService; + let originalScripting: unknown; + + beforeEach(() => { + runtime = _createRuntimeContext().runtime; + originalScripting = (chrome as any).scripting; + (chrome as any).scripting = { + getRegisteredContentScripts: vi.fn().mockResolvedValue([{ id: "scriptcat-scripting" }]), + registerContentScripts: vi.fn().mockResolvedValue(undefined), + unregisterContentScripts: vi.fn().mockResolvedValue(undefined), + }; + vi.spyOn(chrome.userScripts as any, "getScripts").mockResolvedValue([{ id: "scriptcat-inject" }]); + vi.spyOn(chrome.userScripts, "register").mockResolvedValue(undefined); + vi.spyOn(chrome.userScripts, "resetWorldConfiguration").mockResolvedValue(undefined); + vi.spyOn(runtime, "unregisterUserscripts").mockResolvedValue(undefined); + vi.spyOn(runtime as any, "getParticularScriptList").mockResolvedValue([]); + vi.spyOn(runtime as any, "getContentAndInjectScript").mockResolvedValue({ content: [], inject: [] }); + runtime.isUserScriptsAvailable = true; + }); + + afterEach(async () => { + vi.restoreAllMocks(); + await runtime.unregisterUserscripts(); + (chrome as any).scripting = originalScripting; + }); + + const primeRegisteredState = async () => { + await runtime.registerUserscripts(); + vi.clearAllMocks(); + }; + + it("已注册用户脚本但 scripting 广播者丢失时重新注册", async () => { + await primeRegisteredState(); + (chrome as any).scripting.getRegisteredContentScripts.mockResolvedValue([]); + + await runtime.registerUserscripts(); + + expect(chrome.userScripts.getScripts).toHaveBeenCalledWith({ ids: ["scriptcat-inject"] }); + expect((chrome as any).scripting.getRegisteredContentScripts).toHaveBeenCalledWith({ + ids: ["scriptcat-scripting"], + }); + expect(runtime.unregisterUserscripts).toHaveBeenCalled(); + expect(chrome.userScripts.register).toHaveBeenCalled(); + }); + + it("用户脚本和 scripting 广播者都在时跳过重复注册", async () => { + await primeRegisteredState(); + + await runtime.registerUserscripts(); + + expect(chrome.userScripts.getScripts).toHaveBeenCalledWith({ ids: ["scriptcat-inject"] }); + expect((chrome as any).scripting.getRegisteredContentScripts).toHaveBeenCalledWith({ + ids: ["scriptcat-scripting"], + }); + expect(runtime.unregisterUserscripts).not.toHaveBeenCalled(); + expect(chrome.userScripts.register).not.toHaveBeenCalled(); + }); +}); diff --git a/src/app/service/service_worker/runtime.ts b/src/app/service/service_worker/runtime.ts index ca06143f1..ca12ff556 100644 --- a/src/app/service/service_worker/runtime.ts +++ b/src/app/service/service_worker/runtime.ts @@ -1078,7 +1078,8 @@ export class RuntimeService { // 异常情况 // 检查scriptcat-content和scriptcat-inject是否存在 const res = await chrome.userScripts.getScripts({ ids: ["scriptcat-inject"] }); - if (res.length === 1) { + const contentScripts = await chrome.scripting.getRegisteredContentScripts({ ids: ["scriptcat-scripting"] }); + if (res.length === 1 && contentScripts.length === 1) { return; } // scriptcat-content/scriptcat-inject不存在的情况