From fc14aa99599f2ed0aeb7d1a1deaf283fa05eb692 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 4 Dec 2025 23:44:31 -0800 Subject: [PATCH] Preserve original CSS cascade when hoisting late-printed styles --- src/wp-includes/script-loader.php | 130 +++++++++++++++++++----------- tests/phpunit/tests/template.php | 12 +-- 2 files changed, 87 insertions(+), 55 deletions(-) diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 7dccff9775731..c3e387e732a7f 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -3698,9 +3698,10 @@ static function () use ( $placeholder ) { * later hoisted to the HEAD in the template enhancement output buffer. This will run at `wp_print_footer_scripts` * before `print_footer_scripts()` is called. */ - $printed_block_styles = ''; - $printed_late_styles = ''; - $capture_late_styles = static function () use ( &$printed_block_styles, &$printed_late_styles ) { + $printed_block_styles = ''; + $printed_global_styles = ''; + $printed_late_styles = ''; + $capture_late_styles = static function () use ( &$printed_block_styles, &$printed_global_styles, &$printed_late_styles ) { // Gather the styles related to on-demand block enqueues. $all_block_style_handles = array(); foreach ( WP_Block_Type_Registry::get_instance()->get_all_registered() as $block_type ) { @@ -3708,15 +3709,6 @@ static function () use ( $placeholder ) { $all_block_style_handles[] = $style_handle; } } - $all_block_style_handles = array_merge( - $all_block_style_handles, - array( - 'global-styles', - 'block-style-variation-styles', - 'core-block-supports', - 'core-block-supports-duotone', - ) - ); /* * First print all styles related to blocks which should inserted right after the wp-block-library stylesheet @@ -3729,6 +3721,13 @@ static function () use ( $placeholder ) { $printed_block_styles = ob_get_clean(); } + // Capture the global-styles so that it can be printed separately after classic-theme-styles, to preserve the original order, + if ( wp_style_is( 'global-styles' ) ) { + ob_start(); + wp_styles()->do_items( array( 'global-styles' ) ); + $printed_global_styles = ob_get_clean(); + } + /* * Print all remaining styles not related to blocks. This contains a subset of the logic from * `print_late_styles()`, without admin-specific logic and the `print_late_styles` filter to control whether @@ -3767,7 +3766,7 @@ static function () use ( $capture_late_styles ) { // Replace placeholder with the captured late styles. add_filter( 'wp_template_enhancement_output_buffer', - static function ( $buffer ) use ( $placeholder, &$printed_block_styles, &$printed_late_styles ) { + static function ( $buffer ) use ( $placeholder, &$printed_block_styles, &$printed_global_styles, &$printed_late_styles ) { // Anonymous subclass of WP_HTML_Tag_Processor which exposes underlying bookmark spans. $processor = new class( $buffer ) extends WP_HTML_Tag_Processor { @@ -3812,53 +3811,86 @@ public function remove() { } }; - /* - * Insert block styles right after wp-block-library (if it is present), and then insert any remaining styles - * at (or else print everything there). The placeholder CSS comment will always be added to the - * wp-block-library inline style since it gets printed at `wp_head` before the blocks are rendered. - * This means that there may not actually be any block styles to hoist from the footer to insert after this - * inline style. The placeholder CSS comment needs to be added so that the inline style gets printed, but - * if the resulting inline style is empty after the placeholder is removed, then the inline style is - * removed. - */ + // Locate the insertion points in the HEAD. while ( $processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) { if ( 'STYLE' === $processor->get_tag() && 'wp-block-library-inline-css' === $processor->get_attribute( 'id' ) ) { - $css_text = $processor->get_modifiable_text(); - - /* - * A placeholder CSS comment is added to the inline style in order to force an inline STYLE tag to - * be printed. Now that we've located the inline style, the placeholder comment can be removed. If - * there is no CSS left in the STYLE tag after removing the placeholder (aside from the sourceURL - * comment, then remove the STYLE entirely.) - */ - $css_text = str_replace( $placeholder, '', $css_text ); - if ( preg_match( ':^/\*# sourceURL=\S+? \*/$:', trim( $css_text ) ) ) { - $processor->remove(); - } else { - $processor->set_modifiable_text( $css_text ); - } + $processor->set_bookmark( 'wp_block_library' ); + } elseif ( + ( + 'STYLE' === $processor->get_tag() && + 'classic-theme-styles-inline-css' === $processor->get_attribute( 'id' ) + ) || + ( + 'LINK' === $processor->get_tag() && + 'classic-theme-styles-css' === $processor->get_attribute( 'id' ) + ) + ) { + $processor->set_bookmark( 'classic_theme_styles' ); + } elseif ( 'HEAD' === $processor->get_tag() && $processor->is_tag_closer() ) { + $processor->set_bookmark( 'head_end' ); + break; + } + } - // Insert the $printed_late_styles immediately after the closing inline STYLE tag. This preserves the CSS cascade. - if ( '' !== $printed_block_styles ) { - $processor->insert_after( $printed_block_styles ); + /* + * Insert block styles right after wp-block-library (if it is present). The placeholder CSS comment will + * always be added to the wp-block-library inline style since it gets printed at `wp_head` before the blocks + * are rendered. This means that there may not actually be any block styles to hoist from the footer to + * insert after this inline style. The placeholder CSS comment needs to be added so that the inline style + * gets printed, but if the resulting inline style is empty after the placeholder is removed, then the + * inline style is removed. + */ + if ( $processor->has_bookmark( 'wp_block_library' ) ) { + $processor->seek( 'wp_block_library' ); - // Prevent printing them again at . - $printed_block_styles = ''; - } + $css_text = $processor->get_modifiable_text(); - // If there aren't any late styles, there's no need to continue to finding . - if ( '' === $printed_late_styles ) { - break; - } - } elseif ( 'HEAD' === $processor->get_tag() && $processor->is_tag_closer() ) { - $processor->insert_before( $printed_block_styles . $printed_late_styles ); - break; + /* + * A placeholder CSS comment is added to the inline style in order to force an inline STYLE tag to + * be printed. Now that we've located the inline style, the placeholder comment can be removed. If + * there is no CSS left in the STYLE tag after removing the placeholder (aside from the sourceURL + * comment, then remove the STYLE entirely.) + */ + $css_text = str_replace( $placeholder, '', $css_text ); + if ( preg_match( ':^/\*# sourceURL=\S+? \*/$:', trim( $css_text ) ) ) { + $processor->remove(); + } else { + $processor->set_modifiable_text( $css_text ); + } + + $inserted_after = $printed_block_styles; + if ( ! $processor->has_bookmark( 'classic_theme_styles' ) ) { + $inserted_after .= $printed_global_styles; + + // Prevent printing them again at . + $printed_global_styles = ''; + } + + if ( '' !== $inserted_after ) { + $processor->insert_after( $inserted_after ); + + // Prevent printing them again at . + $printed_block_styles = ''; } } + if ( $printed_global_styles && $processor->has_bookmark( 'classic_theme_styles' ) ) { + $processor->seek( 'classic_theme_styles' ); + $processor->insert_after( $printed_global_styles ); + + // Prevent printing them again at . + $printed_global_styles = ''; + } + + // Print all remaining styles. + $remaining_styles = $printed_block_styles . $printed_global_styles . $printed_late_styles; + if ( $remaining_styles && $processor->has_bookmark( 'head_end' ) ) { + $processor->seek( 'head_end' ); + $processor->insert_before( $remaining_styles ); + } return $processor->get_updated_html(); } ); diff --git a/tests/phpunit/tests/template.php b/tests/phpunit/tests/template.php index a304fff95f865..5c8409375f97f 100644 --- a/tests/phpunit/tests/template.php +++ b/tests/phpunit/tests/template.php @@ -1482,14 +1482,14 @@ public function data_wp_hoist_late_printed_styles(): array { 'wp-emoji-styles-inline-css', 'wp-block-library-css', 'wp-block-separator-css', - 'global-styles-inline-css', - 'core-block-supports-inline-css', 'classic-theme-styles-css', + 'global-styles-inline-css', 'normal-css', 'normal-inline-css', 'wp-custom-css', 'late-css', 'late-inline-css', + 'core-block-supports-inline-css', ); return array( @@ -1512,14 +1512,14 @@ public function data_wp_hoist_late_printed_styles(): array { 'wp-emoji-styles-inline-css', 'wp-block-library-inline-css', 'wp-block-separator-inline-css', - 'global-styles-inline-css', - 'core-block-supports-inline-css', 'classic-theme-styles-inline-css', + 'global-styles-inline-css', 'normal-css', 'normal-inline-css', 'wp-custom-css', 'late-css', 'late-inline-css', + 'core-block-supports-inline-css', ), 'BODY' => array(), ), @@ -1652,14 +1652,14 @@ function (): void { 'wp-block-library-css', 'wp-block-library-inline-css', // This contains the "OVERRIDDEN" text. 'wp-block-separator-css', - 'global-styles-inline-css', - 'core-block-supports-inline-css', 'classic-theme-styles-css', + 'global-styles-inline-css', 'normal-css', 'normal-inline-css', 'wp-custom-css', 'late-css', 'late-inline-css', + 'core-block-supports-inline-css', ), 'BODY' => array(), ),