Changeset 64489
- Timestamp:
- 10/18/2026 10:32:14 PM (29 hours ago)
- Location:
- trunk
- Files:
-
- 4 edited
-
src/wp-includes/deprecated.php (modified) (1 diff)
-
src/wp-includes/media-template.php (modified) (2 diffs)
-
src/wp-includes/media.php (modified) (5 diffs)
-
tests/phpunit/tests/media/wpCrossOriginIsolation.php (modified) (11 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/deprecated.php
r64436 r64489 6601 6601 return wp_truncate_slug( $slug, $length ); 6602 6602 } 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 } -
trunk/src/wp-includes/media-template.php
r64241 r64489 157 157 $class = 'media-modal wp-core-ui'; 158 158 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 165 159 $alt_text_description = sprintf( 166 160 /* translators: 1: Link to tutorial, 2: Additional link attributes, 3: Accessibility text. */ … … 1597 1591 */ 1598 1592 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 whether1616 * URLs are actually cross-origin. Media templates use Underscore.js1617 * 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 relevant1620 * media tags to ensure cross-origin isolation works regardless of1621 * 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 browser1625 * loads cross-origin images in credentialless mode without CORS headers,1626 * so adding crossorigin="anonymous" would force a CORS request and break1627 * 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 }1643 1593 } -
trunk/src/wp-includes/media.php
r64436 r64489 6649 6649 * 6650 6650 * @since 7.1.0 6651 */ 6652 function wp_set_up_cross_origin_isolation(): void { 6651 * @since 7.2.0 Returns whether the header was sent. 6652 * 6653 * @return bool Whether the Document-Isolation-Policy header was sent. 6654 */ 6655 function wp_set_up_cross_origin_isolation(): bool { 6653 6656 if ( ! wp_is_client_side_media_processing_enabled() ) { 6654 return ;6657 return false; 6655 6658 } 6656 6659 … … 6658 6661 6659 6662 if ( ! $screen ) { 6660 return ;6663 return false; 6661 6664 } 6662 6665 6663 6666 if ( ! $screen->is_block_editor() && 'site-editor' !== $screen->id && ! ( 'widgets' === $screen->id && wp_use_widgets_block_editor() ) ) { 6664 return ;6667 return false; 6665 6668 } 6666 6669 … … 6678 6681 // phpcs:ignore WordPress.Security.NonceVerification.Recommended 6679 6682 if ( 'site-editor.php' === $pagenow && ! wp_is_block_theme() && ( ! isset( $_GET['p'] ) || '/' === $_GET['p'] ) ) { 6680 return ;6683 return false; 6681 6684 } 6682 6685 … … 6687 6690 */ 6688 6691 if ( isset( $_GET['action'] ) && 'edit' !== $_GET['action'] ) { 6689 return ;6692 return false; 6690 6693 } 6691 6694 6692 6695 // Cross-origin isolation is not needed if users can't upload files anyway. 6693 6696 if ( ! current_user_can( 'upload_files' ) ) { 6694 return ;6695 } 6696 6697 wp_start_cross_origin_isolation_output_buffer();6697 return false; 6698 } 6699 6700 return wp_send_document_isolation_policy_header(); 6698 6701 } 6699 6702 … … 6701 6704 * Sends the Document-Isolation-Policy header for cross-origin isolation. 6702 6705 * 6703 * Uses an output buffer to add crossorigin="anonymous" where needed. 6704 * 6705 * @since 7.1.0 6706 */ 6707 function wp_start_cross_origin_isolation_output_buffer(): void { 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`. 6711 * 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. 6716 */ 6717 function wp_send_document_isolation_policy_header(): bool { 6718 if ( headers_sent() ) { 6719 return false; 6720 } 6721 6708 6722 $chromium_version = wp_get_chromium_major_version(); 6709 6723 6710 6724 if ( null === $chromium_version || $chromium_version < 137 ) { 6711 return; 6712 } 6713 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 } 6771 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(); 6796 } 6797 6725 return false; 6726 } 6727 6728 header( 'Document-Isolation-Policy: isolate-and-credentialless' ); 6729 6730 return true; 6731 } 6732 -
trunk/tests/phpunit/tests/media/wpCrossOriginIsolation.php
r62819 r64489 6 6 * @group media 7 7 * @covers ::wp_set_up_cross_origin_isolation 8 * @covers ::wp_s tart_cross_origin_isolation_output_buffer8 * @covers ::wp_send_document_isolation_policy_header 9 9 * @covers ::wp_is_client_side_media_processing_enabled 10 10 */ … … 82 82 } 83 83 84 // Clean up any output buffers started during tests.85 while ( ob_get_level() > 1 ) {86 ob_end_clean();87 }88 89 84 if ( null === $this->original_pagenow ) { 90 85 unset( $GLOBALS['pagenow'] ); … … 105 100 add_filter( 'wp_client_side_media_processing_enabled', '__return_false' ); 106 101 107 // Should not error or start an output buffer. 108 $level_before = ob_get_level(); 109 wp_set_up_cross_origin_isolation(); 110 $level_after = ob_get_level(); 111 112 $this->assertSame( $level_before, $level_after ); 102 $this->assertFalse( wp_set_up_cross_origin_isolation() ); 113 103 } 114 104 … … 117 107 */ 118 108 public function test_returns_early_when_no_screen() { 119 // No screen is set, so it should return early. 120 $level_before = ob_get_level(); 121 wp_set_up_cross_origin_isolation(); 122 $level_after = ob_get_level(); 123 124 $this->assertSame( $level_before, $level_after ); 125 } 126 127 /** 128 * This test must run in a separate process because the output buffer 129 * callback sends HTTP headers via header(), which would fail in the 130 * main PHPUnit process where output has already started. 131 * 109 $this->assertFalse( wp_set_up_cross_origin_isolation() ); 110 } 111 112 /** 132 113 * @ticket 64766 133 114 * … … 135 116 * @preserveGlobalState disabled 136 117 */ 137 public function test_starts_output_buffer_for_chrome_137() { 138 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; 139 140 $level_before = ob_get_level(); 141 wp_start_cross_origin_isolation_output_buffer(); 142 $level_after = ob_get_level(); 143 144 $this->assertSame( $level_before + 1, $level_after, 'Output buffer should be started for Chrome 137.' ); 145 146 ob_end_clean(); 147 } 148 149 /** 150 * @ticket 64766 151 */ 152 public function test_does_not_start_output_buffer_for_chrome_136() { 118 public function test_sends_header_for_chrome_137() { 119 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; 120 121 $this->assertTrue( wp_send_document_isolation_policy_header(), 'The Document-Isolation-Policy header should be sent for Chrome 137.' ); 122 } 123 124 /** 125 * @ticket 64766 126 */ 127 public function test_does_not_send_header_for_chrome_136() { 153 128 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36'; 154 129 155 $level_before = ob_get_level(); 156 wp_start_cross_origin_isolation_output_buffer(); 157 $level_after = ob_get_level(); 158 159 $this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Chrome < 137.' ); 160 } 161 162 /** 163 * @ticket 64766 164 */ 165 public function test_does_not_start_output_buffer_for_firefox() { 130 $this->assertFalse( wp_send_document_isolation_policy_header(), 'The Document-Isolation-Policy header should not be sent for Chrome < 137.' ); 131 } 132 133 /** 134 * @ticket 64766 135 */ 136 public function test_does_not_send_header_for_firefox() { 166 137 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0'; 167 138 168 $level_before = ob_get_level(); 169 wp_start_cross_origin_isolation_output_buffer(); 170 $level_after = ob_get_level(); 171 172 $this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Firefox.' ); 173 } 174 175 /** 176 * @ticket 64766 177 */ 178 public function test_does_not_start_output_buffer_for_safari() { 139 $this->assertFalse( wp_send_document_isolation_policy_header(), 'The Document-Isolation-Policy header should not be sent for Firefox.' ); 140 } 141 142 /** 143 * @ticket 64766 144 */ 145 public function test_does_not_send_header_for_safari() { 179 146 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15'; 180 147 181 $level_before = ob_get_level(); 182 wp_start_cross_origin_isolation_output_buffer(); 183 $level_after = ob_get_level(); 184 185 $this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Safari.' ); 148 $this->assertFalse( wp_send_document_isolation_policy_header(), 'The Document-Isolation-Policy header should not be sent for Safari.' ); 149 } 150 151 /** 152 * Runs in the main PHPUnit process, where output has already started. 153 * 154 * @ticket 65930 155 */ 156 public function test_does_not_send_header_when_headers_already_sent() { 157 if ( ! headers_sent() ) { 158 $this->markTestSkipped( 'Requires output to have already been sent.' ); 159 } 160 161 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; 162 163 $this->assertFalse( wp_send_document_isolation_policy_header(), 'The Document-Isolation-Policy header cannot be sent once headers are sent.' ); 186 164 } 187 165 … … 212 190 } 213 191 214 $level_before = ob_get_level(); 215 wp_set_up_cross_origin_isolation(); 216 $level_after = ob_get_level(); 217 218 $this->assertSame( $level_before, $level_after, 'DIP should be skipped on the classic-theme site editor home route.' ); 192 $this->assertFalse( wp_set_up_cross_origin_isolation(), 'DIP should be skipped on the classic-theme site editor home route.' ); 219 193 } 220 194 … … 251 225 $_GET['p'] = '/page/about'; 252 226 253 $level_before = ob_get_level(); 254 wp_set_up_cross_origin_isolation(); 255 $level_after = ob_get_level(); 256 257 $this->assertSame( $level_before + 1, $level_after, 'DIP should be set up on a non-home site editor route.' ); 258 259 ob_end_clean(); 227 $this->assertTrue( wp_set_up_cross_origin_isolation(), 'DIP should be set up on a non-home site editor route.' ); 260 228 } 261 229 … … 281 249 unset( $_GET['p'] ); 282 250 283 $level_before = ob_get_level(); 284 wp_set_up_cross_origin_isolation(); 285 $level_after = ob_get_level(); 286 287 $this->assertSame( $level_before + 1, $level_after, 'DIP should be set up on the block-theme site editor home route.' ); 288 289 ob_end_clean(); 251 $this->assertTrue( wp_set_up_cross_origin_isolation(), 'DIP should be set up on the block-theme site editor home route.' ); 290 252 } 291 253 … … 363 325 364 326 /** 365 * Verifies that cross-origin elements get crossorigin="anonymous" added. 366 * 367 * @ticket 64766 327 * The deprecated output buffer helper only sends the header now. 328 * 329 * @ticket 65930 330 * 331 * @expectedDeprecated wp_start_cross_origin_isolation_output_buffer 368 332 * 369 333 * @runInSeparateProcess 370 334 * @preserveGlobalState disabled 371 * 372 * @dataProvider data_elements_that_should_get_crossorigin 335 */ 336 public function test_deprecated_output_buffer_helper_does_not_buffer() { 337 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; 338 339 $level_before = ob_get_level(); 340 wp_start_cross_origin_isolation_output_buffer(); 341 342 $this->assertSame( $level_before, ob_get_level(), 'No output buffer should be started.' ); 343 } 344 345 /** 346 * The deprecated attribute injector leaves the HTML untouched. 347 * 348 * Under Document-Isolation-Policy: isolate-and-credentialless, cross-origin 349 * scripts, styles, images, audio, and video load without a crossorigin 350 * attribute. Adding one forces a CORS request that fails for resources 351 * served without Access-Control-Allow-Origin headers. 352 * 353 * @ticket 65930 354 * 355 * @expectedDeprecated wp_add_crossorigin_attributes 356 * 357 * @dataProvider data_cross_origin_elements 373 358 * 374 359 * @param string $html HTML input to process. 375 360 */ 376 public function test_output_buffer_adds_crossorigin( $html ) { 377 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; 378 379 ob_start(); 380 381 wp_start_cross_origin_isolation_output_buffer(); 382 echo $html; 383 384 ob_end_flush(); 385 $output = ob_get_clean(); 386 387 $this->assertStringContainsString( 'crossorigin="anonymous"', $output ); 388 } 389 390 /** 391 * Data provider for elements that should receive crossorigin="anonymous". 392 * 393 * @return array[] 394 */ 395 public function data_elements_that_should_get_crossorigin() { 361 public function test_deprecated_attribute_injector_returns_html_unchanged( string $html ) { 362 $this->assertSame( $html, wp_add_crossorigin_attributes( $html ) ); 363 } 364 365 /** 366 * Data provider of cross-origin elements that used to receive crossorigin="anonymous". 367 * 368 * @return array<non-falsy-string, array{ non-falsy-string }> 369 */ 370 public function data_cross_origin_elements(): array { 396 371 return array( 397 372 'cross-origin script' => array( … … 414 389 415 390 /** 416 * Verifies that certain elements do not get crossorigin="anonymous" added. 417 * 418 * Images are excluded because under Document-Isolation-Policy: 419 * isolate-and-credentialless, the browser handles cross-origin images 420 * in credentialless mode without needing explicit CORS headers. 421 * 422 * @ticket 64766 423 * 424 * @runInSeparateProcess 425 * @preserveGlobalState disabled 426 * 427 * @dataProvider data_elements_that_should_not_get_crossorigin 428 * 429 * @param string $html HTML input to process. 430 */ 431 public function test_output_buffer_does_not_add_crossorigin( $html ) { 432 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; 433 434 ob_start(); 435 436 wp_start_cross_origin_isolation_output_buffer(); 437 echo $html; 438 439 ob_end_flush(); 440 $output = ob_get_clean(); 441 442 $this->assertStringNotContainsString( 'crossorigin="anonymous"', $output ); 443 } 444 445 /** 446 * Data provider for elements that should not receive crossorigin="anonymous". 447 * 448 * @return array[] 449 */ 450 public function data_elements_that_should_not_get_crossorigin() { 451 return array( 452 'cross-origin img' => array( 453 '<img src="https://external.example.com/image.jpg" />', 454 ), 455 'cross-origin img with srcset' => array( 456 '<img src="https://external.example.com/image.jpg" srcset="https://external.example.com/image-2x.jpg 2x" />', 457 ), 458 'link with cross-origin imagesrcset only' => array( 459 '<link rel="preload" as="image" imagesrcset="https://external.example.com/image.jpg 1x" href="/local-fallback.jpg" />', 460 ), 461 'relative URL script' => array( 462 '<script src="/wp-includes/js/wp-embed.min.js"></script>', 463 ), 464 ); 465 } 466 467 /** 468 * Same-origin URLs should not get crossorigin="anonymous". 469 * 470 * Uses site_url() at runtime since the test domain varies by CI config. 471 * 472 * @ticket 64766 473 * 474 * @runInSeparateProcess 475 * @preserveGlobalState disabled 476 */ 477 public function test_output_buffer_does_not_add_crossorigin_to_same_origin() { 478 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; 479 480 ob_start(); 481 482 wp_start_cross_origin_isolation_output_buffer(); 483 echo '<script src="' . site_url( '/wp-includes/js/wp-embed.min.js' ) . '"></script>'; 484 485 ob_end_flush(); 486 $output = ob_get_clean(); 487 488 $this->assertStringNotContainsString( 'crossorigin="anonymous"', $output ); 489 } 490 491 /** 492 * Elements that already have a crossorigin attribute should not be modified. 493 * 494 * @ticket 64766 495 * 496 * @runInSeparateProcess 497 * @preserveGlobalState disabled 498 */ 499 public function test_output_buffer_does_not_override_existing_crossorigin() { 500 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; 501 502 ob_start(); 503 504 wp_start_cross_origin_isolation_output_buffer(); 505 echo '<script src="https://external.example.com/script.js" crossorigin="use-credentials"></script>'; 506 507 ob_end_flush(); 508 $output = ob_get_clean(); 509 510 $this->assertStringContainsString( 'crossorigin="use-credentials"', $output, 'Existing crossorigin attribute should not be overridden.' ); 511 $this->assertStringNotContainsString( 'crossorigin="anonymous"', $output ); 512 } 513 514 /** 515 * Multiple tags in the same output should each be handled correctly. 516 * 517 * @ticket 64766 518 * 519 * @runInSeparateProcess 520 * @preserveGlobalState disabled 521 */ 522 public function test_output_buffer_handles_mixed_tags() { 523 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; 524 525 ob_start(); 526 527 wp_start_cross_origin_isolation_output_buffer(); 528 echo '<img src="https://external.example.com/image.jpg" />'; 529 echo '<script src="https://external.example.com/script.js"></script>'; 530 echo '<audio src="https://external.example.com/audio.mp3"></audio>'; 531 532 ob_end_flush(); 533 $output = ob_get_clean(); 534 535 // IMG should NOT have crossorigin. 536 $this->assertStringContainsString( '<img src="https://external.example.com/image.jpg" />', $output, 'IMG should not be modified.' ); 537 538 // Script and audio should have crossorigin. 539 $this->assertSame( 2, substr_count( $output, 'crossorigin="anonymous"' ), 'Script and audio should both get crossorigin, but not img.' ); 540 } 541 542 /** 543 * IMG tags in the media manager templates must not receive 544 * crossorigin="anonymous", matching wp_add_crossorigin_attributes(). 545 * 546 * Adding the attribute forces a CORS request that breaks previews of 547 * images served without Access-Control-Allow-Origin headers, such as 548 * media offloaded to a CDN. 391 * The media manager templates must not carry crossorigin="anonymous". 392 * 393 * Adding the attribute forces a CORS request that breaks previews and 394 * playback of media served without Access-Control-Allow-Origin headers, 395 * such as media offloaded to a CDN. 549 396 * 550 397 * @ticket 65673 398 * @ticket 65930 551 399 * 552 400 * @covers ::wp_print_media_templates 553 401 */ 554 public function test_print_media_templates_does_not_add_crossorigin _to_img() {402 public function test_print_media_templates_does_not_add_crossorigin() { 555 403 require_once ABSPATH . WPINC . '/media-template.php'; 556 404 … … 561 409 $output = ob_get_clean(); 562 410 563 $this->assertMatchesRegularExpression( '/<img\b/i', $output, 'Expected the media templates to contain IMG tags.' ); 564 $this->assertDoesNotMatchRegularExpression( '/<img\b[^>]*\bcrossorigin\b/i', $output, 'IMG tags in the media templates must not receive a crossorigin attribute.' ); 565 $this->assertMatchesRegularExpression( '/<(?:audio|video)\b[^>]*crossorigin="anonymous"/i', $output, 'AUDIO and VIDEO tags in the media templates should still receive crossorigin="anonymous".' ); 411 $this->assertMatchesRegularExpression( '/<(?:img|audio|video)\b/i', $output, 'Expected the media templates to contain media tags.' ); 412 $this->assertDoesNotMatchRegularExpression( '/<(?:img|audio|video)\b[^>]*\bcrossorigin\b/i', $output, 'Media tags in the media templates must not receive a crossorigin attribute.' ); 566 413 } 567 414 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)