/** * CDP 命令的基础设施:超时与中止。 * * Electron Debugger 的 sendCommand 在目标页面卡死或 DevTools 通道异常时可能永不 * settle;超时/中止只让调用方继续执行,底层命令后续 settle 时会被安全忽略。 */ export const BROWSER_CDP_COMMAND_TIMEOUT_MS = 8_000 export const BROWSER_OBSERVE_TIMEOUT_MS = 5_000 export const BROWSER_OBSERVE_AX_DEPTH = 8 export const BROWSER_OBSERVE_EXTENDED_AX_DEPTH = 16 /** 高预算 Observe 读取更深的 AX tree,常规观察保持紧凑避免 Chromium 长时间阻塞 */ export function resolveBrowserObserveAxDepth(maxElements: number): number { return maxElements > 240 ? BROWSER_OBSERVE_EXTENDED_AX_DEPTH : BROWSER_OBSERVE_AX_DEPTH } export class BrowserCdpTimeoutError extends Error { constructor(method: string, timeoutMs: number) { super(`浏览器页面未在 ${Math.ceil(timeoutMs / 1_000)} 秒内响应 ${method},请稍后重试或重新加载页面。`) this.name = 'BrowserCdpTimeoutError' } } /** 已发出的 DevTools 命令无法撤销;该错误只保证不再执行后续页面动作 */ export class BrowserOperationAbortedError extends Error { constructor() { super('浏览器操作已停止。已发送的页面指令可能已执行,页面状态请重新观察确认。') this.name = 'BrowserOperationAbortedError' } } export function throwIfBrowserOperationAborted(signal?: AbortSignal): void { if (signal?.aborted) throw new BrowserOperationAbortedError() } export interface CdpResponse { [key: string]: unknown } export function withBrowserCdpTimeout( command: () => Promise, method: string, timeoutMs: number = BROWSER_CDP_COMMAND_TIMEOUT_MS ): Promise { return new Promise((resolve, reject) => { let settled = false const settle = (callback: () => void): void => { if (settled) return settled = true clearTimeout(timer) callback() } const timer = setTimeout(() => settle(() => reject(new BrowserCdpTimeoutError(method, timeoutMs))), timeoutMs) void Promise.resolve() .then(command) .then((value) => settle(() => resolve(value))) .catch((error: unknown) => settle(() => reject(error))) }) }