import { browser, type Browser } from 'wxt/browser'; import type { ActiveTabInfo, BrowserTarget } from '@/types/models'; import { ExtensionError } from '@/shared/errors'; import { resolveBrowserTabInfo } from './isolation'; type DocumentProbeResult = Browser.scripting.InjectionResult & { documentId?: string }; function probeDocument() { return { url: location.href }; } export function scriptingTarget(target: BrowserTarget): Browser.scripting.InjectionTarget { if (target.documentId && !import.meta.env.FIREFOX) { return { tabId: target.tabId, documentIds: [target.documentId] } as unknown as Browser.scripting.InjectionTarget; } return { tabId: target.tabId, frameIds: [target.frameId] }; } export async function resolveDocumentTarget(input: BrowserTarget | number): Promise { const requested: BrowserTarget = typeof input === 'number' ? { tabId: input, frameId: 0 } : { ...input, frameId: input.frameId ?? 0 }; if (import.meta.env.FIREFOX) { try { const frame = await browser.webNavigation.getFrame({ tabId: requested.tabId, frameId: requested.frameId }); if (!frame) throw new Error('目标 frame 不存在'); return { tabId: requested.tabId, frameId: requested.frameId }; } catch (error) { throw new ExtensionError('target_unavailable', error instanceof Error ? error.message : String(error)); } } let probe: DocumentProbeResult | undefined; try { [probe] = await browser.scripting.executeScript({ target: { tabId: requested.tabId, frameIds: [requested.frameId] }, world: 'MAIN', func: probeDocument, }) as DocumentProbeResult[]; } catch (error) { throw new ExtensionError('target_unavailable', error instanceof Error ? error.message : String(error)); } if (!probe) throw new ExtensionError('target_unavailable', '无法定位目标页面文档'); if (requested.documentId && probe.documentId && requested.documentId !== probe.documentId) { throw new ExtensionError('stale_document', '目标页面已经刷新或导航,请重新获取页面上下文'); } return { tabId: requested.tabId, frameId: probe.frameId, documentId: probe.documentId || requested.documentId }; } async function findRecentHttpTab(): Promise { const active = (await browser.tabs.query({ active: true, currentWindow: true }))[0]; if (active?.url && /^https?:/i.test(active.url)) return active; const tabs = await browser.tabs.query({ currentWindow: true }); return tabs.filter((tab) => tab.url && /^https?:/i.test(tab.url)) .sort((left, right) => (right.lastAccessed || 0) - (left.lastAccessed || 0))[0]; } export async function getTab(tabId?: number): Promise { const tab = tabId ? await browser.tabs.get(tabId) : await findRecentHttpTab(); if (!tab?.id || !tab.url) throw new Error('无法读取当前标签页'); return resolveBrowserTabInfo(tab); } export const getActiveTab = () => getTab(); export async function activateTab(tabId?: number): Promise { const tab = tabId ? await browser.tabs.get(tabId) : await browser.tabs.get((await getActiveTab()).id); await browser.tabs.update(tab.id, { active: true }); const window = await browser.windows.get(tab.windowId); if (window.state === 'minimized') await browser.windows.update(tab.windowId, { state: 'normal' }); await browser.windows.update(tab.windowId, { focused: true }); } export async function scheduleBrowserInstanceClose(): Promise<{ closing: boolean; windowCount: number }> { const windowIds = (await browser.windows.getAll()) .map((window) => window.id) .filter((id): id is number => typeof id === 'number'); if (!windowIds.length) return { closing: false, windowCount: 0 }; globalThis.setTimeout(() => { void Promise.all(windowIds.map((id) => browser.windows.remove(id).catch(() => undefined))); }, 250); return { closing: true, windowCount: windowIds.length }; }