// 可独立测试的 HTML 预览安全策略。脚本和 HTTPS 外部资源可运行,满足图表、 // 地图、WebGL 等通用产物;iframe 保持 opaque origin,不能触达 zcbot 宿主权限。 // 使用同源静态宿主页 + postMessage,而非 iframe.srcdoc / blob URL:部分 WebView // 对 srcdoc 支持不完整,原生壳的 URL 白名单又常会误拦 blob: 子 frame。 export const HTML_PREVIEW_CSP = "default-src 'none'; " + "script-src 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval' https:; " + "style-src 'unsafe-inline' https:; img-src data: blob: https:; " + "media-src data: blob: https:; font-src data: https:; connect-src https:; " + "worker-src blob:; child-src blob: https:; frame-src https:; manifest-src https:; " + "object-src 'none'; form-action 'none'; base-uri 'none'"; export const HTML_PREVIEW_SANDBOX = "allow-scripts"; export const HTML_PREVIEW_HOST = "/static/html_preview_host.html"; export function htmlPreviewDocument(text) { const meta = ``; const source = String(text || ""); if (/
]*)?>/i.test(source)) { return source.replace(/]*)?>/i, (head) => head + meta); } return `${meta}${source}`; } export function configureHtmlPreviewFrame(frame, text, title = "HTML 文件预览") { frame.title = title; frame.setAttribute("sandbox", HTML_PREVIEW_SANDBOX); frame.referrerPolicy = "no-referrer"; const document = htmlPreviewDocument(text); frame.addEventListener("load", () => { frame.contentWindow.postMessage({ type: "zcbot-html-preview", document }, "*"); }, { once: true }); frame.src = HTML_PREVIEW_HOST; return frame; }