diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 7dccff9775731..702211c9e4811 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -3678,6 +3678,23 @@ function wp_hoist_late_printed_styles() { return; } + // Capture the styles enqueued at the enqueue_block_assets action, so that non-core block styles and global styles can be inserted after at hoisting. + $style_handles_at_enqueue_block_assets = array(); + add_action( + 'enqueue_block_assets', + static function () use ( &$style_handles_at_enqueue_block_assets ) { + $style_handles_at_enqueue_block_assets = wp_styles()->queue; + }, + PHP_INT_MIN + ); + add_action( + 'enqueue_block_assets', + static function () use ( &$style_handles_at_enqueue_block_assets ) { + $style_handles_at_enqueue_block_assets = array_values( array_diff( wp_styles()->queue, $style_handles_at_enqueue_block_assets ) ); + }, + PHP_INT_MAX + ); + /* * Add a placeholder comment into the inline styles for wp-block-library, after which where the late block styles * can be hoisted from the footer to be printed in the header by means of a filter below on the template enhancement @@ -3698,35 +3715,51 @@ 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_core_block_styles = ''; + $printed_other_block_styles = ''; + $printed_global_styles = ''; + $printed_late_styles = ''; + + $capture_late_styles = static function () use ( &$printed_core_block_styles, &$printed_other_block_styles, &$printed_global_styles, &$printed_late_styles ) { // Gather the styles related to on-demand block enqueues. - $all_block_style_handles = array(); + $all_core_block_style_handles = array(); + $all_other_block_style_handles = array(); foreach ( WP_Block_Type_Registry::get_instance()->get_all_registered() as $block_type ) { - foreach ( $block_type->style_handles as $style_handle ) { - $all_block_style_handles[] = $style_handle; + if ( str_starts_with( $block_type->name, 'core/' ) ) { + foreach ( $block_type->style_handles as $style_handle ) { + $all_core_block_style_handles[] = $style_handle; + } + } else { + foreach ( $block_type->style_handles as $style_handle ) { + $all_other_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 * to preserve the CSS cascade. The logic in this `if` statement is derived from `wp_print_styles()`. */ - $enqueued_block_styles = array_values( array_intersect( $all_block_style_handles, wp_styles()->queue ) ); - if ( count( $enqueued_block_styles ) > 0 ) { + $enqueued_core_block_styles = array_values( array_intersect( $all_core_block_style_handles, wp_styles()->queue ) ); + if ( count( $enqueued_core_block_styles ) > 0 ) { ob_start(); - wp_styles()->do_items( $enqueued_block_styles ); - $printed_block_styles = ob_get_clean(); + wp_styles()->do_items( $enqueued_core_block_styles ); + $printed_core_block_styles = ob_get_clean(); + } + + // Non-core block styles get printed after the classic-theme-styles stylesheet. + $enqueued_other_block_styles = array_values( array_intersect( $all_other_block_style_handles, wp_styles()->queue ) ); + if ( count( $enqueued_other_block_styles ) > 0 ) { + ob_start(); + wp_styles()->do_items( $enqueued_other_block_styles ); + $printed_other_block_styles = ob_get_clean(); + } + + // Capture the global-styles so that it can be printed separately after classic-theme-styles and other styles enqueued at enqueue_block_assets, + if ( wp_style_is( 'global-styles' ) ) { + ob_start(); + wp_styles()->do_items( array( 'global-styles' ) ); + $printed_global_styles = ob_get_clean(); } /* @@ -3767,7 +3800,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, &$style_handles_at_enqueue_block_assets, &$printed_core_block_styles, &$printed_other_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 +3845,112 @@ 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 ( 'HEAD' === $processor->get_tag() && $processor->is_tag_closer() ) { + $processor->set_bookmark( 'head_end' ); + break; + } elseif ( ( 'STYLE' === $processor->get_tag() || 'LINK' === $processor->get_tag() ) && $processor->get_attribute( 'id' ) ) { + $id = $processor->get_attribute( 'id' ); + $handle = null; + if ( 'STYLE' === $processor->get_tag() ) { + if ( preg_match( '/^(.+)-inline-css$/', $id, $matches ) ) { + $handle = $matches[1]; + } + } elseif ( preg_match( '/^(.+)-css$/', $id, $matches ) ) { + $handle = $matches[1]; } - // 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 ); + if ( 'classic-theme-styles' === $handle ) { + $processor->set_bookmark( 'classic_theme_styles' ); + } - // Prevent printing them again at . - $printed_block_styles = ''; + if ( $handle && in_array( $handle, $style_handles_at_enqueue_block_assets, true ) ) { + if ( ! $processor->has_bookmark( 'first_style_at_enqueue_block_assets' ) ) { + $processor->set_bookmark( 'first_style_at_enqueue_block_assets' ); + } + $processor->set_bookmark( 'last_style_at_enqueue_block_assets' ); } + } + } + + /* + * 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' ); + + $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 ); + } + + $inserted_after = $printed_core_block_styles; + $printed_core_block_styles = ''; + + // If the classic-theme-styles is absent, then the third-party block styles cannot be inserted after it, so they get inserted here. + if ( ! $processor->has_bookmark( 'classic_theme_styles' ) ) { + $inserted_after .= "\n" . $printed_other_block_styles; + $printed_other_block_styles = ''; - // If there aren't any late styles, there's no need to continue to finding . - if ( '' === $printed_late_styles ) { - break; + // If there aren't any other styles printed at enqueue_block_assets either, then the global styles needs to also be printed here. + if ( ! $processor->has_bookmark( 'last_style_at_enqueue_block_assets' ) ) { + $inserted_after .= "\n" . $printed_global_styles; + $printed_global_styles = ''; } - } elseif ( 'HEAD' === $processor->get_tag() && $processor->is_tag_closer() ) { - $processor->insert_before( $printed_block_styles . $printed_late_styles ); - break; + } + + if ( '' !== $inserted_after ) { + $processor->insert_after( "\n" . $inserted_after ); + } + } + + // Insert global-styles after the styles enqueued at enqueue_block_assets. + if ( '' !== $printed_global_styles && $processor->has_bookmark( 'last_style_at_enqueue_block_assets' ) ) { + $processor->seek( 'last_style_at_enqueue_block_assets' ); + + $processor->insert_after( "\n" . $printed_global_styles ); + $printed_global_styles = ''; + + if ( ! $processor->has_bookmark( 'classic_theme_styles' ) ) { + $processor->insert_after( "\n" . $printed_other_block_styles ); + $printed_other_block_styles = ''; } } + // Insert third-party block styles right after the classic-theme-styles. + if ( '' !== $printed_other_block_styles && $processor->has_bookmark( 'classic_theme_styles' ) ) { + $processor->seek( 'classic_theme_styles' ); + $processor->insert_after( "\n" . $printed_other_block_styles ); + $printed_other_block_styles = ''; + } + + // Print all remaining styles. + $remaining_styles = $printed_core_block_styles . $printed_other_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 . "\n" ); + } return $processor->get_updated_html(); } ); diff --git a/tests/phpunit/tests/template.php b/tests/phpunit/tests/template.php index a304fff95f865..a7b8783762045 100644 --- a/tests/phpunit/tests/template.php +++ b/tests/phpunit/tests/template.php @@ -146,6 +146,11 @@ public function tear_down() { unregister_taxonomy( 'taxo' ); $this->set_permalink_structure( '' ); + $registry = WP_Block_Type_Registry::get_instance(); + if ( $registry->is_registered( 'third-party/test' ) ) { + $registry->unregister( 'third-party/test' ); + } + parent::tear_down(); } @@ -1475,21 +1480,49 @@ public function test_wp_load_classic_theme_block_styles_on_demand( string $theme * @return array */ public function data_wp_hoist_late_printed_styles(): array { - $common_expected_head_styles = array( + $early_common_styles = array( 'wp-img-auto-sizes-contain-inline-css', 'early-css', 'early-inline-css', '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', + ); + + $common_late_in_head = array( + // Styles enqueued at wp_enqueue_scripts (priority 10). 'normal-css', 'normal-inline-css', + + // Styles printed at wp_head priority 10. 'wp-custom-css', + ); + + $common_late_in_body = array( 'late-css', 'late-inline-css', + 'core-block-supports-inline-css', + ); + + $common_expected_head_styles = array_merge( + $early_common_styles, + array( + // Core block styles enqueued by wp_common_block_scripts_and_styles() at which runs at wp_enqueue_scripts priority 10, added first. + 'wp-block-library-css', // Inline printed. + 'wp-block-separator-css', // Hoisted. + + // The wp_common_block_scripts_and_styles() function also fires enqueue_block_assets, at which wp_enqueue_classic_theme_styles() runs. + 'classic-theme-styles-css', // Printed at enqueue_block_assets. + + // Third-party block styles. + 'third-party-test-block-css', // Hoisted. + + // Other styles enqueued at enqueue_block_assets, which is fired by wp_common_block_scripts_and_styles(). + 'custom-block-styles-css', // Printed at enqueue_block_assets. + + // Hoisted. Enqueued by wp_enqueue_global_styles() which runs at wp_enqueue_scripts priority 10 and wp_footer priority 1. + 'global-styles-inline-css', + ), + $common_late_in_head, + $common_late_in_body ); return array( @@ -1505,21 +1538,99 @@ public function data_wp_hoist_late_printed_styles(): array { 'set_up' => null, 'inline_size_limit' => PHP_INT_MAX, 'expected_styles' => array( - 'HEAD' => array( - 'wp-img-auto-sizes-contain-inline-css', - 'early-css', - 'early-inline-css', - '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', - 'normal-css', - 'normal-inline-css', - 'wp-custom-css', - 'late-css', - 'late-inline-css', + 'HEAD' => array_merge( + $early_common_styles, + array( + 'wp-block-library-inline-css', + 'wp-block-separator-inline-css', + 'classic-theme-styles-inline-css', + 'third-party-test-block-css', + 'custom-block-styles-css', + 'global-styles-inline-css', + ), + $common_late_in_head, + $common_late_in_body + ), + 'BODY' => array(), + ), + ), + 'classic_theme_styles_omitted' => array( + 'set_up' => static function () { + // Note that wp_enqueue_scripts is used instead of enqueue_block_assets because it runs again at the former action. + add_action( + 'wp_enqueue_scripts', + static function () { + wp_dequeue_style( 'classic-theme-styles' ); + }, + 100 + ); + }, + 'inline_size_limit' => PHP_INT_MAX, + 'expected_styles' => array( + 'HEAD' => array_merge( + $early_common_styles, + array( + 'wp-block-library-inline-css', + 'wp-block-separator-inline-css', + 'third-party-test-block-css', + 'custom-block-styles-css', + 'global-styles-inline-css', + ), + $common_late_in_head, + $common_late_in_body + ), + 'BODY' => array(), + ), + ), + 'no_styles_at_enqueued_block_assets' => array( + 'set_up' => static function () { + add_action( + 'wp_enqueue_scripts', + static function () { + wp_dequeue_style( 'classic-theme-styles' ); + wp_dequeue_style( 'custom-block-styles' ); + }, + 100 + ); + }, + 'inline_size_limit' => PHP_INT_MAX, + 'expected_styles' => array( + 'HEAD' => array_merge( + $early_common_styles, + array( + 'wp-block-library-inline-css', + 'wp-block-separator-inline-css', + 'third-party-test-block-css', + 'global-styles-inline-css', + ), + $common_late_in_head, + $common_late_in_body + ), + 'BODY' => array(), + ), + ), + 'no_global_styles' => array( + 'set_up' => static function () { + add_filter( + 'print_styles_array', + static function ( $handles ) { + return array_values( array_diff( $handles, array( 'global-styles' ) ) ); + } + ); + }, + 'inline_size_limit' => PHP_INT_MAX, + 'expected_styles' => array( + 'HEAD' => array_merge( + $early_common_styles, + array( + 'wp-block-library-inline-css', + 'wp-block-separator-inline-css', + 'classic-theme-styles-inline-css', + 'third-party-test-block-css', + 'custom-block-styles-css', + ), + $common_late_in_head, + $common_late_in_body ), 'BODY' => array(), ), @@ -1551,23 +1662,18 @@ static function () { }, 'inline_size_limit' => 0, 'expected_styles' => array( - 'HEAD' => array( - 'wp-img-auto-sizes-contain-inline-css', - 'early-css', - 'early-inline-css', - 'wp-emoji-styles-inline-css', - 'wp-block-library-css', - 'classic-theme-styles-css', - 'global-styles-inline-css', - 'normal-css', - 'normal-inline-css', - 'wp-custom-css', - ), - 'BODY' => array( - 'late-css', - 'late-inline-css', - 'core-block-supports-inline-css', + 'HEAD' => array_merge( + $early_common_styles, + array( + 'wp-block-library-css', + 'classic-theme-styles-css', + 'third-party-test-block-css', + 'custom-block-styles-css', + 'global-styles-inline-css', + ), + $common_late_in_head ), + 'BODY' => $common_late_in_body, ), ), '_wp_footer_scripts_removed' => array( @@ -1614,22 +1720,17 @@ function (): void { }, 'inline_size_limit' => 0, 'expected_styles' => array( - 'HEAD' => array( - 'wp-img-auto-sizes-contain-inline-css', - 'early-css', - 'early-inline-css', - 'wp-emoji-styles-inline-css', - 'classic-theme-styles-css', - 'global-styles-inline-css', - 'normal-css', - 'normal-inline-css', - 'wp-custom-css', - ), - 'BODY' => array( - 'late-css', - 'late-inline-css', - 'core-block-supports-inline-css', + 'HEAD' => array_merge( + $early_common_styles, + array( + 'classic-theme-styles-css', + 'third-party-test-block-css', + 'custom-block-styles-css', + 'global-styles-inline-css', + ), + $common_late_in_head ), + 'BODY' => $common_late_in_body, ), ), 'override_block_library_inline_style_late' => array( @@ -1644,22 +1745,19 @@ function (): void { }, 'inline_size_limit' => 0, 'expected_styles' => array( - 'HEAD' => array( - 'wp-img-auto-sizes-contain-inline-css', - 'early-css', - 'early-inline-css', - 'wp-emoji-styles-inline-css', - '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', - 'normal-css', - 'normal-inline-css', - 'wp-custom-css', - 'late-css', - 'late-inline-css', + 'HEAD' => array_merge( + $early_common_styles, + array( + 'wp-block-library-css', + 'wp-block-library-inline-css', // This contains the "OVERRIDDEN" text. + 'wp-block-separator-css', + 'classic-theme-styles-css', + 'third-party-test-block-css', + 'custom-block-styles-css', + 'global-styles-inline-css', + ), + $common_late_in_head, + $common_late_in_body ), 'BODY' => array(), ), @@ -1696,6 +1794,25 @@ static function () { } ); + wp_register_style( 'third-party-test-block', 'https://example.com/third-party-test-block.css', array(), null ); + register_block_type( + 'third-party/test', + array( + 'style_handles' => array( 'third-party-test-block' ), + ) + ); + + /* + * This is very old guidance about how to add enqueue styles for blocks. Certain themes still enqueue block + * styles using this action. + */ + add_action( + 'enqueue_block_assets', + static function () { + wp_enqueue_style( 'custom-block-styles', 'https://example.com/custom-block-styles.css', array(), null ); + } + ); + if ( $set_up ) { $set_up(); } @@ -1746,7 +1863,8 @@ static function () { // Simulate the_content(). $content = apply_filters( 'the_content', - '
' + '
' . + '
This is only a test!
' ); // Simulate footer scripts.