diff --git a/frontend/src/lib/components/raw_apps/RawAppCoepWarning.svelte b/frontend/src/lib/components/raw_apps/RawAppCoepWarning.svelte new file mode 100644 index 0000000000..d5172c7232 --- /dev/null +++ b/frontend/src/lib/components/raw_apps/RawAppCoepWarning.svelte @@ -0,0 +1,90 @@ + + + +
+

+ The app editor runs in a cross-origin isolated context (COOP/COEP headers). This is + required for SharedArrayBuffer, which powers the TypeScript language workers and + lets the editor build and preview your frontend live in the browser. +

+

+ A side effect is that the browser refuses to load cross-origin resources (images, scripts, + stylesheets, media…) unless the remote server explicitly opts in with CORS or a + Cross-Origin-Resource-Policy header. Resources from servers that don't are + blocked in the editor preview only. +

+

+ The deployed app is served without these headers, so the same resources load normally there — + open the deployed app link to verify. If you control the remote server, sending + Cross-Origin-Resource-Policy: cross-origin makes the resource load in the editor too. +

+
+
diff --git a/frontend/src/lib/components/raw_apps/RawAppEditor.svelte b/frontend/src/lib/components/raw_apps/RawAppEditor.svelte index 0ef8ac5a58..3e10f5e06d 100644 --- a/frontend/src/lib/components/raw_apps/RawAppEditor.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppEditor.svelte @@ -28,6 +28,7 @@ } from './utils' import { runDomQueryOnHtml, type RawAppDomQuery, type RawAppDomRequester } from './rawAppDom' import InlineElementPrompt from './InlineElementPrompt.svelte' + import RawAppCoepWarning from './RawAppCoepWarning.svelte' import DarkModeObserver from '../DarkModeObserver.svelte' import RawAppSidebar from './RawAppSidebar.svelte' import type { Modules } from './RawAppModules.svelte' @@ -299,6 +300,7 @@ let iframe: HTMLIFrameElement | undefined = $state(undefined) let previewIframe: HTMLIFrameElement | undefined = $state(undefined) + let coepWarning: RawAppCoepWarning | undefined = $state(undefined) let previewIframeLoaded = $state(false) let lastBuild: { css: string; js: string } | undefined = undefined // Detached preview tab/window rendering the same app-preview bundle as the @@ -1104,6 +1106,9 @@ e.origin === window.location.origin ) { feedExternalPreview() + // The detached window is cross-origin isolated like the inline preview, + // so blocked external resources warrant the same COEP warning. + coepWarning?.attachTo(externalPreviewWindow) return } @@ -2324,6 +2329,7 @@ src="/ui_builder/app-preview.html" class="w-full flex-1 block" > + {#if buildError}