Skip to content

[Remote] Re-enable client-side media processing via Document-Isolation-Policy#3515

Merged
bgrgicak merged 4 commits intoWordPress:trunkfrom
adamsilverstein:fix/3514-reenable-client-side-media
Apr 23, 2026
Merged

[Remote] Re-enable client-side media processing via Document-Isolation-Policy#3515
bgrgicak merged 4 commits intoWordPress:trunkfrom
adamsilverstein:fix/3514-reenable-client-side-media

Conversation

@adamsilverstein
Copy link
Copy Markdown
Member

@adamsilverstein adamsilverstein commented Apr 21, 2026

Motivation for the change, related issues

Fixes #3514.

Playground currently disables Gutenberg's client-side media processing experiment via an __return_false filter in the mu-plugin (0-playground.php#L265-L274). That workaround was added in #3312 because, at the time, Gutenberg was emitting COEP/COOP for the editor and — even when rewritten to Document-Isolation-Policy by the service worker — the editor's inner iframe was crashing.

Gutenberg PR #75991 (shipped in 22.6+) changed the isolation strategy: on Chromium 137+ Gutenberg now sends Document-Isolation-Policy: isolate-and-credentialless directly on editor screens and no longer sends COEP/COOP. That removes the root cause of the breakage — but it also means Playground's existing COEP→DIP rewrite path never fires on current Gutenberg, so the scope is never added to scopesWithCrossOriginIsolation, empty.html doesn't receive DIP, and parent/child DIP parity breaks inside the block editor canvas (see #3320 for why parity matters).

Implementation details

