PluginProbe
Gutenberg / trunk
Gutenberg vtrunk
24.1.0 24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 All 404 releases
← All changes | lib/media/load.php +32 -94 23.9.1 → trunk View file →
@@ -346,9 +346,9 @@
346 346 if ( ! user_can( $user_id, 'upload_files' ) ) {
347 347 return;
348 348 }
349 349
350 - gutenberg_start_cross_origin_isolation_output_buffer();
350 + gutenberg_send_document_isolation_policy_header();
351 351 }
352 352
353 353 add_action( 'load-post.php', 'gutenberg_set_up_cross_origin_isolation' );
354 354 add_action( 'load-post-new.php', 'gutenberg_set_up_cross_origin_isolation' );
@@ -364,11 +364,18 @@
364 364
365 365 /**
366 366 * Sends the Document-Isolation-Policy header for cross-origin isolation.
367 367 *
368 - * Uses an output buffer to add crossorigin="anonymous" where needed.
368 + * `isolate-and-credentialless` loads cross-origin subresources without
369 + * credentials instead of blocking them, so no `crossorigin` attribute is
370 + * needed on scripts, styles, images, audio, or video for the page to work.
371 + * Forcing `crossorigin="anonymous"` would turn those into CORS requests
372 + * and break any resource served without `Access-Control-Allow-Origin`,
373 + * such as media offloaded to a CDN.
374 + *
375 + * @return bool Whether the header was sent.
369 376 */
370 -function gutenberg_start_cross_origin_isolation_output_buffer(): void {
377 +function gutenberg_send_document_isolation_policy_header(): bool {
371 378 $chromium_version = gutenberg_get_chromium_major_version();
372 379
373 380 /**
374 381 * Filters whether to use Document-Isolation-Policy for cross-origin isolation.
@@ -386,104 +393,32 @@
386 393 null !== $chromium_version && $chromium_version >= 137
387 394 );
388 395
389 396 if ( ! $use_dip ) {
390 - return;
397 + return false;
391 398 }
392 399
393 - ob_start(
394 - function ( string $output ): string {
395 - header( 'Document-Isolation-Policy: isolate-and-credentialless' );
400 + header( 'Document-Isolation-Policy: isolate-and-credentialless' );
396 401
397 - return gutenberg_add_crossorigin_attributes( $output );
398 - }
399 - );
402 + return true;
400 403 }
401 404
402 405 /**
403 - * Adds crossorigin="anonymous" to relevant tags in the given HTML string.
406 + * Removes `crossorigin` attributes from the printed media templates.
404 407 *
405 - * @param string $html HTML input.
408 + * WordPress 7.1 forces `crossorigin="anonymous"` onto the AUDIO and VIDEO
409 + * tags inside the Backbone `<script type="text/html">` templates whenever
410 + * client-side media processing is enabled. Under
411 + * `Document-Isolation-Policy: isolate-and-credentialless` the attribute is
412 + * not needed to play cross-origin media, and it turns the load into a CORS
413 + * request that fails for media served without CORS headers, such as media
414 + * offloaded to a CDN. See https://core.trac.wordpress.org/ticket/65930.
406 415 *
407 - * @return string Modified HTML.
408 - */
409 -function gutenberg_add_crossorigin_attributes( string $html ): string {
410 - $site_url = site_url();
411 -
412 - $processor = new WP_HTML_Tag_Processor( $html );
413 -
414 - // See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin.
415 - $tags = array(
416 - 'AUDIO' => 'src',
417 - 'LINK' => 'href',
418 - 'SCRIPT' => 'src',
419 - 'VIDEO' => 'src',
420 - 'SOURCE' => 'src',
421 - );
422 -
423 - $tag_names = array_keys( $tags );
424 -
425 - while ( $processor->next_tag() ) {
426 - $tag = $processor->get_tag();
427 -
428 - if ( ! in_array( $tag, $tag_names, true ) ) {
429 - continue;
430 - }
431 -
432 - if ( 'AUDIO' === $tag || 'VIDEO' === $tag ) {
433 - $processor->set_bookmark( 'audio-video-parent' );
434 - }
435 -
436 - $processor->set_bookmark( 'resume' );
437 -
438 - $sought = false;
439 -
440 - $crossorigin = $processor->get_attribute( 'crossorigin' );
441 -
442 - $url = $processor->get_attribute( $tags[ $tag ] );
443 -
444 - if ( is_string( $url ) && ! str_starts_with( $url, $site_url ) && ! str_starts_with( $url, '/' ) && ! is_string( $crossorigin ) ) {
445 - if ( 'SOURCE' === $tag ) {
446 - $sought = $processor->seek( 'audio-video-parent' );
447 -
448 - if ( $sought ) {
449 - $processor->set_attribute( 'crossorigin', 'anonymous' );
450 - }
451 - } else {
452 - $processor->set_attribute( 'crossorigin', 'anonymous' );
453 - }
454 -
455 - if ( $sought ) {
456 - $processor->seek( 'resume' );
457 - $processor->release_bookmark( 'audio-video-parent' );
458 - }
459 - }
460 - }
461 -
462 - return $processor->get_updated_html();
463 -}
464 -
465 -/**
466 - * Updates `crossorigin` attributes in the printed media templates.
467 - *
468 - * Adds `crossorigin="anonymous"` to AUDIO and VIDEO tags inside the
469 - * Backbone `<script type="text/html">` templates so the media modal can
470 - * play cross-origin audio and video under cross-origin isolation. Tags
471 - * that already have the attribute are left untouched so the output does
472 - * not gain duplicates on WordPress versions where Core adds it itself.
473 - *
474 - * IMG is intentionally excluded: under
475 - * `Document-Isolation-Policy: isolate-and-credentialless` the browser
476 - * already loads cross-origin images in credentialless mode, so forcing
477 - * `crossorigin="anonymous"` triggers a CORS request that breaks previews
478 - * of images served without CORS headers, such as media offloaded to a
479 - * CDN. See https://core.trac.wordpress.org/ticket/65673.
480 - *
481 416 * @param string $html The printed media templates.
482 417 *
483 418 * @return string Modified media templates.
484 419 */
485 -function gutenberg_update_media_template_crossorigin_attributes( string $html ): string {
420 +function gutenberg_remove_media_template_crossorigin_attributes( string $html ): string {
486 421 /*
487 422 * The media templates are inside <script type="text/html"> tags,
488 423 * whose content is treated as raw text by the HTML Tag Processor.
489 424 * Extract each script block's content, process it separately,
@@ -496,12 +431,12 @@
496 431 }
497 432 $template_processor = new WP_HTML_Tag_Processor( $script_processor->get_modifiable_text() );
498 433 while ( $template_processor->next_tag() ) {
499 434 if (
500 - in_array( $template_processor->get_tag(), array( 'AUDIO', 'VIDEO' ), true )
501 - && ! is_string( $template_processor->get_attribute( 'crossorigin' ) )
435 + in_array( $template_processor->get_tag(), array( 'AUDIO', 'IMG', 'VIDEO' ), true )
436 + && 'anonymous' === $template_processor->get_attribute( 'crossorigin' )
502 437 ) {
503 - $template_processor->set_attribute( 'crossorigin', 'anonymous' );
438 + $template_processor->remove_attribute( 'crossorigin' );
504 439 }
505 440 }
506 441 $script_processor->set_modifiable_text( $template_processor->get_updated_html() );
507 442 }
@@ -511,13 +446,16 @@
511 446
512 447 /**
513 448 * Overrides templates from wp_print_media_templates with custom ones.
514 449 *
515 - * Updates the `crossorigin` attributes on media tags so cross-origin
516 - * audio and video can be processed under cross-origin isolation without
517 - * breaking previews of images served without CORS headers.
450 + * Only needed on WordPress 7.1, the one release whose
451 + * `wp_print_media_templates()` injects `crossorigin="anonymous"` itself.
518 452 */
519 453 function gutenberg_override_media_templates(): void {
454 + if ( ! function_exists( 'wp_add_crossorigin_attributes' ) || function_exists( 'wp_send_document_isolation_policy_header' ) ) {
455 + return;
456 + }
457 +
520 458 remove_action( 'admin_footer', 'wp_print_media_templates' );
521 459 add_action(
522 460 'admin_footer',
523 461 static function (): void {
@@ -524,9 +462,9 @@
524 462 ob_start();
525 463 wp_print_media_templates();
526 464 $html = (string) ob_get_clean();
527 465
528 - echo gutenberg_update_media_template_crossorigin_attributes( $html ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
466 + echo gutenberg_remove_media_template_crossorigin_attributes( $html ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
529 467 }
530 468 );
531 469 }
532 470