/** * DOM 工具 - 等待元素、Shadow DOM 穿透 * 从 docs/extension/plan.md §6.5 移植 */ /** * 等待任一选择器出现(MutationObserver + 超时) */ export function waitForAny( selectors: string[], timeoutMs = 10_000 ): Promise { const hit = () => selectors.map(s => document.querySelector(s)).find(Boolean) ?? null; const found = hit(); if (found) return Promise.resolve(found); return new Promise((resolve) => { const timer = setTimeout(() => { observer.disconnect(); resolve(null); // 超时返回 null }, timeoutMs); const observer = new MutationObserver(() => { const el = hit(); if (el) { clearTimeout(timer); observer.disconnect(); resolve(el); } }); observer.observe(document.documentElement, { childList: true, subtree: true }); }); } /** * 穿透 Shadow DOM 查询元素 * 1688 部分组件用了 Web Components */ export function queryAllDeep(selectors: string[]): Element[] { const out: Element[] = []; for (const sel of selectors) { let nodes: NodeListOf; try { nodes = document.querySelectorAll(sel); } catch { continue; // 选择器写错不能拖垮整个扫描 } nodes.forEach(el => { if (el.shadowRoot) { out.push(...Array.from(el.shadowRoot.querySelectorAll('img, video'))); } else { out.push(el); } }); } return out; }