diff --git a/src/wp-includes/plugin.php b/src/wp-includes/plugin.php index 0ca495b6f76d4..c1d68a81d713d 100644 --- a/src/wp-includes/plugin.php +++ b/src/wp-includes/plugin.php @@ -271,17 +271,23 @@ function apply_filters_ref_array( $hook_name, $args ) { * * @global WP_Hook[] $wp_filter Stores all of the filters and actions. * - * @param string $hook_name The name of the filter hook. - * @param callable|string|array|false $callback Optional. The callback to check for. - * This function can be called unconditionally to speculatively check - * a callback that may or may not exist. Default false. - * @param int|false $priority Optional. The specific priority at which to check for the callback. - * Default false. + * @param string $hook_name The name of the filter hook. + * @param callable|false $callback Optional. The callback to check for. + * This function can be called unconditionally to speculatively check + * a callback that may or may not exist. Default false. + * @param int|false $priority Optional. The specific priority at which to check for the callback. + * Default false. * @return bool|int If `$callback` is omitted, returns boolean for whether the hook has * anything registered. When checking a specific function, the priority * of that hook is returned, or false if the function is not attached. * If `$callback` and `$priority` are both provided, a boolean is returned * for whether the specific function is registered at that priority. + * + * @phpstan-return ( + * $callback is false ? bool : ( + * $priority is false ? int|false : bool + * ) + * ) */ function has_filter( $hook_name, $callback = false, $priority = false ) { global $wp_filter; @@ -583,17 +589,23 @@ function do_action_ref_array( $hook_name, $args ) { * * @see has_filter() This function is an alias of has_filter(). * - * @param string $hook_name The name of the action hook. - * @param callable|string|array|false $callback Optional. The callback to check for. - * This function can be called unconditionally to speculatively check - * a callback that may or may not exist. Default false. - * @param int|false $priority Optional. The specific priority at which to check for the callback. - * Default false. + * @param string $hook_name The name of the action hook. + * @param callable|false $callback Optional. The callback to check for. + * This function can be called unconditionally to speculatively check + * a callback that may or may not exist. Default false. + * @param int|false $priority Optional. The specific priority at which to check for the callback. + * Default false. * @return bool|int If `$callback` is omitted, returns boolean for whether the hook has * anything registered. When checking a specific function, the priority * of that hook is returned, or false if the function is not attached. * If `$callback` and `$priority` are both provided, a boolean is returned * for whether the specific function is registered at that priority. + * + * @phpstan-return ( + * $callback is false ? bool : ( + * $priority is false ? int|false : bool + * ) + * ) */ function has_action( $hook_name, $callback = false, $priority = false ) { return has_filter( $hook_name, $callback, $priority ); diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index f9ea36720baea..9ed8cde7e7321 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -2551,21 +2551,42 @@ function wp_enqueue_global_styles() { $is_block_theme = wp_is_block_theme(); $is_classic_theme = ! $is_block_theme; - /* - * Global styles should be printed in the head for block themes, or for classic themes when loading assets on - * demand is disabled, which is the default. - * The footer should only be used for classic themes when loading assets on demand is enabled. + /** + * Global styles should be printed in the HEAD for block themes, or for classic themes when loading assets on + * demand is disabled (which is no longer the default). * - * See https://core.trac.wordpress.org/ticket/53494 and https://core.trac.wordpress.org/ticket/61965. + * @link https://core.trac.wordpress.org/ticket/53494 + * @link https://core.trac.wordpress.org/ticket/61965 */ if ( - ( $is_block_theme && doing_action( 'wp_footer' ) ) || - ( $is_classic_theme && doing_action( 'wp_footer' ) && ! $assets_on_demand ) || - ( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) && $assets_on_demand ) + doing_action( 'wp_footer' ) && + ( + $is_block_theme || + ( $is_classic_theme && ! $assets_on_demand ) + ) ) { return; } + /** + * The footer should only be used for classic themes when loading assets on demand is enabled. This is now the + * default with the introduction of hoisting late-printed styles (via {@see wp_load_classic_theme_block_styles_on_demand()}). + * So even though the main global styles are not printed here in the HEAD for classic themes with on-demand asset + * loading, a placeholder for the global styles is still enqueued. Then when {@see wp_hoist_late_printed_styles()} + * processes the output buffer, it can locate the placeholder and inject the global styles from the footer into the + * HEAD. + * + * @link https://core.trac.wordpress.org/ticket/64099 + */ + if ( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) && $assets_on_demand ) { + if ( has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) { + wp_register_style( 'wp-global-styles-placeholder', false ); + wp_add_inline_style( 'wp-global-styles-placeholder', ':root { --wp-internal-comment: "Placeholder for wp_hoist_late_printed_styles() to replace with the global-styles printed at wp_footer." }' ); + wp_enqueue_style( 'wp-global-styles-placeholder' ); + } + return; + } + /* * If loading the CSS for each block separately, then load the theme.json CSS conditionally. * This removes the CSS from the global-styles stylesheet and adds it to the inline CSS for each block. @@ -2741,6 +2762,16 @@ function wp_should_load_block_assets_on_demand() { */ function wp_enqueue_registered_block_scripts_and_styles() { if ( wp_should_load_block_assets_on_demand() ) { + /** + * Add placeholder for where block styles would historically get enqueued in a classic theme when block assets + * are not loaded on demand. This happens right after {@see wp_common_block_scripts_and_styles()} is called + * at which time wp-block-library is enqueued. + */ + if ( ! wp_is_block_theme() && has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) { + wp_register_style( 'wp-block-styles-placeholder', false ); + wp_add_inline_style( 'wp-block-styles-placeholder', ':root { --wp-internal-comment: "Placeholder for wp_hoist_late_printed_styles() to replace with the block styles printed at wp_footer." }' ); + wp_enqueue_style( 'wp-block-styles-placeholder' ); + } return; } @@ -3700,7 +3731,7 @@ function wp_load_classic_theme_block_styles_on_demand() { * @see wp_load_classic_theme_block_styles_on_demand() * @see _wp_footer_scripts() */ -function wp_hoist_late_printed_styles() { +function wp_hoist_late_printed_styles(): void { // Skip the embed template on-demand styles aren't relevant, and there is no wp_head action. if ( is_embed() ) { return; @@ -3726,16 +3757,21 @@ static function () use ( &$style_handles_at_enqueue_block_assets ) { /* * Add a placeholder comment into the inline styles for wp-block-library, after which 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 - * output buffer. The `wp_print_styles` action is used to ensure that if the inline style gets replaced at - * `enqueue_block_assets` or `wp_enqueue_scripts` that the placeholder will be sure to be present. + * output buffer. + * + * Note that wp_maybe_inline_styles() prepends the inlined style to the extra 'after' array, which happens after + * this code runs. This ensures that the placeholder appears right after any inlined wp-block-library styles, + * which would be common.css. */ $placeholder = sprintf( '/*%s*/', uniqid( 'wp_block_styles_on_demand_placeholder:' ) ); - add_action( - 'wp_print_styles', - static function () use ( $placeholder ) { + $dependency = wp_styles()->query( 'wp-block-library', 'registered' ); + if ( $dependency ) { + if ( ! isset( $dependency->extra['after'] ) ) { wp_add_inline_style( 'wp-block-library', $placeholder ); + } else { + array_unshift( $dependency->extra['after'], $placeholder ); } - ); + } /* * Create a substitute for `print_late_styles()` which is aware of block styles. This substitute does not print @@ -3765,29 +3801,29 @@ static function () use ( $placeholder ) { } /* - * First print all styles related to blocks which should be inserted right after the wp-block-library stylesheet + * First print all styles related to core blocks which should be 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_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_core_block_styles ); - $printed_core_block_styles = ob_get_clean(); + $printed_core_block_styles = (string) ob_get_clean(); } - // Non-core block styles get printed after the classic-theme-styles stylesheet. + // Capture non-core block styles so they can get printed at the point where wp_enqueue_registered_block_scripts_and_styles() runs. $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(); + $printed_other_block_styles = (string) 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. + // Capture the global-styles so that it can be printed at the point where wp_enqueue_global_styles() runs. if ( wp_style_is( 'global-styles' ) ) { ob_start(); wp_styles()->do_items( array( 'global-styles' ) ); - $printed_global_styles = ob_get_clean(); + $printed_global_styles = (string) ob_get_clean(); } /* @@ -3797,7 +3833,7 @@ static function () use ( $placeholder ) { */ ob_start(); wp_styles()->do_footer_items(); - $printed_late_styles = ob_get_clean(); + $printed_late_styles = (string) ob_get_clean(); }; /* @@ -3848,7 +3884,7 @@ private function get_span(): WP_HTML_Span { * * @param string $text Text to insert. */ - public function insert_before( string $text ) { + public function insert_before( string $text ): void { $this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->get_span()->start, 0, $text ); } @@ -3857,7 +3893,7 @@ public function insert_before( string $text ) { * * @param string $text Text to insert. */ - public function insert_after( string $text ) { + public function insert_after( string $text ): void { $span = $this->get_span(); $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start + $span->length, 0, $text ); @@ -3866,25 +3902,49 @@ public function insert_after( string $text ) { /** * Removes the current token. */ - public function remove() { + public function remove(): void { $span = $this->get_span(); $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, '' ); } + + /** + * Replaces the current token. + * + * @param string $text Text to replace with. + */ + public function replace( string $text ): void { + $span = $this->get_span(); + + $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, $text ); + } }; // Locate the insertion points in the HEAD. while ( $processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) { if ( + 'STYLE' === $processor->get_tag() && + 'wp-global-styles-placeholder-inline-css' === $processor->get_attribute( 'id' ) + ) { + /** This is added in {@see wp_enqueue_global_styles()} */ + $processor->set_bookmark( 'wp_global_styles_placeholder' ); + } elseif ( + 'STYLE' === $processor->get_tag() && + 'wp-block-styles-placeholder-inline-css' === $processor->get_attribute( 'id' ) + ) { + /** This is added in {@see wp_enqueue_registered_block_scripts_and_styles()} */ + $processor->set_bookmark( 'wp_block_styles_placeholder' ); + } elseif ( 'STYLE' === $processor->get_tag() && 'wp-block-library-inline-css' === $processor->get_attribute( 'id' ) ) { + /** This is added here in {@see wp_hoist_late_printed_styles()} */ $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' ); + $id = (string) $processor->get_attribute( 'id' ); $handle = null; if ( 'STYLE' === $processor->get_tag() ) { if ( preg_match( '/^(.+)-inline-css$/', $id, $matches ) ) { @@ -3894,10 +3954,6 @@ public function remove() { $handle = $matches[1]; } - if ( 'classic-theme-styles' === $handle ) { - $processor->set_bookmark( 'classic_theme_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' ); @@ -3907,6 +3963,16 @@ public function remove() { } } + /** + * Replace the placeholder for global styles enqueued during {@see wp_enqueue_global_styles()}. This is done + * even if $printed_global_styles is empty. + */ + if ( $processor->has_bookmark( 'wp_global_styles_placeholder' ) ) { + $processor->seek( 'wp_global_styles_placeholder' ); + $processor->replace( $printed_global_styles ); + $printed_global_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 @@ -3921,13 +3987,34 @@ public function remove() { $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. + * Split the block library inline style by the placeholder to identify the original inlined CSS, which + * likely would be common.css, followed by any inline styles which had been added by the theme or + * plugins via `wp_add_inline_style( 'wp-block-library', '...' )`. The separate block styles loaded on + * demand will get inserted after the inlined common.css and before the extra inline styles added by the + * user. + */ + $css_text_around_placeholder = explode( $placeholder, $css_text, 2 ); + $extra_inline_styles = ''; + if ( count( $css_text_around_placeholder ) === 2 ) { + $css_text = $css_text_around_placeholder[0]; + if ( '' !== trim( $css_text ) ) { + $inlined_src = wp_styles()->get_data( 'wp-block-library', 'inlined_src' ); + if ( $inlined_src ) { + $css_text .= sprintf( + "\n/*# sourceURL=%s */\n", + esc_url_raw( $inlined_src ) + ); + } + } + $extra_inline_styles = $css_text_around_placeholder[1]; + } + + /* + * The placeholder CSS comment was added to the inline style in order to force an inline STYLE tag to + * be printed. Now that the inline style has been located and the placeholder comment has been removed, if + * there is no CSS left in the STYLE tag after removal, then remove the STYLE tag entirely. */ - $css_text = str_replace( $placeholder, '', $css_text ); - if ( preg_match( ':^/\*# sourceURL=\S+? \*/$:', trim( $css_text ) ) ) { + if ( '' === trim( $css_text ) ) { $processor->remove(); } else { $processor->set_modifiable_text( $css_text ); @@ -3936,20 +4023,18 @@ public function remove() { $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' ) ) { - if ( '' !== $printed_other_block_styles ) { - $inserted_after .= $printed_other_block_styles; - } - $printed_other_block_styles = ''; - - // If there aren't any other styles printed at enqueue_block_assets either, then the global styles need to also be printed here. - if ( ! $processor->has_bookmark( 'last_style_at_enqueue_block_assets' ) ) { - if ( '' !== $printed_global_styles ) { - $inserted_after .= $printed_global_styles; - } - $printed_global_styles = ''; - } + /* + * Add a new inline style for any user styles added via wp_add_inline_style( 'wp-block-library', '...' ). + * This must be added here after $printed_core_block_styles to preserve the original CSS cascade when + * the combined block library stylesheet was used. The pattern here is checking to see if it is not just + * a sourceURL comment after the placeholder above is removed. + */ + if ( ! preg_match( ':^\s*(/\*# sourceURL=\S+? \*/\s*)?$:s', $extra_inline_styles ) ) { + $style_processor = new WP_HTML_Tag_Processor( '' ); + $style_processor->next_tag(); + $style_processor->set_attribute( 'id', 'wp-block-library-inline-css-extra' ); + $style_processor->set_modifiable_text( $extra_inline_styles ); + $inserted_after .= "{$style_processor->get_updated_html()}\n"; } if ( '' !== $inserted_after ) { @@ -3957,23 +4042,14 @@ public function remove() { } } - // 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' ) && '' !== $printed_other_block_styles ) { - $processor->insert_after( "\n" . $printed_other_block_styles ); - $printed_other_block_styles = ''; + // Insert block styles at the point where wp_enqueue_registered_block_scripts_and_styles() normally enqueues styles. + if ( $processor->has_bookmark( 'wp_block_styles_placeholder' ) ) { + $processor->seek( 'wp_block_styles_placeholder' ); + if ( '' !== $printed_other_block_styles ) { + $processor->replace( "\n" . $printed_other_block_styles ); + } else { + $processor->remove(); } - } - - // 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 = ''; } diff --git a/tests/phpunit/tests/template.php b/tests/phpunit/tests/template.php index e899ea2f06908..8ab3f0547e301 100644 --- a/tests/phpunit/tests/template.php +++ b/tests/phpunit/tests/template.php @@ -151,6 +151,7 @@ public function tear_down() { $registry->unregister( 'third-party/test' ); } + unset( $GLOBALS['_wp_tests_development_mode'] ); parent::tear_down(); } @@ -1477,9 +1478,17 @@ public function test_wp_load_classic_theme_block_styles_on_demand( string $theme /** * Data provider. * - * @return array + * @return array */ public function data_wp_hoist_late_printed_styles(): array { + $blocks_content = '
This is only a test!
'; + $early_common_styles = array( 'wp-img-auto-sizes-contain-inline-css', 'early-css', @@ -1487,12 +1496,14 @@ public function data_wp_hoist_late_printed_styles(): array { 'wp-emoji-styles-inline-css', ); - $common_late_in_head = array( - // Styles enqueued at wp_enqueue_scripts (priority 10). + // Styles enqueued at wp_enqueue_scripts (priority 10). + $common_at_wp_enqueue_scripts = array( 'normal-css', 'normal-inline-css', + ); - // Styles printed at wp_head priority 10. + $common_late_in_head = array( + // Styles printed at wp_head priority 101. 'wp-custom-css', ); @@ -1521,6 +1532,7 @@ public function data_wp_hoist_late_printed_styles(): array { // 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_at_wp_enqueue_scripts, $common_late_in_head, $common_late_in_body ); @@ -1528,14 +1540,17 @@ public function data_wp_hoist_late_printed_styles(): array { return array( 'standard_classic_theme_config_with_min_styles_inlined' => array( 'set_up' => null, + 'content' => $blocks_content, 'inline_size_limit' => 0, 'expected_styles' => array( 'HEAD' => $common_expected_head_styles, 'BODY' => array(), ), ), + 'standard_classic_theme_config_with_max_styles_inlined' => array( 'set_up' => null, + 'content' => $blocks_content, 'inline_size_limit' => PHP_INT_MAX, 'expected_styles' => array( 'HEAD' => array_merge( @@ -1548,12 +1563,14 @@ public function data_wp_hoist_late_printed_styles(): array { 'custom-block-styles-css', 'global-styles-inline-css', ), + $common_at_wp_enqueue_scripts, $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. @@ -1565,6 +1582,7 @@ static function () { 100 ); }, + 'content' => $blocks_content, 'inline_size_limit' => PHP_INT_MAX, 'expected_styles' => array( 'HEAD' => array_merge( @@ -1576,12 +1594,14 @@ static function () { 'custom-block-styles-css', 'global-styles-inline-css', ), + $common_at_wp_enqueue_scripts, $common_late_in_head, $common_late_in_body ), 'BODY' => array(), ), ), + 'no_styles_at_enqueued_block_assets' => array( 'set_up' => static function () { add_action( @@ -1593,6 +1613,7 @@ static function () { 100 ); }, + 'content' => $blocks_content, 'inline_size_limit' => PHP_INT_MAX, 'expected_styles' => array( 'HEAD' => array_merge( @@ -1603,21 +1624,23 @@ static function () { 'third-party-test-block-css', 'global-styles-inline-css', ), + $common_at_wp_enqueue_scripts, $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' ) ) ); - } - ); + $dequeue = static function () { + wp_dequeue_style( 'global-styles' ); + }; + add_action( 'wp_enqueue_scripts', $dequeue, 1000 ); + add_action( 'wp_footer', $dequeue, 2 ); }, + 'content' => $blocks_content, 'inline_size_limit' => PHP_INT_MAX, 'expected_styles' => array( 'HEAD' => array_merge( @@ -1629,37 +1652,121 @@ static function ( $handles ) { 'third-party-test-block-css', 'custom-block-styles-css', ), + $common_at_wp_enqueue_scripts, $common_late_in_head, $common_late_in_body ), 'BODY' => array(), ), ), - 'standard_classic_theme_config_extra_block_library_inline_style' => array( + + 'standard_classic_theme_config_extra_block_library_inline_style_none_inlined' => array( 'set_up' => static function () { add_action( 'enqueue_block_assets', static function () { - wp_add_inline_style( 'wp-block-library', '/* Extra CSS which prevents empty inline style containing placeholder from being removed. */' ); + // Extra CSS which prevents empty inline style containing placeholder from being removed. + wp_add_inline_style( 'wp-block-library', '.wp-block-separator{ outline:solid 1px lime; }' ); } ); }, + 'content' => $blocks_content, 'inline_size_limit' => 0, 'expected_styles' => array( - 'HEAD' => ( function ( $expected_styles ) { - // Insert 'wp-block-library-inline-css' right after 'wp-block-library-css'. - $i = array_search( 'wp-block-library-css', $expected_styles, true ); - $this->assertIsInt( $i, 'Expected wp-block-library-css to be among the styles.' ); - array_splice( $expected_styles, $i + 1, 0, 'wp-block-library-inline-css' ); - return $expected_styles; - } )( $common_expected_head_styles ), + 'HEAD' => array_merge( + $early_common_styles, + array( + 'wp-block-library-css', + 'wp-block-separator-css', + 'wp-block-library-inline-css-extra', + 'classic-theme-styles-css', + 'third-party-test-block-css', + 'custom-block-styles-css', + 'global-styles-inline-css', + ), + $common_at_wp_enqueue_scripts, + $common_late_in_head, + $common_late_in_body + ), 'BODY' => array(), ), + 'assert' => function ( string $buffer, string $filtered_buffer ) { + $block_separator_core_style_span = null; + $block_separator_custom_style_span = null; + $processor = new class( $filtered_buffer ) extends WP_HTML_Tag_Processor { + public function get_span(): WP_HTML_Span { + $this->set_bookmark( 'here' ); + return $this->bookmarks['here']; + } + }; + while ( $processor->next_tag() ) { + if ( + $processor->get_tag() === 'LINK' && + $processor->get_attribute( 'rel' ) === 'stylesheet' && + $processor->get_attribute( 'id' ) === 'wp-block-separator-css' + ) { + $block_separator_core_style_span = $processor->get_span(); + } elseif ( + $processor->get_tag() === 'STYLE' && + $processor->get_attribute( 'id' ) === 'wp-block-library-inline-css-extra' && + str_contains( $processor->get_modifiable_text(), '.wp-block-separator{ outline:solid 1px lime; }' ) + ) { + $block_separator_custom_style_span = $processor->get_span(); + } + } + + $this->assertInstanceOf( WP_HTML_Span::class, $block_separator_core_style_span, 'Expected the block separator core style to be present.' ); + $this->assertInstanceOf( WP_HTML_Span::class, $block_separator_custom_style_span, 'Expected the block separator custom style to be present.' ); + $this->assertGreaterThan( $block_separator_core_style_span->start, $block_separator_custom_style_span->start, 'Expected the block separator custom style to appear after the block separator stylesheet.' ); + }, ), + + 'standard_classic_theme_config_extra_block_library_inline_style_all_inlined' => array( + 'set_up' => static function () { + add_action( + 'enqueue_block_assets', + static function () { + // Extra CSS which prevents empty inline style containing placeholder from being removed. + wp_add_inline_style( 'wp-block-library', '.wp-block-separator{ outline:solid 1px lime; }' ); + } + ); + }, + 'content' => $blocks_content, + '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', + 'wp-block-library-inline-css-extra', + 'classic-theme-styles-inline-css', + 'third-party-test-block-css', + 'custom-block-styles-css', + 'global-styles-inline-css', + ), + $common_at_wp_enqueue_scripts, + $common_late_in_head, + $common_late_in_body + ), + 'BODY' => array(), + ), + 'assert' => function ( string $buffer, string $filtered_buffer ) { + $block_separator_inline_style_start_tag = '