From 320d2f5bdf554048a2748ded297416778bf34113 Mon Sep 17 00:00:00 2001 From: EYHN Date: Thu, 26 Jun 2025 10:50:38 +0800 Subject: [PATCH] feat(editor): use affine container url in preview (#12919) ## Summary by CodeRabbit - **Bug Fixes** - Improved code block preview rendering to only display the preview when appropriate, preventing unwanted previews. - **Refactor** - Simplified the HTML preview system by always using a secure iframe-based approach and removing the WebContainer integration. - Updated iframe permissions and content delivery for enhanced security and compatibility. - **Chores** - Removed the "Enable Web Container" feature flag and all related internal logic. --- .../affine/blocks/code/src/code-block.ts | 2 +- .../src/services/feature-flag-service.ts | 2 - .../code-block-preview/html-preview.ts | 37 +----- .../code-block-preview/iframe-container.ts | 17 ++- .../code-block-preview/web-container.ts | 110 ------------------ .../core/src/modules/feature-flag/constant.ts | 8 -- 6 files changed, 22 insertions(+), 154 deletions(-) delete mode 100644 packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/web-container.ts diff --git a/blocksuite/affine/blocks/code/src/code-block.ts b/blocksuite/affine/blocks/code/src/code-block.ts index 92bc747a40..f8bcacb7d3 100644 --- a/blocksuite/affine/blocks/code/src/code-block.ts +++ b/blocksuite/affine/blocks/code/src/code-block.ts @@ -450,7 +450,7 @@ export class CodeBlockComponent extends CaptionedBlockComponent contenteditable="false" class="affine-code-block-preview" > - ${previewContext?.renderer(this.model)} + ${shouldRenderPreview && previewContext?.renderer(this.model)} ${this.renderChildren(this.model)} ${Object.values(this.widgets)} diff --git a/blocksuite/affine/shared/src/services/feature-flag-service.ts b/blocksuite/affine/shared/src/services/feature-flag-service.ts index 659388d1e3..bdbf5c55b8 100644 --- a/blocksuite/affine/shared/src/services/feature-flag-service.ts +++ b/blocksuite/affine/shared/src/services/feature-flag-service.ts @@ -21,7 +21,6 @@ export interface BlockSuiteFlags { enable_table_virtual_scroll: boolean; enable_turbo_renderer: boolean; enable_dom_renderer: boolean; - enable_web_container: boolean; } export class FeatureFlagService extends StoreExtension { @@ -47,7 +46,6 @@ export class FeatureFlagService extends StoreExtension { enable_table_virtual_scroll: false, enable_turbo_renderer: false, enable_dom_renderer: false, - enable_web_container: false, }); setFlag(key: keyof BlockSuiteFlags, value: boolean) { diff --git a/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/html-preview.ts b/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/html-preview.ts index cf149a4e96..316155f0d9 100644 --- a/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/html-preview.ts +++ b/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/html-preview.ts @@ -1,8 +1,6 @@ -import track from '@affine/track'; import { CodeBlockPreviewExtension } from '@blocksuite/affine/blocks/code'; import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; import type { CodeBlockModel } from '@blocksuite/affine/model'; -import { FeatureFlagService } from '@blocksuite/affine/shared/services'; import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; import { css, html, LitElement, type PropertyValues } from 'lit'; import { property, query, state } from 'lit/decorators.js'; @@ -10,7 +8,6 @@ import { choose } from 'lit/directives/choose.js'; import { styleMap } from 'lit/directives/style-map.js'; import { linkIframe } from './iframe-container'; -import { linkWebContainer } from './web-container'; export const CodeBlockHtmlPreview = CodeBlockPreviewExtension( 'html', @@ -81,34 +78,12 @@ export class HTMLPreview extends SignalWatcher(WithDisposable(LitElement)) { private _link() { this.state = 'loading'; - const featureFlagService = this.model.store.get(FeatureFlagService); - const isWebContainerEnabled = featureFlagService.getFlag( - 'enable_web_container' - ); - - if (isWebContainerEnabled) { - linkWebContainer(this.iframe, this.model) - .then(() => { - this.state = 'finish'; - }) - .catch(error => { - const errorMessage = `Failed to link WebContainer: ${error}`; - - console.error(errorMessage); - track.doc.editor.codeBlock.htmlBlockPreviewFailed({ - type: errorMessage, - }); - - this.state = 'error'; - }); - } else { - try { - linkIframe(this.iframe, this.model); - this.state = 'finish'; - } catch (error) { - console.error('HTML preview iframe failed:', error); - this.state = 'error'; - } + try { + linkIframe(this.iframe, this.model); + this.state = 'finish'; + } catch (error) { + console.error('HTML preview iframe failed:', error); + this.state = 'error'; } } 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 e2b40143e6..a7fc055e12 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 @@ -2,6 +2,19 @@ import type { CodeBlockModel } from '@blocksuite/affine/model'; export function linkIframe(iframe: HTMLIFrameElement, model: CodeBlockModel) { const html = model.props.text.toString(); - iframe.srcdoc = html; - iframe.sandbox.add('allow-scripts', 'allow-same-origin'); + // 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'); + }; } diff --git a/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/web-container.ts b/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/web-container.ts deleted file mode 100644 index 797d5007c3..0000000000 --- a/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/web-container.ts +++ /dev/null @@ -1,110 +0,0 @@ -import type { CodeBlockModel } from '@blocksuite/affine-model'; -import { WebContainer } from '@webcontainer/api'; - -// cross-browser replacement for `Promise.withResolvers` -interface Deferred { - promise: Promise; - resolve: (value: T | PromiseLike) => void; - reject: (reason?: any) => void; -} -const createDeferred = (): Deferred => { - let resolve!: (value: T | PromiseLike) => void; - let reject!: (reason?: any) => void; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - return { promise, resolve, reject }; -}; - -let sharedWebContainer: WebContainer | null = null; -let bootPromise: Promise | null = null; - -const getSharedWebContainer = async (): Promise => { - if (sharedWebContainer) { - return sharedWebContainer; - } - - if (bootPromise) { - return bootPromise; - } - - bootPromise = WebContainer.boot(); - - try { - sharedWebContainer = await bootPromise; - return sharedWebContainer; - } catch (e) { - throw new Error('Failed to boot WebContainer: ' + e); - } -}; - -let serveUrl: string | null = null; -let settingServerUrlPromise: Promise | null = null; - -const resetServerUrl = () => { - serveUrl = null; - settingServerUrlPromise = null; -}; - -const getServeUrl = async (): Promise => { - if (serveUrl) { - return serveUrl; - } - - if (settingServerUrlPromise) { - return settingServerUrlPromise; - } - - const { promise, resolve, reject } = createDeferred(); - settingServerUrlPromise = promise; - - try { - const webContainer = await getSharedWebContainer(); - await webContainer.fs.writeFile( - 'package.json', - `{ - "name":"preview", - "devDependencies":{"serve":"^14.0.0"} - }` - ); - - const dispose = webContainer.on('server-ready', (_, url) => { - dispose(); - serveUrl = url; - resolve(url); - }); - - const installProcess = await webContainer.spawn('npm', ['install']); - await installProcess.exit; - - const serverProcess = await webContainer.spawn('npx', ['serve']); - serverProcess.exit - .then(() => { - resetServerUrl(); - }) - .catch(e => { - resetServerUrl(); - reject(e); - }); - } catch (e) { - resetServerUrl(); - reject(e); - } - - return promise; -}; - -export async function linkWebContainer( - iframe: HTMLIFrameElement, - model: CodeBlockModel -) { - const html = model.props.text.toString(); - const id = model.id; - - const webContainer = await getSharedWebContainer(); - const serveUrl = await getServeUrl(); - - await webContainer.fs.writeFile(`${id}.html`, html); - iframe.src = `${serveUrl}/${id}.html`; -} diff --git a/packages/frontend/core/src/modules/feature-flag/constant.ts b/packages/frontend/core/src/modules/feature-flag/constant.ts index 388fd1c231..a57bbbef4e 100644 --- a/packages/frontend/core/src/modules/feature-flag/constant.ts +++ b/packages/frontend/core/src/modules/feature-flag/constant.ts @@ -274,14 +274,6 @@ export const AFFINE_FLAGS = { configurable: isCanaryBuild, defaultState: false, }, - enable_web_container: { - category: 'blocksuite', - bsFlag: 'enable_web_container', - displayName: 'Enable Web Container', - description: 'Enable web container for code block preview', - defaultState: false, - configurable: true, - }, } satisfies { [key in string]: FlagInfo }; // oxlint-disable-next-line no-redeclare