Skip to content

Ensure Service Worker intercepts requests from iframe blob-URL documents#837

Merged
brandonpayton merged 1 commit into
mainfrom
emdash/wp-load-script-error-l6vwe
Jul 3, 2026
Merged

Ensure Service Worker intercepts requests from iframe blob-URL documents#837
brandonpayton merged 1 commit into
mainfrom
emdash/wp-load-script-error-l6vwe

Conversation

@brandonpayton

Copy link
Copy Markdown
Member

Why

Logged into wp-admin, opening the site editor produces a 404 for
wp-admin/load-scripts.php (and load-styles.php) in the browser demo. The
rest of wp-admin works, so this looks like a WordPress/nginx bug — it isn't.

The browser demo serves WordPress from an in-kernel nginx/PHP stack and reaches
it through a service worker that intercepts fetch for the /app/… prefix and
bridges it into the Wasm kernel. The service worker can only bridge requests
from documents it controls.

The block/site-editor canvas iframe is mounted from a blob: URL
(URL.createObjectURL(new Blob([html], { type: "text/html" }))). A blob:
document is not service-worker-controlled (and has no base URL), so the
<script src=".../app/wp-admin/load-scripts.php?load[]=wp-polyfill"> that
WordPress injects into that canvas bypasses the bridge entirely and is sent to
the page's real origin (Vite in dev, GitHub Pages in prod), which has no such
file → 404. about:srcdoc iframes, by contrast, are controlled and work.

Reproduced and confirmed in a real headless Chromium against the live demo: the
exact same URL returns 200 (33 KB of real JS) through the bridge from a
controlled document, but 404 when requested by the editor's blob: canvas
iframe. This is browser-app-specific — the request never reaches nginx.

What

A reusable, framework-free DOM patch that neutralizes the whole class of
"blob iframe escapes the bridge" issues for any app, without patching WordPress
or app code.

  • apps/browser-demos/public/blob-iframe-interceptor.js (new) — hooks
    Blob / URL.createObjectURL / URL.revokeObjectURL to track text/html
    blob URLs, and overrides HTMLIFrameElement src (setter/getter) and
    setAttribute so any iframe pointed at a text/html blob URL is rendered from
    srcdoc (an about:srcdoc document the service worker controls). Idempotent,
    a no-op unless a text/html blob iframe is created, preserves iframe.src
    read-back, and leaves all other blob usage (downloads, workers, media)
    untouched.
  • apps/browser-demos/public/service-worker.js — inlines the interceptor
    into the <head> of every bridged HTML document (guarded/idempotent,
    HTML-only) so it runs before any app script creates a blob iframe. Applies to
    all demos, not just WordPress.
  • apps/browser-demos/vite.config.ts — substitutes the interceptor source
    into the SW via a "__BLOB_IFRAME_INTERCEPTOR__" placeholder, mirroring the
    existing "__CORS_PROXY_URL__" mechanism (dev middleware + prod
    writeBundle). Keeps the interceptor a separate, testable file.
  • apps/browser-demos/test/kandelo-wordpress.spec.ts@slow regression
    test: opens the site editor and asserts zero load-scripts/load-styles
    404s and zero /app escapes.
  • docs/browser-support.md — documents the blob-URL service-worker
    boundary and the interceptor.

Verification

End-to-end in headless Chromium against the running demo:

  • Before: 404 frame=blob:… /app/wp-admin/load-scripts.php?load[]=wp-polyfill
  • After: 200 frame=about:srcdoc /app/wp-admin/load-scripts.php?load[]=wp-polyfill
  • Full site-editor run: 0 asset failures, 0 /app escapes;
    window.__kandeloBlobIframePatched === true in the admin document.

Both JS files pass node --check; no type errors in the changed files.

Notes / follow-ups

  • Inline-script injection assumes the app document has no strict script-src
    CSP (WordPress admin doesn't). If a future app ships strict CSP, we'd switch
    to a nonce or a same-origin <script src>.
  • I did not run a full production vite build here (needs node/
    nginx-vfs binaries not published at this ABI locally); the prod path is a
    one-line mirror of the already-proven CORS substitution.

🤖 Generated with Claude Code

The WordPress site editor's canvas iframe is mounted from a blob: URL.
Blob documents are not controlled by the service worker, so their
subresource requests (load-scripts.php/load-styles.php, block assets)
bypass the kernel HTTP bridge and 404 against the static origin even
though the in-kernel nginx serves them correctly. Convert such iframes
to about:srcdoc, which the service worker controls, via a reusable DOM
patch injected into every bridged HTML document.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@brandonpayton brandonpayton changed the title Route blob-URL iframe assets through the service-worker bridge Ensure iframe blob-URL documents still have their requests intercepted by the service worker Jul 2, 2026
@brandonpayton brandonpayton changed the title Ensure iframe blob-URL documents still have their requests intercepted by the service worker Ensure Service Worker intercepts requests from iframe blob-URL documents Jul 2, 2026
@github-actions github-actions Bot enabled auto-merge (squash) July 3, 2026 17:18
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

prepare-merge: runtime/materialization tests passed against the synthetic PR merge; package staging and durable package publishing were skipped. merge-gate=success posted on PR HEAD and squash auto-merge enabled.

@brandonpayton brandonpayton disabled auto-merge July 3, 2026 17:31
@brandonpayton brandonpayton merged commit 56d0a7b into main Jul 3, 2026
90 of 92 checks passed
@brandonpayton brandonpayton deleted the emdash/wp-load-script-error-l6vwe branch July 3, 2026 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant