Skip to content

Commit 7ed4f03

Browse files
Media: stop adding crossorigin attributes in the editor.
Fix an issue where editing a post in Chromium 137+ could end in a fatal error and a blank screen when the page loaded any cross-origin resource. `wp_add_crossorigin_attributes()` ran `WP_HTML_Tag_Processor` over the whole document inside an `ob_start()` display handler, where errors cannot be caught. The injected `crossorigin="anonymous"` was not needed in the first place: `Document-Isolation-Policy: isolate-and-credentialless` loads cross-origin scripts, stylesheets, audio and video without credentials instead of blocking them, and forcing a CORS request broke any host that does not send `Access-Control-Allow-Origin`, such as media offloaded to a CDN. The header is now sent directly by the new `wp_send_document_isolation_policy_header()`, and the editor page is no longer buffered or rewritten. `wp_start_cross_origin_isolation_output_buffer()` and `wp_add_crossorigin_attributes()` are deprecated; the first only sends the header and the second returns the HTML unchanged. `wp_print_media_templates()` no longer adds the attribute to AUDIO and VIDEO tags. Cross-origin isolation and client-side media processing are unaffected. Follow-up to [61844], [62048], [62819]. Props b0b3k, khokansardar, arkaprabhachowdhury, westonruter, andrewserong, algorithmsunlocks, sabernhardt, swissspidy, adamsilverstein. Fixes #65930. See #65673. git-svn-id: https://develop.svn.wordpress.org/trunk@64489 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 8cffc95 commit 7ed4f03

4 files changed

Lines changed: 131 additions & 361 deletions

File tree

‎src/wp-includes/deprecated.php‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6600,3 +6600,41 @@ function _truncate_post_slug( $slug, $length = 200 ) {
66006600

66016601
return wp_truncate_slug( $slug, $length );
66026602
}
6603+
6604+
/**
6605+
* Starts an output buffer that sends the Document-Isolation-Policy header
6606+
* and adds crossorigin="anonymous" to cross-origin resources.
6607+
*
6608+
* The attribute injection was dropped because `isolate-and-credentialless`
6609+
* loads cross-origin resources without it, and forcing CORS mode broke
6610+
* resources served without `Access-Control-Allow-Origin`. Only the header
6611+
* is sent now.
6612+
*
6613+
* @since 7.1.0
6614+
* @deprecated 7.2.0 Use wp_send_document_isolation_policy_header() instead.
6615+
* @see wp_send_document_isolation_policy_header()
6616+
*/
6617+
function wp_start_cross_origin_isolation_output_buffer(): void {
6618+
_deprecated_function( __FUNCTION__, '7.2.0', 'wp_send_document_isolation_policy_header()' );
6619+
6620+
wp_send_document_isolation_policy_header();
6621+
}
6622+
6623+
/**
6624+
* Adds crossorigin="anonymous" to relevant tags in the given HTML string.
6625+
*
6626+
* No longer modifies the HTML. Under `Document-Isolation-Policy:
6627+
* isolate-and-credentialless` cross-origin resources load without the
6628+
* attribute, and adding it broke resources served without CORS headers.
6629+
*
6630+
* @since 7.1.0
6631+
* @deprecated 7.2.0
6632+
*
6633+
* @param string $html HTML input.
6634+
* @return string The unmodified HTML.
6635+
*/
6636+
function wp_add_crossorigin_attributes( string $html ): string {
6637+
_deprecated_function( __FUNCTION__, '7.2.0' );
6638+
6639+
return $html;
6640+
}

