diff --git a/src/app/service/agent/core/tools/tab_tools.ts b/src/app/service/agent/core/tools/tab_tools.ts index b926a8a96..0e82ecb9f 100644 --- a/src/app/service/agent/core/tools/tab_tools.ts +++ b/src/app/service/agent/core/tools/tab_tools.ts @@ -310,7 +310,7 @@ export function createTabTools(deps: { const tab = await chrome.tabs.update(tabId, { active: true }); if (!tab) throw new Error(`Tab ${tabId} not found`); // 也激活对应窗口 - if (tab.windowId) { + if (tab.windowId && Number.isFinite(tab.windowId) && tab.windowId >= 0) { await chrome.windows.update(tab.windowId, { focused: true }); } return JSON.stringify({ diff --git a/src/app/service/agent/service_worker/dom.ts b/src/app/service/agent/service_worker/dom.ts index b280d9e61..df0f6d951 100644 --- a/src/app/service/agent/service_worker/dom.ts +++ b/src/app/service/agent/service_worker/dom.ts @@ -150,6 +150,9 @@ export class AgentDomService { private async captureVisibleTab(tabId: number, options?: ScreenshotOptions): Promise { const quality = options?.quality ?? 80; const tab = await chrome.tabs.get(tabId); + if (!tab || !Number.isFinite(tab.windowId) || tab.windowId < 0) { + throw new Error(`No windowId for tab ${tabId}`); + } return chrome.tabs.captureVisibleTab(tab.windowId, { format: "jpeg", quality, diff --git a/src/app/service/service_worker/gm_api/gm_api.test.ts b/src/app/service/service_worker/gm_api/gm_api.test.ts index 7617e3fa6..04aafd41a 100644 --- a/src/app/service/service_worker/gm_api/gm_api.test.ts +++ b/src/app/service/service_worker/gm_api/gm_api.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, vi } from "vitest"; import { type IGetSender } from "@Packages/message/server"; import { type ExtMessageSender } from "@Packages/message/types"; import GMApi, { ConnectMatch, getConnectMatched, getExtensionSiteAccessOriginPattern } from "./gm_api"; @@ -119,6 +119,27 @@ describe.concurrent("GM API 注册完整性", () => { }); }); +describe("window.focus", () => { + it("应同时激活标签页并将其所在窗口置于前台", async () => { + const tabsUpdate = vi.fn().mockResolvedValue(undefined); + const windowsUpdate = vi.fn().mockResolvedValue(undefined); + const originalChrome = globalThis.chrome; + vi.stubGlobal("chrome", { + ...originalChrome, + tabs: { ...originalChrome.tabs, update: tabsUpdate }, + windows: { ...originalChrome.windows, update: windowsUpdate }, + }); + const sender = makeSender(); + sender.getSender = () => ({ tab: { id: 42, windowId: 7 } as chrome.tabs.Tab }); + + await GMApi.prototype["window.focus"]({} as GMApiRequest, sender); + + expect(tabsUpdate).toHaveBeenCalledWith(42, { active: true }); + expect(windowsUpdate).toHaveBeenCalledWith(7, { focused: true }); + vi.stubGlobal("chrome", originalChrome); + }); +}); + describe.concurrent("getExtensionSiteAccessOriginPattern", () => { it.concurrent("应生成不带端口的扩展站点访问权限 pattern", () => { expect(getExtensionSiteAccessOriginPattern(new URL("http://127.0.0.1:3000/get"))).toBe("http://127.0.0.1/*"); 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 31eaae2c9..0d174a100 100644 --- a/src/app/service/service_worker/gm_api/gm_api.ts +++ b/src/app/service/service_worker/gm_api/gm_api.ts @@ -1497,11 +1497,17 @@ export default class GMApi { @PermissionVerify.API() async ["window.focus"](request: GMApiRequest, sender: IGetSender) { - const tabId = sender.getSender()?.tab?.id; + const tab = sender.getSender()?.tab; + const tabId = tab?.id; if (Number.isFinite(tabId)) { await chrome.tabs.update(tabId as number, { active: true, }); + if (tab && Number.isFinite(tab.windowId) && tab.windowId >= 0) { + await chrome.windows.update(tab.windowId as number, { + focused: true, + }); + } } }