From d503e0be4849f8bfda8e405b4cc7c3d2d3d31597 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 9 Nov 2026 15:05:01 -0500 Subject: [PATCH 1/3] Media: Only trust core's flag for cross-origin isolation. wp_is_cross_origin_isolated_request() also returned true when any Document-Isolation-Policy header was queued, so a plugin screen that isolates itself for its own reasons, such as the Elementor editor, got the client-side pipeline in every media frame without asking for it. Uploads there then skipped the plugin's own plupload handling. Only the flag wp_send_document_isolation_policy_header() sets now counts. A plugin that wants the pipeline on a screen core does not isolate calls that function instead of sending the header itself. The Gutenberg plugin sends the header directly on the editor screens today, so its media modal falls back to classic uploads until it defers to core's function. See #66490. Claude-Session: https://claude.ai/code/session_018vwYjVChF8EN33VU3DGbni --- src/wp-includes/media.php | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 4cd69ade6cbb7..07910423d8364 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -6996,14 +6996,15 @@ function wp_send_document_isolation_policy_header(): bool { /** * Whether the current request has been set up for cross-origin isolation. * - * True once wp_set_up_cross_origin_isolation() has sent the + * True once wp_send_document_isolation_policy_header() has sent the * Document-Isolation-Policy header for the screen being rendered, which is * what lets scripts that need a cross-origin isolated page, such as the * client-side media upload integration, decide whether to load at all. * - * A plugin can isolate a screen on its own by sending the header directly, - * as the Gutenberg plugin does on the block editor screens, so a header - * already queued for the response counts too. + * A header a plugin sends itself does not count, since the plugin may + * isolate its screen for reasons of its own. To run the client-side + * pipeline on a screen core does not isolate, call + * wp_send_document_isolation_policy_header(). * * @since 7.2.0 * @@ -7014,13 +7015,6 @@ function wp_send_document_isolation_policy_header(): bool { function wp_is_cross_origin_isolated_request(): bool { global $_wp_cross_origin_isolated; - if ( true === $_wp_cross_origin_isolated ) { - return true; - } - - return array_any( - headers_list(), - fn( $header ) => 0 === stripos( $header, 'Document-Isolation-Policy:' ) - ); + return true === $_wp_cross_origin_isolated; } From da497c41a357af7fd1fee9f10796ab9f2cb712b7 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 9 Nov 2026 15:26:10 -0500 Subject: [PATCH 2/3] Media: Document how a screen opts into client-side uploads. The docblocks and test comments around the upload pipeline still named wp_set_up_cross_origin_isolation() or any isolated screen as what enables it, which stopped being true once only the flag set by wp_send_document_isolation_policy_header() counted. They also left out that the function has to run before wp_enqueue_media(), so a late call isolates the page without loading the pipeline. The comments now name the function that sets the flag and state the ordering requirement. No behavior changes. See #66490. Claude-Session: https://claude.ai/code/session_018vwYjV5F1fB1H7E4PDwsnV --- src/wp-includes/media.php | 14 +++++++++----- .../tests/media/wpEnqueueMediaFrameUpload.php | 8 ++++---- .../tests/media/wpEnqueueMediaNewUpload.php | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 07910423d8364..70756fa4a9acf 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -6885,9 +6885,9 @@ function wp_get_media_library_upload_settings(): array { * browser honors Document-Isolation-Policy: without isolation the page never * becomes cross-origin isolated, the script would no-op, and the whole * wp-upload-media dependency chain would be loaded for nothing. The same - * goes for a screen wp_set_up_cross_origin_isolation() did not isolate, - * such as the Customizer or the Site Icon picker: the media frame loads - * there too, but its uploads stay on the classic path. + * goes for a screen wp_send_document_isolation_policy_header() did not + * isolate, such as the Customizer or the Site Icon picker: the media frame + * loads there too, but its uploads stay on the classic path. * * The screen script self-guards at runtime too: when the browser turns out * not to be isolated or lacks client-side media support, it no-ops and the @@ -6945,7 +6945,8 @@ function _wp_enqueue_media_upload_pipeline_script( string $handle ): void { * Covers every `wp.media` frame that uploads through `wp.Uploader`: the * Media Library grid and the media modal the block editor, the site editor * and the block widgets screen open. wp_enqueue_media() calls this on any - * screen that was cross-origin isolated, so it rarely needs calling directly. + * screen wp_send_document_isolation_policy_header() isolated, so it rarely + * needs calling directly. * * @since 7.2.0 */ @@ -6972,6 +6973,9 @@ function wp_enqueue_media_new_upload(): void { * Forcing `crossorigin="anonymous"` would turn those into CORS requests * and break any resource served without `Access-Control-Allow-Origin`. * + * Sending the header also opts the screen's media frames into client-side + * uploads, so call this before wp_enqueue_media() runs. + * * @since 7.2.0 * * @global bool $_wp_cross_origin_isolated Whether the Document-Isolation-Policy header was sent. @@ -7004,7 +7008,7 @@ function wp_send_document_isolation_policy_header(): bool { * A header a plugin sends itself does not count, since the plugin may * isolate its screen for reasons of its own. To run the client-side * pipeline on a screen core does not isolate, call - * wp_send_document_isolation_policy_header(). + * wp_send_document_isolation_policy_header() before wp_enqueue_media(). * * @since 7.2.0 * diff --git a/tests/phpunit/tests/media/wpEnqueueMediaFrameUpload.php b/tests/phpunit/tests/media/wpEnqueueMediaFrameUpload.php index 0b6a917e324ed..81f049fbd601c 100644 --- a/tests/phpunit/tests/media/wpEnqueueMediaFrameUpload.php +++ b/tests/phpunit/tests/media/wpEnqueueMediaFrameUpload.php @@ -56,7 +56,7 @@ public function set_up() { $this->original_wp_scripts = $wp_scripts; $wp_scripts = new WP_Scripts(); - // The screen was cross-origin isolated, as wp_set_up_cross_origin_isolation() records. + // The screen was cross-origin isolated, as wp_send_document_isolation_policy_header() records. $GLOBALS['_wp_cross_origin_isolated'] = true; } @@ -91,9 +91,9 @@ public function test_script_enqueued() { } /** - * A screen wp_set_up_cross_origin_isolation() did not isolate cannot run - * the pipeline, so the media frame there keeps its classic uploads and - * does not download the pipeline bundles. + * A screen wp_send_document_isolation_policy_header() did not isolate + * cannot run the pipeline, so the media frame there keeps its classic + * uploads and does not download the pipeline bundles. * * @ticket 65661 */ diff --git a/tests/phpunit/tests/media/wpEnqueueMediaNewUpload.php b/tests/phpunit/tests/media/wpEnqueueMediaNewUpload.php index 02d00b730c79d..afa75cf4fc12d 100644 --- a/tests/phpunit/tests/media/wpEnqueueMediaNewUpload.php +++ b/tests/phpunit/tests/media/wpEnqueueMediaNewUpload.php @@ -54,7 +54,7 @@ public function set_up() { $this->original_wp_scripts = $wp_scripts; $wp_scripts = new WP_Scripts(); - // The screen was cross-origin isolated, as wp_set_up_cross_origin_isolation() records. + // The screen was cross-origin isolated, as wp_send_document_isolation_policy_header() records. $GLOBALS['_wp_cross_origin_isolated'] = true; } From ff0b152c9b398ee3b5ddd74234710e36428d35ab Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 9 Nov 2026 15:26:10 -0500 Subject: [PATCH 3/3] Tests: Cover the cross-origin isolation flag being set by the header function. The enqueue tests set $_wp_cross_origin_isolated by hand, so nothing would fail if wp_send_document_isolation_policy_header() stopped setting it and the pipeline silently never loaded in production. The new tests assert wp_is_cross_origin_isolated_request() flips to true only after the header is sent, and stays false when the browser does not support the policy. See #66490. Claude-Session: https://claude.ai/code/session_018vwYjV5F1fB1H7E4PDwsnV --- .../tests/media/wpCrossOriginIsolation.php | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/phpunit/tests/media/wpCrossOriginIsolation.php b/tests/phpunit/tests/media/wpCrossOriginIsolation.php index 64884b4e284f9..3722e1618a482 100644 --- a/tests/phpunit/tests/media/wpCrossOriginIsolation.php +++ b/tests/phpunit/tests/media/wpCrossOriginIsolation.php @@ -6,6 +6,7 @@ * @group media * @covers ::wp_set_up_cross_origin_isolation * @covers ::wp_send_document_isolation_policy_header + * @covers ::wp_is_cross_origin_isolated_request * @covers ::wp_is_client_side_media_processing_enabled */ class Tests_Media_wpCrossOriginIsolation extends WP_UnitTestCase { @@ -121,6 +122,32 @@ public function test_sends_header_for_chrome_137() { $this->assertTrue( wp_send_document_isolation_policy_header(), 'The Document-Isolation-Policy header should be sent for Chrome 137.' ); } + /** + * @ticket 66490 + * + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function test_sending_header_marks_request_isolated(): void { + $_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'; + + $this->assertFalse( wp_is_cross_origin_isolated_request(), 'The request should not be isolated before the header is sent.' ); + + wp_send_document_isolation_policy_header(); + + $this->assertTrue( wp_is_cross_origin_isolated_request(), 'The request should be isolated once core sends the header.' ); + } + + /** + * @ticket 66490 + */ + public function test_request_not_isolated_when_header_not_sent(): void { + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0'; + + $this->assertFalse( wp_send_document_isolation_policy_header() ); + $this->assertFalse( wp_is_cross_origin_isolated_request(), 'The request should not be isolated when the header was not sent.' ); + } + /** * @ticket 64766 */