‎src/wp-includes/media-template.php‎

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,6 @@ class="wp-video-shortcode {{ classes.join( ' ' ) }}"
156156
function wp_print_media_templates() {
157157
$class = 'media-modal wp-core-ui';
158158

159-
$is_cross_origin_isolation_enabled = wp_is_client_side_media_processing_enabled();
160-
161-
if ( $is_cross_origin_isolation_enabled ) {
162-
ob_start();
163-
}
164-
165159
$alt_text_description = sprintf(
166160
/* translators: 1: Link to tutorial, 2: Additional link attributes, 3: Accessibility text. */
167161
__( '<a href="%1$s" %2$s>Learn how to describe the purpose of the image%3$s</a>. Leave empty if the image is purely decorative.' ),
@@ -1596,48 +1590,4 @@ function wp_print_media_templates() {
15961590
* @since 3.5.0
15971591
*/
15981592
do_action( 'print_media_templates' );
1599-
1600-
if ( $is_cross_origin_isolation_enabled ) {
1601-
$html = (string) ob_get_clean();
1602-
1603-
/*
1604-
* The media templates are inside <script type="text/html"> tags,
1605-
* whose content is treated as raw text by the HTML Tag Processor.
1606-
* Extract each script block's content, process it separately,
1607-
* then reassemble the full output.
1608-
*/
1609-
$script_processor = new WP_HTML_Tag_Processor( $html );
1610-
while ( $script_processor->next_tag( 'SCRIPT' ) ) {
1611-
if ( 'text/html' !== $script_processor->get_attribute( 'type' ) ) {
1612-
continue;
1613-
}
1614-
/*
1615-
* Unlike wp_add_crossorigin_attributes(), this does not check whether
1616-
* URLs are actually cross-origin. Media templates use Underscore.js
1617-
* template expressions (e.g. {{ data.url }}) as placeholder URLs,
1618-
* so actual URLs are not available at parse time.
1619-
* The crossorigin attribute is added unconditionally to all relevant
1620-
* media tags to ensure cross-origin isolation works regardless of
1621-
* the final URL value at render time.
1622-
*
1623-
* IMG is intentionally excluded, matching wp_add_crossorigin_attributes().
1624-
* Under Document-Isolation-Policy: isolate-and-credentialless the browser
1625-
* loads cross-origin images in credentialless mode without CORS headers,
1626-
* so adding crossorigin="anonymous" would force a CORS request and break
1627-
* previews of images served without Access-Control-Allow-Origin headers.
1628-
*/
1629-
$template_processor = new WP_HTML_Tag_Processor( $script_processor->get_modifiable_text() );
1630-
while ( $template_processor->next_tag() ) {
1631-
if (
1632-
in_array( $template_processor->get_tag(), array( 'AUDIO', 'VIDEO' ), true )
1633-
&& ! is_string( $template_processor->get_attribute( 'crossorigin' ) )
1634-
) {
1635-
$template_processor->set_attribute( 'crossorigin', 'anonymous' );
1636-
}
1637-
}
1638-
$script_processor->set_modifiable_text( $template_processor->get_updated_html() );
1639-
}
1640-
1641-
echo $script_processor->get_updated_html();
1642-
}
16431593
}

‎src/wp-includes/media.php‎

Lines changed: 28 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -6648,20 +6648,23 @@ function wp_get_chromium_major_version(): ?int {
66486648
* same-origin iframe access that these editors rely on.
66496649
*
66506650
* @since 7.1.0
6651+
* @since 7.2.0 Returns whether the header was sent.
6652+
*
6653+
* @return bool Whether the Document-Isolation-Policy header was sent.
66516654
*/
6652-
function wp_set_up_cross_origin_isolation(): void {
6655+
function wp_set_up_cross_origin_isolation(): bool {
66536656
if ( ! wp_is_client_side_media_processing_enabled() ) {
6654-
return;
6657+
return false;
66556658
}
66566659

66576660
$screen = get_current_screen();
66586661

66596662
if ( ! $screen ) {
6660-
return;
6663+
return false;
66616664
}
66626665

66636666
if ( ! $screen->is_block_editor() && 'site-editor' !== $screen->id && ! ( 'widgets' === $screen->id && wp_use_widgets_block_editor() ) ) {
6664-
return;
6667+
return false;
66656668
}
66666669

66676670
/*
@@ -6677,7 +6680,7 @@ function wp_set_up_cross_origin_isolation(): void {
66776680

66786681
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
66796682
if ( 'site-editor.php' === $pagenow && ! wp_is_block_theme() && ( ! isset( $_GET['p'] ) || '/' === $_GET['p'] ) ) {
6680-
return;
6683+
return false;
66816684
}
66826685

66836686
/*
@@ -6686,112 +6689,44 @@ function wp_set_up_cross_origin_isolation(): void {
66866689
* which blocks same-origin iframe access that these editors rely on.
66876690
*/
66886691
if ( isset( $_GET['action'] ) && 'edit' !== $_GET['action'] ) {
6689-
return;
6692+
return false;
66906693
}
66916694

66926695
// Cross-origin isolation is not needed if users can't upload files anyway.
66936696
if ( ! current_user_can( 'upload_files' ) ) {
6694-
return;
6697+
return false;
66956698
}
66966699

6697-
wp_start_cross_origin_isolation_output_buffer();
6700+
return wp_send_document_isolation_policy_header();
66986701
}
66996702

67006703
/**
67016704
* Sends the Document-Isolation-Policy header for cross-origin isolation.
67026705
*
6703-
* Uses an output buffer to add crossorigin="anonymous" where needed.
6706+
* The `isolate-and-credentialless` directive loads cross-origin subresources
6707+
* without credentials instead of blocking them, so no `crossorigin` attribute
6708+
* is needed on scripts, styles, images, audio, or video for the page to work.
6709+
* Forcing `crossorigin="anonymous"` would turn those into CORS requests
6710+
* and break any resource served without `Access-Control-Allow-Origin`.
67046711
*
6705-
* @since 7.1.0
6712+
* @since 7.2.0
6713+
*
6714+
* @return bool Whether the header was sent. False if the browser does not
6715+
* support Document-Isolation-Policy or headers were already sent.
67066716
*/
6707-
function wp_start_cross_origin_isolation_output_buffer(): void {
6717+
function wp_send_document_isolation_policy_header(): bool {
6718+
if ( headers_sent() ) {
6719+
return false;
6720+
}
6721+
67086722
$chromium_version = wp_get_chromium_major_version();
67096723

67106724
if ( null === $chromium_version || $chromium_version < 137 ) {
6711-
return;
6725+
return false;
67126726
}
67136727

6714-
ob_start(
6715-
static function ( string $output ): string {
6716-
header( 'Document-Isolation-Policy: isolate-and-credentialless' );
6717-
6718-
return wp_add_crossorigin_attributes( $output );
6719-
}
6720-
);
6721-
}
6722-
6723-
/**
6724-
* Adds crossorigin="anonymous" to relevant tags in the given HTML string.
6725-
*
6726-
* @since 7.1.0
6727-
*
6728-
* @param string $html HTML input.
6729-
* @return string Modified HTML.
6730-
*/
6731-
function wp_add_crossorigin_attributes( string $html ): string {
6732-
$site_url = site_url();
6733-
6734-
$processor = new WP_HTML_Tag_Processor( $html );
6735-
6736-
// See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin.
6737-
$cross_origin_tag_attributes = array(
6738-
'AUDIO' => array( 'src' ),
6739-
'LINK' => array( 'href' ),
6740-
'SCRIPT' => array( 'src' ),
6741-
'VIDEO' => array( 'src', 'poster' ),
6742-
'SOURCE' => array( 'src' ),
6743-
);
6744-
6745-
while ( $processor->next_tag() ) {
6746-
$tag = $processor->get_tag();
6747-
6748-
if ( ! isset( $cross_origin_tag_attributes[ $tag ] ) ) {
6749-
continue;
6750-
}
6751-
$crossorigin = $processor->get_attribute( 'crossorigin' );
6752-
if ( null !== $crossorigin ) {
6753-
continue;
6754-
}
6755-
6756-
if ( 'AUDIO' === $tag || 'VIDEO' === $tag ) {
6757-
$processor->set_bookmark( 'audio-video-parent' );
6758-
}
6759-
6760-
$processor->set_bookmark( 'resume' );
6761-
6762-
$sought = false;
6763-
6764-
$is_cross_origin = false;
6765-
6766-
foreach ( $cross_origin_tag_attributes[ $tag ] as $attr ) {
6767-
$url = $processor->get_attribute( $attr );
6768-
if ( is_string( $url ) && ! str_starts_with( $url, $site_url ) && ! str_starts_with( $url, '/' ) ) {
6769-
$is_cross_origin = true;
6770-
}
6728+
header( 'Document-Isolation-Policy: isolate-and-credentialless' );
67716729

6772-
if ( $is_cross_origin ) {
6773-
break;
6774-
}
6775-
}
6776-
6777-
if ( $is_cross_origin ) {
6778-
if ( 'SOURCE' === $tag ) {
6779-
$sought = $processor->seek( 'audio-video-parent' );
6780-
6781-
if ( $sought ) {
6782-
$processor->set_attribute( 'crossorigin', 'anonymous' );
6783-
}
6784-
} else {
6785-
$processor->set_attribute( 'crossorigin', 'anonymous' );
6786-
}
6787-
6788-
if ( $sought ) {
6789-
$processor->seek( 'resume' );
6790-
$processor->release_bookmark( 'audio-video-parent' );
6791-
}
6792-
}
6793-
}
6794-
6795-
return $processor->get_updated_html();
6730+
return true;
67966731
}
67976732

0 commit comments

Comments
 (0)