Skip to content

Media: Stop injecting crossorigin attributes under Document-Isolation-Policy - #13446

Open
adamsilverstein wants to merge 1 commit into
WordPress:trunkfrom
adamsilverstein:remove/crossorigin-injection
Open

Media: Stop injecting crossorigin attributes under Document-Isolation-Policy#13446
adamsilverstein wants to merge 1 commit into
WordPress:trunkfrom
adamsilverstein:remove/crossorigin-injection

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Sep 8, 2026

Copy link
Copy Markdown
Member

Trac ticket: https://core.trac.wordpress.org/ticket/65930

I spent some time testing what isolate-and-credentialless actually blocks before deciding how to fix the bookmark bug in #13221, and it turns out the crossorigin injection is not doing anything useful under DIP. So this removes it rather than patching it. Companion Gutenberg PR: WordPress/gutenberg#82614. Safari keeps its injection through the Client-Side Media Everywhere plugin: adamsilverstein/client-side-media-everywhere#63.

Claude ran the browser tests and wrote up the change, here is the summary:

What

Send the Document-Isolation-Policy header directly and stop rewriting the editor page to add crossorigin="anonymous".

  • wp_send_document_isolation_policy_header() sends the header and returns whether it did. wp_set_up_cross_origin_isolation() calls it and now returns that bool, which is also what the tests assert on.
  • wp_start_cross_origin_isolation_output_buffer() and wp_add_crossorigin_attributes() move to deprecated.php. The first only sends the header now, the second returns the HTML unchanged.
  • wp_print_media_templates() no longer forces crossorigin="anonymous" onto the AUDIO and VIDEO tags in the Backbone templates.
  • The output buffer tests become header and no-op tests, and the media template test now checks that no media tag carries the attribute.

Why

isolate-and-credentialless loads cross-origin subresources without credentials instead of blocking them. That is the whole difference from require-corp. So a cross-origin script, stylesheet, image, audio or video element works with no crossorigin attribute at all. Adding crossorigin="anonymous" turns the load into a CORS request, which fails for any host that does not send Access-Control-Allow-Origin, such as media offloaded to a CDN. That is the bug behind #65673, and the AUDIO and VIDEO tags still had it.

Verified with a two-origin harness (localhost:4801 document, 127.0.0.1:4802 resources) in Chromium 151 and Chrome 152, with crossOriginIsolated === true on the DIP pages:

Resource, host without CORS headers No DIP DIP
img, script, stylesheet, audio, video, <source>, no attribute loads loads
same, with crossorigin="anonymous" fails, media error code 4 fails, media error code 4
img with the attribute, host sends CORS loads, canvas readback works loads, canvas readback works

DIP changes nothing in any row. The attribute only buys canvas pixel readback, and only when the host serves CORS. Nothing in core or Gutenberg reads pixels out of remote media; the client-side upload path works on local blobs.

The injection dates from the require-corp era of the client-side media experiment, where it was the only way for anything cross-origin to load. It came along unchanged when the editor switched to DIP in [61844], IMG was carved out in [62048] and [62819] after CDN images broke, and the question of why AUDIO and VIDEO still needed it (asked on #11291) never got an answer.

Removing it also retires the whole class of failure in #65930: running WP_HTML_Tag_Processor over the finished document inside an ob_start() display handler. The reporter there still gets a white screen with the bookmark fix from #13221 applied, so that fix was not reaching the real cause anyway.

Testing

  1. On Chrome 137 or newer, enqueue a cross-origin script on the editor screen, for example wp_enqueue_script( 'x', 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js' ), and open a post. The response still carries Document-Isolation-Policy: isolate-and-credentialless, window.crossOriginIsolated is true, and the script tag has no crossorigin attribute.
  2. Upload an image and confirm client-side processing still runs (the sideload requests in the Network panel).
  3. Point a media library audio or video at a host without CORS headers (the gist in #65673 works) and open it in the media modal. It plays.
  4. npm run test:php -- --filter Tests_Media_wpCrossOriginIsolation passes: 20 tests.

AI Use

Code and description both written with 🤖 Claude Code. I will review and test.

…-Policy.

`isolate-and-credentialless` loads cross-origin subresources without
credentials instead of blocking them, so scripts, styles, images, audio,
and video from other origins work without a `crossorigin` attribute.
Forcing `crossorigin="anonymous"` turns each load into a CORS request,
which fails for any host that does not send `Access-Control-Allow-Origin`,
such as media offloaded to a CDN.

The injection dates from the `require-corp` era of the Gutenberg
experiment, where it was needed for anything cross-origin to load. It was
carried over unchanged when the editor switched to DIP, and has since
caused two regressions (IMG previews, #65673) and a white screen from the
tag processor running inside an output buffer display handler (#65930).

Send the header directly, deprecate the output buffer helper and the
attribute injector, and drop the matching injection from the media
templates.

Props khokansardar, b0b3k, ianmjones, westonruter, andrewserong.
Fixes #65930. See #65673, #64766.
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props adamsilverstein.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@adamsilverstein

Copy link
Copy Markdown
Member Author

Please note the RC for 7.1.1 is September 10th if we want it included in that version. Otherwise it is fine to punt to 7.1.2.

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.

1 participant