diff --git a/openspec/changes/add-inline-html-renderer/.openspec.yaml b/openspec/changes/add-inline-html-renderer/.openspec.yaml new file mode 100644 index 0000000000000..5e6d53a3fdaa4 --- /dev/null +++ b/openspec/changes/add-inline-html-renderer/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-24 diff --git a/openspec/changes/add-inline-html-renderer/design.md b/openspec/changes/add-inline-html-renderer/design.md new file mode 100644 index 0000000000000..55eb767ae1879 --- /dev/null +++ b/openspec/changes/add-inline-html-renderer/design.md @@ -0,0 +1,99 @@ +## Context + +HTML artifact previews are rendered by `HTMLPreview` (`code-block-preview/html-preview.ts`), which delegates to `linkIframe` (`code-block-preview/iframe-container.ts`). Today `linkIframe`: + +1. Points the iframe at `https://affine.run/static/container.html`. +2. Adds a permissive sandbox including both `allow-scripts` **and** `allow-same-origin`. +3. On `onload`, posts the artifact HTML to the remote container via `contentWindow.postMessage(html, 'https://affine.run')`. + +The remote page is the isolation boundary: it runs on the `affine.run` origin, so artifact scripts execute cross-origin to the host app. The cost is a hard runtime dependency on affine.run for something that is otherwise a purely local render. This renderer is shared by two call sites — the chat code-artifact preview (`ai-tools/code-artifact.ts` → ``) and inserted `affine:code` blocks with `preview: true` in docs (`CodeBlockHtmlPreview` extension) — so both benefit from a local replacement. + +The only existing local path (`adapter-panel/.../adapter-panel-body.ts`) uses ` diff --git a/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/iframe-container.ts b/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/iframe-container.ts index 86124b976d389..2ebab5bf85214 100644 --- a/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/iframe-container.ts +++ b/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/iframe-container.ts @@ -1,17 +1,34 @@ +import { wrapArtifactHtml } from './host-bootstrap'; + +/** + * Sandbox flags for artifact previews. + * + * `allow-same-origin` is deliberately absent. Without it the frame gets a unique + * opaque origin, so artifact scripts run but cannot reach the host document, + * storage, cookies, or auth state. Adding it back would collapse the isolation + * boundary, since frame and embedder would share an origin. + */ +export const ARTIFACT_SANDBOX = [ + 'allow-scripts', + 'allow-forms', + 'allow-modals', + 'allow-popups', + 'allow-popups-to-escape-sandbox', + 'allow-downloads', + 'allow-pointer-lock', +] as const; + +/** + * Render artifact HTML into an iframe entirely locally. + * + * The document is delivered via `srcdoc`, so rendering needs no network and no + * remote container origin. + */ export function linkIframe(iframe: HTMLIFrameElement, html: string) { - // force reload iframe - iframe.src = ''; - iframe.src = 'https://affine.run/static/container.html'; - iframe.sandbox.add( - 'allow-pointer-lock', - 'allow-popups', - 'allow-forms', - 'allow-popups-to-escape-sandbox', - 'allow-downloads', - 'allow-scripts', - 'allow-same-origin' - ); - iframe.onload = () => { - iframe.contentWindow?.postMessage(html, 'https://affine.run'); - }; + iframe.removeAttribute('src'); + + iframe.sandbox.value = ''; + iframe.sandbox.add(...ARTIFACT_SANDBOX); + + iframe.srcdoc = wrapArtifactHtml(html); }