From 52839e51944e44b2aab36f9de71b14c2f7bd9681 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 1 Mar 2026 13:33:25 +0700 Subject: [PATCH 01/21] Media: Use Document-Isolation-Policy for COI Replace COEP/COOP headers with Document-Isolation-Policy on Chrome 137+ for cross-origin isolation. DIP provides per-document isolation without breaking third-party page builder iframes that rely on same-origin DOM access. Non-DIP browsers skip isolation entirely since COEP/COOP caused CORS failures for embeds and broke plugins like Elementor. --- 75991-backport.md | 13 +++++++ src/wp-includes/media.php | 74 ++++++++++++++++++++++++++++++++++----- 2 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 75991-backport.md diff --git a/75991-backport.md b/75991-backport.md new file mode 100644 index 0000000000000..500ce905810a4 --- /dev/null +++ b/75991-backport.md @@ -0,0 +1,13 @@ +Media: Use Document-Isolation-Policy for cross-origin isolation on Chrome 137+. + +Replace COEP/COOP headers with the new Document-Isolation-Policy header on Chrome 137+ for cross-origin isolation. DIP provides per-document isolation without breaking third-party page builder iframes (e.g. Elementor). Non-DIP browsers skip cross-origin isolation entirely, since COEP/COOP caused CORS failures for embeds. + +* Add `wp_get_chrome_major_version()` helper to detect Chromium browser version. +* Add `wp_use_document_isolation_policy` filter for customization. +* Set `window.__documentIsolationPolicy` JS flag when DIP is active. +* Skip cross-origin isolation when a third-party editor action is detected. +* Send `Document-Isolation-Policy: isolate-and-credentialless` header instead of COEP/COOP. +* Skip the output buffer entirely on non-DIP browsers to prevent CORS failures. + +Props adamsilverstein. +Fixes #XXXXX. See https://github.com/WordPress/gutenberg/pull/75991. diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index efb82399ed688..77f7ce02a80ae 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -6393,6 +6393,18 @@ function wp_set_client_side_media_processing_flag(): void { wp_add_inline_script( 'wp-block-editor', 'window.__clientSideMediaProcessing = true', 'before' ); + $chrome_version = wp_get_chrome_major_version(); + + /** This filter is documented in wp-includes/media.php */ + $use_dip = apply_filters( + 'wp_use_document_isolation_policy', + null !== $chrome_version && $chrome_version >= 137 + ); + + if ( $use_dip ) { + wp_add_inline_script( 'wp-block-editor', 'window.__documentIsolationPolicy = true', 'before' ); + } + /* * Register the @wordpress/vips/worker script module as a dynamic dependency * of the wp-upload-media classic script. This ensures it is included in the @@ -6405,6 +6417,25 @@ function wp_set_client_side_media_processing_flag(): void { ); } +/** + * Returns the major Chrome/Chromium version from the current request's User-Agent. + * + * Matches all Chromium-based browsers (Chrome, Edge, Opera, Brave). + * + * @since 7.0.0 + * + * @return int|null The major Chrome version, or null if not a Chromium browser. + */ +function wp_get_chrome_major_version(): ?int { + if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) { + return null; + } + if ( preg_match( '/Chrome\/(\d+)/', $_SERVER['HTTP_USER_AGENT'], $matches ) ) { + return (int) $matches[1]; + } + return null; +} + /** * Enables cross-origin isolation in the block editor. * @@ -6430,6 +6461,14 @@ function wp_set_up_cross_origin_isolation(): void { return; } + // Skip when a third-party page builder (e.g. Elementor) overrides the + // block editor. DIP isolates the document into its own agent cluster, + // which blocks same-origin iframe access that these editors rely on. + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( isset( $_GET['action'] ) && 'edit' !== $_GET['action'] ) { + return; + } + // Cross-origin isolation is not needed if users can't upload files anyway. if ( ! current_user_can( 'upload_files' ) ) { return; @@ -6441,24 +6480,41 @@ function wp_set_up_cross_origin_isolation(): void { /** * Starts an output buffer to send cross-origin isolation headers. * - * Sends headers and uses an output buffer to add crossorigin="anonymous" - * attributes where needed. + * Uses Document-Isolation-Policy on Chrome 137+ to provide per-document + * cross-origin isolation without breaking third-party iframes. Non-DIP + * browsers skip isolation entirely to avoid CORS failures from COEP/COOP. * * @since 7.0.0 * * @link https://web.dev/coop-coep/ - * - * @global bool $is_safari + * @link https://github.com/nicolo-ribaudo/iframe-coi-dip-proposal */ function wp_start_cross_origin_isolation_output_buffer(): void { - global $is_safari; + $chrome_version = wp_get_chrome_major_version(); - $coep = $is_safari ? 'require-corp' : 'credentialless'; + /** + * Filters whether to use Document-Isolation-Policy instead of COEP/COOP. + * + * Document-Isolation-Policy provides per-document cross-origin isolation + * without affecting other iframes on the page, avoiding breakage of plugins + * like Elementor whose iframes lose credentials/DOM access with COEP. + * + * @since 7.0.0 + * + * @param bool $use_dip Whether DIP is supported and should be used. + */ + $use_dip = apply_filters( + 'wp_use_document_isolation_policy', + null !== $chrome_version && $chrome_version >= 137 + ); + + if ( ! $use_dip ) { + return; + } ob_start( - static function ( string $output ) use ( $coep ): string { - header( 'Cross-Origin-Opener-Policy: same-origin' ); - header( "Cross-Origin-Embedder-Policy: $coep" ); + static function ( string $output ): string { + header( 'Document-Isolation-Policy: isolate-and-credentialless' ); return wp_add_crossorigin_attributes( $output ); } From f4bfea049d4ee517bbffe30626803b9cbfaa3fe1 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 1 Mar 2026 13:39:15 +0700 Subject: [PATCH 02/21] remove md file --- 75991-backport.md | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 75991-backport.md diff --git a/75991-backport.md b/75991-backport.md deleted file mode 100644 index 500ce905810a4..0000000000000 --- a/75991-backport.md +++ /dev/null @@ -1,13 +0,0 @@ -Media: Use Document-Isolation-Policy for cross-origin isolation on Chrome 137+. - -Replace COEP/COOP headers with the new Document-Isolation-Policy header on Chrome 137+ for cross-origin isolation. DIP provides per-document isolation without breaking third-party page builder iframes (e.g. Elementor). Non-DIP browsers skip cross-origin isolation entirely, since COEP/COOP caused CORS failures for embeds. - -* Add `wp_get_chrome_major_version()` helper to detect Chromium browser version. -* Add `wp_use_document_isolation_policy` filter for customization. -* Set `window.__documentIsolationPolicy` JS flag when DIP is active. -* Skip cross-origin isolation when a third-party editor action is detected. -* Send `Document-Isolation-Policy: isolate-and-credentialless` header instead of COEP/COOP. -* Skip the output buffer entirely on non-DIP browsers to prevent CORS failures. - -Props adamsilverstein. -Fixes #XXXXX. See https://github.com/WordPress/gutenberg/pull/75991. From 064ee21c4af19d6e72f0852cbd6c585e0a174c32 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 1 Mar 2026 13:55:51 +0700 Subject: [PATCH 03/21] Disable client-side media processing on non-DIP browsers Without Document-Isolation-Policy, SharedArrayBuffer is unavailable and wasm-vips cannot run. Gate the entire feature (JS flags + module registration) behind the DIP check so browsers that lack support don't attempt to use client-side processing at all. --- src/wp-includes/media.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 77f7ce02a80ae..9966b4587bc47 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -6391,8 +6391,6 @@ function wp_set_client_side_media_processing_flag(): void { return; } - wp_add_inline_script( 'wp-block-editor', 'window.__clientSideMediaProcessing = true', 'before' ); - $chrome_version = wp_get_chrome_major_version(); /** This filter is documented in wp-includes/media.php */ @@ -6401,10 +6399,17 @@ function wp_set_client_side_media_processing_flag(): void { null !== $chrome_version && $chrome_version >= 137 ); - if ( $use_dip ) { - wp_add_inline_script( 'wp-block-editor', 'window.__documentIsolationPolicy = true', 'before' ); + // Client-side media processing requires cross-origin isolation via + // Document-Isolation-Policy. Skip the feature entirely on browsers + // that do not support DIP, since without isolation SharedArrayBuffer + // (needed by wasm-vips) is unavailable. + if ( ! $use_dip ) { + return; } + wp_add_inline_script( 'wp-block-editor', 'window.__clientSideMediaProcessing = true', 'before' ); + wp_add_inline_script( 'wp-block-editor', 'window.__documentIsolationPolicy = true', 'before' ); + /* * Register the @wordpress/vips/worker script module as a dynamic dependency * of the wp-upload-media classic script. This ensures it is included in the From aa34c2df1a6fc486d7d751fe0e672276486475a8 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 1 Mar 2026 14:02:11 +0700 Subject: [PATCH 04/21] Add tests for cross-origin isolation functions Cover wp_get_chrome_major_version(), the output buffer, and wp_set_up_cross_origin_isolation() including DIP detection, browser gating, filter overrides, and third-party editor skip logic. --- .../tests/media/wpGetChromeMajorVersion.php | 71 +++++++++++ .../media/wpSetUpCrossOriginIsolation.php | 71 +++++++++++ ...pStartCrossOriginIsolationOutputBuffer.php | 117 ++++++++++++++++++ 3 files changed, 259 insertions(+) create mode 100644 tests/phpunit/tests/media/wpGetChromeMajorVersion.php create mode 100644 tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php create mode 100644 tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php diff --git a/tests/phpunit/tests/media/wpGetChromeMajorVersion.php b/tests/phpunit/tests/media/wpGetChromeMajorVersion.php new file mode 100644 index 0000000000000..ba4ee55923586 --- /dev/null +++ b/tests/phpunit/tests/media/wpGetChromeMajorVersion.php @@ -0,0 +1,71 @@ +original_user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : null; + } + + public function tear_down() { + if ( null === $this->original_user_agent ) { + unset( $_SERVER['HTTP_USER_AGENT'] ); + } else { + $_SERVER['HTTP_USER_AGENT'] = $this->original_user_agent; + } + parent::tear_down(); + } + + public function test_returns_null_when_no_user_agent() { + unset( $_SERVER['HTTP_USER_AGENT'] ); + $this->assertNull( wp_get_chrome_major_version() ); + } + + public function test_returns_null_for_empty_user_agent() { + $_SERVER['HTTP_USER_AGENT'] = ''; + $this->assertNull( wp_get_chrome_major_version() ); + } + + public function test_returns_null_for_firefox() { + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0'; + $this->assertNull( wp_get_chrome_major_version() ); + } + + public function test_returns_null_for_safari() { + $_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'; + $this->assertNull( wp_get_chrome_major_version() ); + } + + public function test_returns_version_for_chrome() { + $_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->assertSame( 137, wp_get_chrome_major_version() ); + } + + public function test_returns_version_for_edge() { + $_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 Edg/137.0.0.0'; + $this->assertSame( 137, wp_get_chrome_major_version() ); + } + + public function test_returns_version_for_opera() { + $_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 OPR/122.0.0.0'; + $this->assertSame( 136, wp_get_chrome_major_version() ); + } + + public function test_returns_version_for_older_chrome() { + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36'; + $this->assertSame( 100, wp_get_chrome_major_version() ); + } +} diff --git a/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php b/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php new file mode 100644 index 0000000000000..66a2ce208a58a --- /dev/null +++ b/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php @@ -0,0 +1,71 @@ +original_get = $_GET; + } + + public function tear_down() { + $_GET = $this->original_get; + remove_all_filters( 'wp_client_side_media_processing_enabled' ); + parent::tear_down(); + } + + public function test_returns_early_when_client_side_processing_disabled() { + add_filter( 'wp_client_side_media_processing_enabled', '__return_false' ); + + // Should not error or start an output buffer. + $level_before = ob_get_level(); + wp_set_up_cross_origin_isolation(); + $level_after = ob_get_level(); + + $this->assertSame( $level_before, $level_after ); + } + + public function test_returns_early_when_no_screen() { + // No screen is set, so it should return early. + $level_before = ob_get_level(); + wp_set_up_cross_origin_isolation(); + $level_after = ob_get_level(); + + $this->assertSame( $level_before, $level_after ); + } + + public function test_skips_for_third_party_editor_action() { + $_GET['action'] = 'elementor'; + + $level_before = ob_get_level(); + wp_set_up_cross_origin_isolation(); + $level_after = ob_get_level(); + + $this->assertSame( $level_before, $level_after, 'Should skip when action is not "edit".' ); + } + + public function test_does_not_skip_for_edit_action() { + $_GET['action'] = 'edit'; + + // Still won't start the buffer because no screen is set, + // but confirms the action check doesn't block 'edit'. + $level_before = ob_get_level(); + wp_set_up_cross_origin_isolation(); + $level_after = ob_get_level(); + + // Returns early at the screen check, not the action check. + $this->assertSame( $level_before, $level_after ); + } +} diff --git a/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php b/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php new file mode 100644 index 0000000000000..c3fec62395a13 --- /dev/null +++ b/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php @@ -0,0 +1,117 @@ +original_user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : null; + } + + public function tear_down() { + if ( null === $this->original_user_agent ) { + unset( $_SERVER['HTTP_USER_AGENT'] ); + } else { + $_SERVER['HTTP_USER_AGENT'] = $this->original_user_agent; + } + + // Clean up any output buffers started during tests. + while ( ob_get_level() > 1 ) { + ob_end_clean(); + } + + remove_all_filters( 'wp_use_document_isolation_policy' ); + + parent::tear_down(); + } + + public function test_starts_output_buffer_for_chrome_137() { + $_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'; + + $level_before = ob_get_level(); + wp_start_cross_origin_isolation_output_buffer(); + $level_after = ob_get_level(); + + $this->assertSame( $level_before + 1, $level_after, 'Output buffer should be started for Chrome 137.' ); + + ob_end_clean(); + } + + public function test_does_not_start_output_buffer_for_chrome_136() { + $_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'; + + $level_before = ob_get_level(); + wp_start_cross_origin_isolation_output_buffer(); + $level_after = ob_get_level(); + + $this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Chrome < 137.' ); + } + + public function test_does_not_start_output_buffer_for_firefox() { + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0'; + + $level_before = ob_get_level(); + wp_start_cross_origin_isolation_output_buffer(); + $level_after = ob_get_level(); + + $this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Firefox.' ); + } + + public function test_does_not_start_output_buffer_for_safari() { + $_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'; + + $level_before = ob_get_level(); + wp_start_cross_origin_isolation_output_buffer(); + $level_after = ob_get_level(); + + $this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Safari.' ); + } + + public function test_filter_can_force_enable_dip() { + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0'; + add_filter( 'wp_use_document_isolation_policy', '__return_true' ); + + $level_before = ob_get_level(); + wp_start_cross_origin_isolation_output_buffer(); + $level_after = ob_get_level(); + + $this->assertSame( $level_before + 1, $level_after, 'Filter should force-enable output buffer.' ); + + ob_end_clean(); + } + + public function test_filter_can_force_disable_dip() { + $_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'; + add_filter( 'wp_use_document_isolation_policy', '__return_false' ); + + $level_before = ob_get_level(); + wp_start_cross_origin_isolation_output_buffer(); + $level_after = ob_get_level(); + + $this->assertSame( $level_before, $level_after, 'Filter should disable output buffer.' ); + } + + public function test_output_buffer_adds_crossorigin_attributes() { + $_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'; + + wp_start_cross_origin_isolation_output_buffer(); + echo ''; + + // Flush the output buffer to trigger the callback. + $output = ob_get_flush(); + + $this->assertStringContainsString( 'crossorigin="anonymous"', $output ); + } +} From c936dcc9a9eea5c535b2f443ca3bd08cea4bd711 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 1 Mar 2026 15:49:19 +0700 Subject: [PATCH 05/21] Update for DIP-only approach Remove __documentIsolationPolicy JS flag and DIP early-return from wp_set_client_side_media_processing_flag(). Update docblocks and comments to reflect DIP-only cross-origin isolation. --- src/wp-includes/media.php | 39 ++++--------------- .../media/wpSetUpCrossOriginIsolation.php | 2 +- 2 files changed, 9 insertions(+), 32 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 9966b4587bc47..74619a7eada31 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -6391,24 +6391,7 @@ function wp_set_client_side_media_processing_flag(): void { return; } - $chrome_version = wp_get_chrome_major_version(); - - /** This filter is documented in wp-includes/media.php */ - $use_dip = apply_filters( - 'wp_use_document_isolation_policy', - null !== $chrome_version && $chrome_version >= 137 - ); - - // Client-side media processing requires cross-origin isolation via - // Document-Isolation-Policy. Skip the feature entirely on browsers - // that do not support DIP, since without isolation SharedArrayBuffer - // (needed by wasm-vips) is unavailable. - if ( ! $use_dip ) { - return; - } - wp_add_inline_script( 'wp-block-editor', 'window.__clientSideMediaProcessing = true', 'before' ); - wp_add_inline_script( 'wp-block-editor', 'window.__documentIsolationPolicy = true', 'before' ); /* * Register the @wordpress/vips/worker script module as a dynamic dependency @@ -6445,11 +6428,10 @@ function wp_get_chrome_major_version(): ?int { * Enables cross-origin isolation in the block editor. * * Required for enabling SharedArrayBuffer for WebAssembly-based - * media processing in the editor. + * media processing in the editor. Uses Document-Isolation-Policy + * on supported browsers (Chrome 137+). * * @since 7.0.0 - * - * @link https://web.dev/coop-coep/ */ function wp_set_up_cross_origin_isolation(): void { if ( ! wp_is_client_side_media_processing_enabled() ) { @@ -6466,8 +6448,8 @@ function wp_set_up_cross_origin_isolation(): void { return; } - // Skip when a third-party page builder (e.g. Elementor) overrides the - // block editor. DIP isolates the document into its own agent cluster, + // Skip when a third-party page builder overrides the block editor. + // DIP isolates the document into its own agent cluster, // which blocks same-origin iframe access that these editors rely on. // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['action'] ) && 'edit' !== $_GET['action'] ) { @@ -6483,26 +6465,21 @@ function wp_set_up_cross_origin_isolation(): void { } /** - * Starts an output buffer to send cross-origin isolation headers. + * Sends the Document-Isolation-Policy header for cross-origin isolation. * - * Uses Document-Isolation-Policy on Chrome 137+ to provide per-document - * cross-origin isolation without breaking third-party iframes. Non-DIP - * browsers skip isolation entirely to avoid CORS failures from COEP/COOP. + * Uses an output buffer to add crossorigin="anonymous" where needed. * * @since 7.0.0 - * - * @link https://web.dev/coop-coep/ - * @link https://github.com/nicolo-ribaudo/iframe-coi-dip-proposal */ function wp_start_cross_origin_isolation_output_buffer(): void { $chrome_version = wp_get_chrome_major_version(); /** - * Filters whether to use Document-Isolation-Policy instead of COEP/COOP. + * Filters whether to use Document-Isolation-Policy for cross-origin isolation. * * Document-Isolation-Policy provides per-document cross-origin isolation * without affecting other iframes on the page, avoiding breakage of plugins - * like Elementor whose iframes lose credentials/DOM access with COEP. + * whose iframes lose credentials/DOM access. * * @since 7.0.0 * diff --git a/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php b/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php index 66a2ce208a58a..4116614c2ca2d 100644 --- a/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php +++ b/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php @@ -47,7 +47,7 @@ public function test_returns_early_when_no_screen() { } public function test_skips_for_third_party_editor_action() { - $_GET['action'] = 'elementor'; + $_GET['action'] = 'third_party_editor'; $level_before = ob_get_level(); wp_set_up_cross_origin_isolation(); From 602018af67a13aebf0efa1a10477ab5a7a27b864 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 1 Mar 2026 17:12:30 +0700 Subject: [PATCH 06/21] Fix header tests to run in separate processes Tests that flush output buffers call header() which fails when PHPUnit has already sent output. Adding @runInSeparateProcess prevents "headers already sent" errors. --- .../wpStartCrossOriginIsolationOutputBuffer.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php b/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php index c3fec62395a13..76742060903d2 100644 --- a/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php +++ b/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php @@ -37,6 +37,10 @@ public function tear_down() { parent::tear_down(); } + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ public function test_starts_output_buffer_for_chrome_137() { $_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'; @@ -79,6 +83,10 @@ public function test_does_not_start_output_buffer_for_safari() { $this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Safari.' ); } + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ public function test_filter_can_force_enable_dip() { $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0'; add_filter( 'wp_use_document_isolation_policy', '__return_true' ); @@ -103,6 +111,10 @@ public function test_filter_can_force_disable_dip() { $this->assertSame( $level_before, $level_after, 'Filter should disable output buffer.' ); } + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ public function test_output_buffer_adds_crossorigin_attributes() { $_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'; From 291c608745f8638e3fdd105e560f09f99848743f Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 1 Mar 2026 17:13:26 +0700 Subject: [PATCH 07/21] Add __documentIsolationPolicy JS flag Sets window.__documentIsolationPolicy when DIP is active so JS can distinguish DIP from COOP/COEP cross-origin isolation and skip unnecessary iframe credentialless attributes. --- src/wp-includes/media.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 74619a7eada31..92c64c134e899 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -6393,6 +6393,18 @@ function wp_set_client_side_media_processing_flag(): void { wp_add_inline_script( 'wp-block-editor', 'window.__clientSideMediaProcessing = true', 'before' ); + $chrome_version = wp_get_chrome_major_version(); + + /** This filter is documented in src/wp-includes/media.php */ + $use_dip = apply_filters( + 'wp_use_document_isolation_policy', + null !== $chrome_version && $chrome_version >= 137 + ); + + if ( $use_dip ) { + wp_add_inline_script( 'wp-block-editor', 'window.__documentIsolationPolicy = true', 'before' ); + } + /* * Register the @wordpress/vips/worker script module as a dynamic dependency * of the wp-upload-media classic script. This ensures it is included in the From 05dcc3682926da1e9f9ad856bce72b664c8c0bd4 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 1 Mar 2026 17:49:38 +0700 Subject: [PATCH 08/21] Tests: Fix output buffer test to correctly capture callback-processed output ob_get_flush() returns the original unprocessed buffer content, not the callback-processed result. Use a nested buffer approach instead: start an outer buffer, flush the inner buffer (triggering the callback), then get the processed content from the outer buffer. Co-Authored-By: Claude Opus 4.6 --- .../media/wpStartCrossOriginIsolationOutputBuffer.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php b/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php index 76742060903d2..3932dedd6fd6d 100644 --- a/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php +++ b/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php @@ -118,11 +118,15 @@ public function test_filter_can_force_disable_dip() { public function test_output_buffer_adds_crossorigin_attributes() { $_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'; + // Start an outer buffer to capture the callback-processed output. + ob_start(); + wp_start_cross_origin_isolation_output_buffer(); echo ''; - // Flush the output buffer to trigger the callback. - $output = ob_get_flush(); + // Flush the inner buffer to trigger the callback, sending processed output to the outer buffer. + ob_end_flush(); + $output = ob_get_clean(); $this->assertStringContainsString( 'crossorigin="anonymous"', $output ); } From 0bdef9d80cf173af019ceb96435069aea1d333fa Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 2 Mar 2026 10:52:25 +0700 Subject: [PATCH 09/21] Remove wp_use_document_isolation_policy filter DIP headers are sent based on browser capability alone. The existing wp_client_side_media_processing_enabled filter already gates the entire feature higher in the call chain. --- src/wp-includes/media.php | 26 ++-------------- ...pStartCrossOriginIsolationOutputBuffer.php | 30 ------------------- 2 files changed, 2 insertions(+), 54 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 92c64c134e899..1307ce3f0b115 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -6395,13 +6395,7 @@ function wp_set_client_side_media_processing_flag(): void { $chrome_version = wp_get_chrome_major_version(); - /** This filter is documented in src/wp-includes/media.php */ - $use_dip = apply_filters( - 'wp_use_document_isolation_policy', - null !== $chrome_version && $chrome_version >= 137 - ); - - if ( $use_dip ) { + if ( null !== $chrome_version && $chrome_version >= 137 ) { wp_add_inline_script( 'wp-block-editor', 'window.__documentIsolationPolicy = true', 'before' ); } @@ -6486,23 +6480,7 @@ function wp_set_up_cross_origin_isolation(): void { function wp_start_cross_origin_isolation_output_buffer(): void { $chrome_version = wp_get_chrome_major_version(); - /** - * Filters whether to use Document-Isolation-Policy for cross-origin isolation. - * - * Document-Isolation-Policy provides per-document cross-origin isolation - * without affecting other iframes on the page, avoiding breakage of plugins - * whose iframes lose credentials/DOM access. - * - * @since 7.0.0 - * - * @param bool $use_dip Whether DIP is supported and should be used. - */ - $use_dip = apply_filters( - 'wp_use_document_isolation_policy', - null !== $chrome_version && $chrome_version >= 137 - ); - - if ( ! $use_dip ) { + if ( null === $chrome_version || $chrome_version < 137 ) { return; } diff --git a/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php b/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php index 3932dedd6fd6d..2d318927c9581 100644 --- a/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php +++ b/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php @@ -32,8 +32,6 @@ public function tear_down() { ob_end_clean(); } - remove_all_filters( 'wp_use_document_isolation_policy' ); - parent::tear_down(); } @@ -83,34 +81,6 @@ public function test_does_not_start_output_buffer_for_safari() { $this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Safari.' ); } - /** - * @runInSeparateProcess - * @preserveGlobalState disabled - */ - public function test_filter_can_force_enable_dip() { - $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0'; - add_filter( 'wp_use_document_isolation_policy', '__return_true' ); - - $level_before = ob_get_level(); - wp_start_cross_origin_isolation_output_buffer(); - $level_after = ob_get_level(); - - $this->assertSame( $level_before + 1, $level_after, 'Filter should force-enable output buffer.' ); - - ob_end_clean(); - } - - public function test_filter_can_force_disable_dip() { - $_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'; - add_filter( 'wp_use_document_isolation_policy', '__return_false' ); - - $level_before = ob_get_level(); - wp_start_cross_origin_isolation_output_buffer(); - $level_after = ob_get_level(); - - $this->assertSame( $level_before, $level_after, 'Filter should disable output buffer.' ); - } - /** * @runInSeparateProcess * @preserveGlobalState disabled From d501fb9c1e0cdedcd540139959e655f0f0deab5a Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 4 Mar 2026 18:30:05 +0700 Subject: [PATCH 10/21] Update src/wp-includes/media.php Co-authored-by: Weston Ruter --- src/wp-includes/media.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 1307ce3f0b115..fefbce430d4ec 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -6435,7 +6435,7 @@ function wp_get_chrome_major_version(): ?int { * * Required for enabling SharedArrayBuffer for WebAssembly-based * media processing in the editor. Uses Document-Isolation-Policy - * on supported browsers (Chrome 137+). + * on supported browsers (Chromium 137+). * * @since 7.0.0 */ From 1c612942cb63a5166c51fa3aced8ffcc526cb787 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 4 Mar 2026 18:31:03 +0700 Subject: [PATCH 11/21] Apply suggestions from code review Co-authored-by: Weston Ruter --- src/wp-includes/media.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index fefbce430d4ec..710f136ad546d 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -6393,9 +6393,9 @@ function wp_set_client_side_media_processing_flag(): void { wp_add_inline_script( 'wp-block-editor', 'window.__clientSideMediaProcessing = true', 'before' ); - $chrome_version = wp_get_chrome_major_version(); + $chromium_version = wp_get_chromium_major_version(); - if ( null !== $chrome_version && $chrome_version >= 137 ) { + if ( null !== $chromium_version && $chromium_version >= 137 ) { wp_add_inline_script( 'wp-block-editor', 'window.__documentIsolationPolicy = true', 'before' ); } @@ -6420,7 +6420,7 @@ function wp_set_client_side_media_processing_flag(): void { * * @return int|null The major Chrome version, or null if not a Chromium browser. */ -function wp_get_chrome_major_version(): ?int { +function wp_get_chromium_major_version(): ?int { if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) { return null; } @@ -6478,9 +6478,9 @@ function wp_set_up_cross_origin_isolation(): void { * @since 7.0.0 */ function wp_start_cross_origin_isolation_output_buffer(): void { - $chrome_version = wp_get_chrome_major_version(); + $chromium_version = wp_get_chromium_major_version(); - if ( null === $chrome_version || $chrome_version < 137 ) { + if ( null === $chromium_version || $chromium_version < 137 ) { return; } From 86107f135eb8001caf7cffa370919cd0c075cd80 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 5 Mar 2026 10:53:50 +0700 Subject: [PATCH 12/21] Tests: Rename wpGetChromeMajorVersion test to wpGetChromiumMajorVersion. The function was renamed to `wp_get_chromium_major_version()` to reflect that it works with any Chromium-based browser, not just Chrome. This updates the test file name and all references to match. Co-Authored-By: Claude Opus 4.6 --- ...sion.php => wpGetChromiumMajorVersion.php} | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) rename tests/phpunit/tests/media/{wpGetChromeMajorVersion.php => wpGetChromiumMajorVersion.php} (75%) diff --git a/tests/phpunit/tests/media/wpGetChromeMajorVersion.php b/tests/phpunit/tests/media/wpGetChromiumMajorVersion.php similarity index 75% rename from tests/phpunit/tests/media/wpGetChromeMajorVersion.php rename to tests/phpunit/tests/media/wpGetChromiumMajorVersion.php index ba4ee55923586..db6f9ec8ef32b 100644 --- a/tests/phpunit/tests/media/wpGetChromeMajorVersion.php +++ b/tests/phpunit/tests/media/wpGetChromiumMajorVersion.php @@ -1,12 +1,12 @@ assertNull( wp_get_chrome_major_version() ); + $this->assertNull( wp_get_chromium_major_version() ); } public function test_returns_null_for_empty_user_agent() { $_SERVER['HTTP_USER_AGENT'] = ''; - $this->assertNull( wp_get_chrome_major_version() ); + $this->assertNull( wp_get_chromium_major_version() ); } public function test_returns_null_for_firefox() { $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0'; - $this->assertNull( wp_get_chrome_major_version() ); + $this->assertNull( wp_get_chromium_major_version() ); } public function test_returns_null_for_safari() { $_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'; - $this->assertNull( wp_get_chrome_major_version() ); + $this->assertNull( wp_get_chromium_major_version() ); } public function test_returns_version_for_chrome() { $_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->assertSame( 137, wp_get_chrome_major_version() ); + $this->assertSame( 137, wp_get_chromium_major_version() ); } public function test_returns_version_for_edge() { $_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 Edg/137.0.0.0'; - $this->assertSame( 137, wp_get_chrome_major_version() ); + $this->assertSame( 137, wp_get_chromium_major_version() ); } public function test_returns_version_for_opera() { $_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 OPR/122.0.0.0'; - $this->assertSame( 136, wp_get_chrome_major_version() ); + $this->assertSame( 136, wp_get_chromium_major_version() ); } public function test_returns_version_for_older_chrome() { $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36'; - $this->assertSame( 100, wp_get_chrome_major_version() ); + $this->assertSame( 100, wp_get_chromium_major_version() ); } } From 619cb05cf9ce1664c82889c721a153f565514c12 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 5 Mar 2026 14:31:10 +0700 Subject: [PATCH 13/21] Update src/wp-includes/media.php Co-authored-by: Weston Ruter --- src/wp-includes/media.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 710f136ad546d..7788a101e5aeb 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -6424,7 +6424,7 @@ function wp_get_chromium_major_version(): ?int { if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) { return null; } - if ( preg_match( '/Chrome\/(\d+)/', $_SERVER['HTTP_USER_AGENT'], $matches ) ) { + if ( preg_match( '#Chrome/(\d+)#', $_SERVER['HTTP_USER_AGENT'], $matches ) ) { return (int) $matches[1]; } return null; From a0221528ae34f9fc82e8426dee5ce0f98ff731a0 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 5 Mar 2026 14:44:53 +0700 Subject: [PATCH 14/21] Media: Remove page builder skip logic from cross-origin isolation. Move the page builder detection check to a separate PR for further discussion on edge cases like Web Stories that use action=edit but replace the block editor entirely. Co-Authored-By: Claude Opus 4.6 --- src/wp-includes/media.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 7788a101e5aeb..e06195b975a4c 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -6454,14 +6454,6 @@ function wp_set_up_cross_origin_isolation(): void { return; } - // Skip when a third-party page builder overrides the block editor. - // DIP isolates the document into its own agent cluster, - // which blocks same-origin iframe access that these editors rely on. - // phpcs:ignore WordPress.Security.NonceVerification.Recommended - if ( isset( $_GET['action'] ) && 'edit' !== $_GET['action'] ) { - return; - } - // Cross-origin isolation is not needed if users can't upload files anyway. if ( ! current_user_can( 'upload_files' ) ) { return; From 513471fbbd2ea5a276d4860870204bc2c6671dc6 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 5 Mar 2026 14:50:52 +0700 Subject: [PATCH 15/21] Update src/wp-includes/media.php Co-authored-by: Weston Ruter --- src/wp-includes/media.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index e06195b975a4c..bfd2e58487429 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -6396,7 +6396,7 @@ function wp_set_client_side_media_processing_flag(): void { $chromium_version = wp_get_chromium_major_version(); if ( null !== $chromium_version && $chromium_version >= 137 ) { - wp_add_inline_script( 'wp-block-editor', 'window.__documentIsolationPolicy = true', 'before' ); + wp_add_inline_script( 'wp-block-editor', 'window.__documentIsolationPolicy = true;', 'before' ); } /* From bfb8a9bf3b5ecc5b04fe86e536be6efd79d40ad4 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 5 Mar 2026 15:24:17 +0700 Subject: [PATCH 16/21] Tests: Remove obsolete page builder skip tests from cross-origin isolation. Remove tests for the page builder action check that was moved to a separate PR (#11170). Co-Authored-By: Claude Opus 4.6 --- .../media/wpSetUpCrossOriginIsolation.php | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php b/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php index 4116614c2ca2d..479b6bb4dedcc 100644 --- a/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php +++ b/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php @@ -8,20 +8,11 @@ */ class Tests_Media_wpSetUpCrossOriginIsolation extends WP_UnitTestCase { - /** - * Original $_GET values. - * - * @var array - */ - private $original_get; - public function set_up() { parent::set_up(); - $this->original_get = $_GET; } public function tear_down() { - $_GET = $this->original_get; remove_all_filters( 'wp_client_side_media_processing_enabled' ); parent::tear_down(); } @@ -45,27 +36,4 @@ public function test_returns_early_when_no_screen() { $this->assertSame( $level_before, $level_after ); } - - public function test_skips_for_third_party_editor_action() { - $_GET['action'] = 'third_party_editor'; - - $level_before = ob_get_level(); - wp_set_up_cross_origin_isolation(); - $level_after = ob_get_level(); - - $this->assertSame( $level_before, $level_after, 'Should skip when action is not "edit".' ); - } - - public function test_does_not_skip_for_edit_action() { - $_GET['action'] = 'edit'; - - // Still won't start the buffer because no screen is set, - // but confirms the action check doesn't block 'edit'. - $level_before = ob_get_level(); - wp_set_up_cross_origin_isolation(); - $level_after = ob_get_level(); - - // Returns early at the screen check, not the action check. - $this->assertSame( $level_before, $level_after ); - } } From 23ee73aa995487303d8a501f1c84c40dd99c971c Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 5 Mar 2026 15:30:54 +0700 Subject: [PATCH 17/21] Tests: Merge cross-origin isolation test files into one. Combine wpSetUpCrossOriginIsolation.php and wpStartCrossOriginIsolationOutputBuffer.php into a single wpCrossOriginIsolation.php file that covers both functions. Co-Authored-By: Claude Opus 4.6 --- ...tBuffer.php => wpCrossOriginIsolation.php} | 26 ++++++++++++- .../media/wpSetUpCrossOriginIsolation.php | 39 ------------------- 2 files changed, 24 insertions(+), 41 deletions(-) rename tests/phpunit/tests/media/{wpStartCrossOriginIsolationOutputBuffer.php => wpCrossOriginIsolation.php} (78%) delete mode 100644 tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php diff --git a/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php b/tests/phpunit/tests/media/wpCrossOriginIsolation.php similarity index 78% rename from tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php rename to tests/phpunit/tests/media/wpCrossOriginIsolation.php index 2d318927c9581..b8c464b4b0d76 100644 --- a/tests/phpunit/tests/media/wpStartCrossOriginIsolationOutputBuffer.php +++ b/tests/phpunit/tests/media/wpCrossOriginIsolation.php @@ -1,12 +1,13 @@ assertSame( $level_before, $level_after ); + } + + public function test_returns_early_when_no_screen() { + // No screen is set, so it should return early. + $level_before = ob_get_level(); + wp_set_up_cross_origin_isolation(); + $level_after = ob_get_level(); + + $this->assertSame( $level_before, $level_after ); + } + /** * @runInSeparateProcess * @preserveGlobalState disabled diff --git a/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php b/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php deleted file mode 100644 index 479b6bb4dedcc..0000000000000 --- a/tests/phpunit/tests/media/wpSetUpCrossOriginIsolation.php +++ /dev/null @@ -1,39 +0,0 @@ -assertSame( $level_before, $level_after ); - } - - public function test_returns_early_when_no_screen() { - // No screen is set, so it should return early. - $level_before = ob_get_level(); - wp_set_up_cross_origin_isolation(); - $level_after = ob_get_level(); - - $this->assertSame( $level_before, $level_after ); - } -} From 42f9d6d8e9f31c6b85bc41d8d3c214765d6ea291 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 5 Mar 2026 16:48:59 +0700 Subject: [PATCH 18/21] Add @ticket 64766 annotations to tests --- .../tests/media/wpCrossOriginIsolation.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/phpunit/tests/media/wpCrossOriginIsolation.php b/tests/phpunit/tests/media/wpCrossOriginIsolation.php index b8c464b4b0d76..e3a8688678726 100644 --- a/tests/phpunit/tests/media/wpCrossOriginIsolation.php +++ b/tests/phpunit/tests/media/wpCrossOriginIsolation.php @@ -37,6 +37,9 @@ public function tear_down() { parent::tear_down(); } + /** + * @ticket 64766 + */ public function test_returns_early_when_client_side_processing_disabled() { add_filter( 'wp_client_side_media_processing_enabled', '__return_false' ); @@ -48,6 +51,9 @@ public function test_returns_early_when_client_side_processing_disabled() { $this->assertSame( $level_before, $level_after ); } + /** + * @ticket 64766 + */ public function test_returns_early_when_no_screen() { // No screen is set, so it should return early. $level_before = ob_get_level(); @@ -58,6 +64,8 @@ public function test_returns_early_when_no_screen() { } /** + * @ticket 64766 + * * @runInSeparateProcess * @preserveGlobalState disabled */ @@ -73,6 +81,9 @@ public function test_starts_output_buffer_for_chrome_137() { ob_end_clean(); } + /** + * @ticket 64766 + */ public function test_does_not_start_output_buffer_for_chrome_136() { $_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'; @@ -83,6 +94,9 @@ public function test_does_not_start_output_buffer_for_chrome_136() { $this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Chrome < 137.' ); } + /** + * @ticket 64766 + */ public function test_does_not_start_output_buffer_for_firefox() { $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0'; @@ -93,6 +107,9 @@ public function test_does_not_start_output_buffer_for_firefox() { $this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Firefox.' ); } + /** + * @ticket 64766 + */ public function test_does_not_start_output_buffer_for_safari() { $_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'; @@ -104,6 +121,8 @@ public function test_does_not_start_output_buffer_for_safari() { } /** + * @ticket 64766 + * * @runInSeparateProcess * @preserveGlobalState disabled */ From 7922385336efa3925ed6e9f17465ff2490218ed1 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 5 Mar 2026 16:50:42 +0700 Subject: [PATCH 19/21] Add comments explaining @runInSeparateProcess usage The output buffer callback calls header() which requires a separate process since PHPUnit has already started output. --- tests/phpunit/tests/media/wpCrossOriginIsolation.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/phpunit/tests/media/wpCrossOriginIsolation.php b/tests/phpunit/tests/media/wpCrossOriginIsolation.php index e3a8688678726..31f2e85975ee0 100644 --- a/tests/phpunit/tests/media/wpCrossOriginIsolation.php +++ b/tests/phpunit/tests/media/wpCrossOriginIsolation.php @@ -64,6 +64,10 @@ public function test_returns_early_when_no_screen() { } /** + * This test must run in a separate process because the output buffer + * callback sends HTTP headers via header(), which would fail in the + * main PHPUnit process where output has already started. + * * @ticket 64766 * * @runInSeparateProcess @@ -121,6 +125,10 @@ public function test_does_not_start_output_buffer_for_safari() { } /** + * This test must run in a separate process because the output buffer + * callback sends HTTP headers via header(), which would fail in the + * main PHPUnit process where output has already started. + * * @ticket 64766 * * @runInSeparateProcess From 4cf424e48a0bbbdf5da86800371efee6b073fdd1 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 5 Mar 2026 16:54:37 +0700 Subject: [PATCH 20/21] Refactor Chromium version tests to use dataProvider --- .../tests/media/wpGetChromiumMajorVersion.php | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/tests/phpunit/tests/media/wpGetChromiumMajorVersion.php b/tests/phpunit/tests/media/wpGetChromiumMajorVersion.php index db6f9ec8ef32b..10a15c98c87c4 100644 --- a/tests/phpunit/tests/media/wpGetChromiumMajorVersion.php +++ b/tests/phpunit/tests/media/wpGetChromiumMajorVersion.php @@ -29,43 +29,41 @@ public function tear_down() { parent::tear_down(); } + /** + * @ticket 64766 + */ public function test_returns_null_when_no_user_agent() { unset( $_SERVER['HTTP_USER_AGENT'] ); $this->assertNull( wp_get_chromium_major_version() ); } - public function test_returns_null_for_empty_user_agent() { - $_SERVER['HTTP_USER_AGENT'] = ''; - $this->assertNull( wp_get_chromium_major_version() ); - } - - public function test_returns_null_for_firefox() { - $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0'; - $this->assertNull( wp_get_chromium_major_version() ); - } - - public function test_returns_null_for_safari() { - $_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'; - $this->assertNull( wp_get_chromium_major_version() ); - } - - public function test_returns_version_for_chrome() { - $_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->assertSame( 137, wp_get_chromium_major_version() ); - } - - public function test_returns_version_for_edge() { - $_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 Edg/137.0.0.0'; - $this->assertSame( 137, wp_get_chromium_major_version() ); - } - - public function test_returns_version_for_opera() { - $_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 OPR/122.0.0.0'; - $this->assertSame( 136, wp_get_chromium_major_version() ); + /** + * @ticket 64766 + * + * @dataProvider data_user_agents + * + * @param string $user_agent The user agent string. + * @param int|null $expected The expected Chromium major version, or null. + */ + public function test_returns_expected_version( $user_agent, $expected ) { + $_SERVER['HTTP_USER_AGENT'] = $user_agent; + $this->assertSame( $expected, wp_get_chromium_major_version() ); } - public function test_returns_version_for_older_chrome() { - $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36'; - $this->assertSame( 100, wp_get_chromium_major_version() ); + /** + * Data provider for test_returns_expected_version. + * + * @return array[] + */ + public function data_user_agents() { + return array( + 'empty user agent' => array( '', null ), + 'Firefox' => array( 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0', null ), + 'Safari' => array( 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15', null ), + 'Chrome 137' => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36', 137 ), + 'Edge 137' => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 137 ), + 'Opera (Chrome 136)' => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 OPR/122.0.0.0', 136 ), + 'Chrome 100' => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36', 100 ), + ); } } From ef0e41c50620a1d32a52bdb0caef8eb2fe00135a Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 5 Mar 2026 16:57:32 +0700 Subject: [PATCH 21/21] Fix array double arrow alignment in data provider --- .../tests/media/wpGetChromiumMajorVersion.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/phpunit/tests/media/wpGetChromiumMajorVersion.php b/tests/phpunit/tests/media/wpGetChromiumMajorVersion.php index 10a15c98c87c4..7249d9b91b665 100644 --- a/tests/phpunit/tests/media/wpGetChromiumMajorVersion.php +++ b/tests/phpunit/tests/media/wpGetChromiumMajorVersion.php @@ -57,13 +57,13 @@ public function test_returns_expected_version( $user_agent, $expected ) { */ public function data_user_agents() { return array( - 'empty user agent' => array( '', null ), - 'Firefox' => array( 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0', null ), - 'Safari' => array( 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15', null ), - 'Chrome 137' => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36', 137 ), - 'Edge 137' => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 137 ), + 'empty user agent' => array( '', null ), + 'Firefox' => array( 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0', null ), + 'Safari' => array( 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15', null ), + 'Chrome 137' => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36', 137 ), + 'Edge 137' => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 137 ), 'Opera (Chrome 136)' => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 OPR/122.0.0.0', 136 ), - 'Chrome 100' => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36', 100 ), + 'Chrome 100' => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36', 100 ), ); } }