From 52839e51944e44b2aab36f9de71b14c2f7bd9681 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 1 Mar 2026 13:33:25 +0700 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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