Two small, coordinated changes:

  1. packages/playground/remote/service-worker.ts — broaden the post-response handler (now applyCrossOriginIsolationHeaders) so that:

    • If a response already carries Document-Isolation-Policy, the scope is added to scopesWithCrossOriginIsolation and the response passes through unchanged. This is the modern path once Gutenberg serves DIP directly.
    • If a response carries COEP/COOP, the existing rewrite path continues to operate unchanged (covers older Gutenberg, WP core's wp_set_up_cross_origin_isolation, or custom plugins).

    The existing empty.html branch (which adds DIP to the inner editor iframe when the scope is tracked) needs no change — it now gets populated in both cases.

  2. packages/playground/remote/src/lib/playground-mu-plugin/0-playground.php — remove add_filter('wp_client_side_media_processing_enabled', '__return_false') and its comment block. The workaround is no longer necessary.

Tests

New Playwright spec: packages/playground/website/playwright/e2e/client-side-media.spec.ts

  • Chromium-only (skipped in Firefox/Safari — DIP and client-side media are Chromium-only today).
  • Boots Playground with Gutenberg + gutenberg-media-processing experiment on /wp-admin/post-new.php and asserts, inside the WP admin iframe:
    • window.crossOriginIsolated === true
    • typeof SharedArrayBuffer !== 'undefined'
    • window.__clientSideMediaProcessing === true (Gutenberg's own enablement flag)

Editor rendering under DIP is already covered by the existing document-isolation-policy.spec.ts suite, which implicitly verifies parent/child DIP parity.

Testing instructions

Manual (Chromium ≥ 137):

  1. Run npm run dev and open http://127.0.0.1:5400/website-server/.
  2. Load Guetnberg with this Blueprint (paste into the URL after #):
{
  "$schema": "https://playground.wordpress.net/blueprint-schema.json",
  "landingPage": "/wp-admin/post-new.php",
  "plugins": ["gutenberg"],
  "login": true,
  "steps": [
    {
      "step": "runPHP",
      "code": "<?php require '/wordpress/wp-load.php'; update_option('gutenberg-experiments', array('gutenberg-media-processing' => true));"
    }
  ]
}
  1. The post editor loads without crashing.
  2. In the WP iframe's DevTools console:
  • crossOriginIsolatedtrue
  • typeof SharedArrayBuffer"function"
  • window.__clientSideMediaProcessingtrue
  1. The admin document response carries Document-Isolation-Policy: isolate-and-credentialless and no COEP/COOP.
  2. Upload an image — it is processed client-side (wasm-vips loads; no server-side sub-size generation request).
  3. Upload a small AVIF image (sample attached below) - it works (before this PR it fails)
  4. Non-Chromium (Firefox/Safari): editor still opens; client-side media is simply not engaged (unchanged behavior).
  5. Embedded Playground on a 3rd-party page with no COEP/COOP still loads (DIP is per-document, so this should hold).
  6. Embedding 3p elements on the page, eg. a YouTube embed, still work
  7. Iframe using elements such as the classic block work as expected

Automated:

npx nx e2e playground-website --grep="Document-Isolation-Policy|client-side media|crossOriginIsolated"

Notes on backwards compatibility

  • No user-facing breaking changes.
  • The COEP→DIP rewrite path is preserved, so older WordPress/Gutenberg versions, or plugins that set COEP/COOP themselves, keep working as they do today.
  • Non-Chromium browsers: no regression — DIP is unsupported there, Playground's feature detection returns false, headers pass through, and Gutenberg's JS feature detection falls back the same as before.

Broadens the service worker's cross-origin-isolation handling so that a
scope is marked as isolated whenever its response includes a
`Document-Isolation-Policy` header — not only when the SW rewrites
COEP/COOP to DIP.

This is needed because Gutenberg PR #75991 (merged Mar 2026, shipped in
22.6+) now sends DIP directly on editor screens in Chromium 137+ and
stopped sending COEP/COOP. Without this change, the existing COEP→DIP
rewrite path never fires, so `empty.html` (the block editor's inner
iframe) is not served with DIP and parent/child DIP parity breaks,
crashing the editor.

Rename `rewriteCoopHeadersToDocumentIsolationPolicy` to
`applyCrossOriginIsolationHeaders` to reflect the broadened scope.
Prepares the ground for re-enabling client-side media processing
(WordPress#3514).
Removes the `wp_client_side_media_processing_enabled => __return_false`
filter that was introduced in WordPress#3312 as a temporary workaround after the
editor was crashing when Gutenberg sent COEP/COOP. The workaround is no
longer necessary:

- Gutenberg PR #75991 now sends `Document-Isolation-Policy` directly
  (Chromium 137+) instead of COEP/COOP, avoiding the embed/iframe
  breakage that COEP/COOP caused.
- The companion service worker change makes Playground treat responses
  that already carry DIP the same way it treats rewritten COEP/COOP
  responses, so the block editor's inner `empty.html` iframe continues
  to get DIP and parent/child DIP parity is preserved.

Adds e2e tests that verify, inside the WP admin iframe on
`/wp-admin/post-new.php` with the Gutenberg plugin and the
`gutenberg-media-processing` experiment enabled:

- `window.crossOriginIsolated === true`
- `SharedArrayBuffer` is available
- Gutenberg's `window.__clientSideMediaProcessing` is set to `true`

Non-Chromium browsers are skipped — DIP and Gutenberg's client-side
media path are Chromium-only today. Editor rendering under DIP is still
covered by the existing `document-isolation-policy.spec.ts` suite.

Fixes WordPress#3514.
@adamsilverstein adamsilverstein requested review from a team, bgrgicak and Copilot April 21, 2026 17:22
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Re-enables Gutenberg’s client-side media processing in Playground by tracking cross-origin isolation when Document-Isolation-Policy (DIP) is already present, while preserving the existing COEP/COOP→DIP rewrite path; also removes the MU-plugin workaround that disabled the feature and adds an E2E regression test.

Changes:

  • Update the service worker to track scopes when responses already include Document-Isolation-Policy, and rename/generalize the header handling helper.
  • Remove the MU-plugin filter that forcibly disabled wp_client_side_media_processing_enabled.
  • Add Playwright E2E coverage asserting cross-origin isolation + SharedArrayBuffer + Gutenberg enablement flag in Chromium.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
packages/playground/website/playwright/e2e/client-side-media.spec.ts Adds Chromium-only E2E coverage for cross-origin isolation and client-side media enablement.
packages/playground/remote/src/lib/playground-mu-plugin/0-playground.php Removes temporary filter that disabled client-side media processing.
packages/playground/remote/service-worker.ts Broadens header handling to support direct DIP responses and keeps COEP/COOP rewrite behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/playground/remote/service-worker.ts
Comment thread packages/playground/website/playwright/e2e/client-side-media.spec.ts Outdated
- Opt the client-side-media spec into `channel: 'chromium'`: Playwright's
  default `chromium_headless_shell` does not honor
  Document-Isolation-Policy, so `window.crossOriginIsolated` returns
  false even though Gutenberg sends the DIP response header. The full
  Chromium channel (already installed by CI via `playwright install
  chromium --with-deps`) supports DIP and makes the test assertions
  meaningful.

- Correct the Gutenberg version reference in the service worker
  docblock (21.8 -> 22.6), where Gutenberg PR #75991 actually shipped.

- Extract a small `skipNonChromium` helper to avoid duplicating the
  skip guard across the two tests in the spec.
Move the Chromium-only guard from per-test `test.skip()` in the test
body to a file-level `test.skip(condition)` modifier. The body-level
skip fires after fixtures/browser launch, so WebKit and Firefox workers
errored with "Unsupported <browser> channel 'chromium'" when the
file-level `test.use({ channel: 'chromium' })` tried to apply before
the skip.

A file-level `test.skip()` is evaluated before the worker launches the
browser, so non-Chromium projects no longer try to combine
`channel: 'chromium'` with an incompatible browser and now report as
skipped cleanly.
@adamsilverstein
Copy link
Copy Markdown
Member Author

AVIF file for testing:

Pisa-1152x1536.avif.zip

Copy link
Copy Markdown
Collaborator

@bgrgicak bgrgicak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this @adamsilverstein! I manually tested and everything works as expected.

@bgrgicak bgrgicak merged commit 23edeca into WordPress:trunk Apr 23, 2026
47 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Re-enable client-side media processing in Playground by providing cross-origin isolation

3 participants