Make WordPress Core

Changeset 61945


Ignore:
Timestamp:
03/11/2026 08:56:01 PM (7 months ago)
Author:
westonruter
Message:

Script Loader: Refine hoisted stylesheet ordering to preserve original CSS cascade in classic themes.

This introduces placeholder inline STYLE tags in the HEAD which are used to accurately locate hoisted styles which were printed in the footer. This improves the robustness of the hoisting logic. A style placeholder had been used previously in the inline style for wp-block-library for placing core block styles, and new placeholders are added for global styles and non-core block styles.

Furthermore, this fixes the cascade for inline styles added to wp-block-library. When separate block styles are not used, these styles would have appeared after the single combined wp-block-library. However, in 6.9 the order changed so that separate block styles would appear after. To preserve the original cascade, this splits the wp-block-library inline style: the first half (likely just the inlined block-library/common.css) appears before the block styles, and the remainder appears in a new STYLE#wp-block-library-inline-css-extra element.

Developed in ​https://github.com/WordPress/wordpress-develop/pull/10875

Follow-up to r61554, r61174, r61122, r61076, r61008.

Props westonruter, joefusco, adamsilverstein, ocean90, mmorris8, ozgursar, vanonsopensource, xwolf, immeet94, george9, joezappie, jorbin, sajib1223, sabernhardt.
See #64099, #64354, #64150, #43258.
Fixes #64389.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/script-loader.php

    r61703 r61945  
    25522552        $is_classic_theme = ! $is_block_theme;
    25532553
    2554         /*
    2555          * Global styles should be printed in the head for block themes, or for classic themes when loading assets on
    2556          * demand is disabled, which is the default.
    2557          * The footer should only be used for classic themes when loading assets on demand is enabled.
     2554        /**
     2555         * Global styles should be printed in the HEAD for block themes, or for classic themes when loading assets on
     2556         * demand is disabled (which is no longer the default since WordPress 6.9).
    25582557         *
    2559          * See https://core.trac.wordpress.org/ticket/53494 and https://core.trac.wordpress.org/ticket/61965.
     2558         * @link https://core.trac.wordpress.org/ticket/53494
     2559         * @link https://core.trac.wordpress.org/ticket/61965
    25602560         */
    25612561        if (
    2562                 ( $is_block_theme && doing_action( 'wp_footer' ) ) ||
    2563                 ( $is_classic_theme && doing_action( 'wp_footer' ) && ! $assets_on_demand ) ||
    2564                 ( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) && $assets_on_demand )
     2562                doing_action( 'wp_footer' ) &&
     2563                (
     2564                        $is_block_theme ||
     2565                        ( $is_classic_theme && ! $assets_on_demand )
     2566                )
    25652567        ) {
     2568                return;
     2569        }
     2570
     2571        /**
     2572         * The footer should only be used for classic themes when loading assets on demand is enabled. In WP 6.9 this is the
     2573         * default with the introduction of hoisting late-printed styles (via {@see wp_load_classic_theme_block_styles_on_demand()}).
     2574         * So even though the main global styles are not printed here in the HEAD for classic themes with on-demand asset
     2575         * loading, a placeholder for the global styles is still enqueued. Then when {@see wp_hoist_late_printed_styles()}
     2576         * processes the output buffer, it can locate the placeholder and inject the global styles from the footer into the
     2577         * HEAD, replacing the placeholder.
     2578         *
     2579         * @link https://core.trac.wordpress.org/ticket/64099
     2580         */
     2581        if ( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) && $assets_on_demand ) {
     2582                if ( has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) {
     2583                        wp_register_style( 'wp-global-styles-placeholder', false );
     2584                        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." }' );
     2585                        wp_enqueue_style( 'wp-global-styles-placeholder' );
     2586                }
    25662587                return;
    25672588        }
    … …  
    27422763function wp_enqueue_registered_block_scripts_and_styles() {
    27432764        if ( wp_should_load_block_assets_on_demand() ) {
     2765                /**
     2766                 * Add placeholder for where block styles would historically get enqueued in a classic theme when block assets
     2767                 * are not loaded on demand. This happens right after {@see wp_common_block_scripts_and_styles()} is called
     2768                 * at which time wp-block-library is enqueued.
     2769                 */
     2770                if ( ! wp_is_block_theme() && has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) {
     2771                        wp_register_style( 'wp-block-styles-placeholder', false );
     2772                        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." }' );
     2773                        wp_enqueue_style( 'wp-block-styles-placeholder' );
     2774                }
    27442775                return;
    27452776        }
    … …  
    37013732 * @see _wp_footer_scripts()
    37023733 */
    3703 function wp_hoist_late_printed_styles() {
     3734function wp_hoist_late_printed_styles(): void {
    37043735        // Skip the embed template on-demand styles aren't relevant, and there is no wp_head action.
    37053736        if ( is_embed() ) {
    … …  
    37073738        }
    37083739
    3709         // Capture the styles enqueued at the enqueue_block_assets action, so that non-core block styles and global styles can be inserted afterwards during hoisting.
    3710         $style_handles_at_enqueue_block_assets = array();
    3711         add_action(
    3712                 'enqueue_block_assets',
    3713                 static function () use ( &$style_handles_at_enqueue_block_assets ) {
    3714                         $style_handles_at_enqueue_block_assets = wp_styles()->queue;
    3715                 },
    3716                 PHP_INT_MIN
    3717         );
    3718         add_action(
    3719                 'enqueue_block_assets',
    3720                 static function () use ( &$style_handles_at_enqueue_block_assets ) {
    3721                         $style_handles_at_enqueue_block_assets = array_values( array_diff( wp_styles()->queue, $style_handles_at_enqueue_block_assets ) );
    3722                 },
    3723                 PHP_INT_MAX
    3724         );
    3725 
    37263740        /*
    37273741         * Add a placeholder comment into the inline styles for wp-block-library, after which the late block styles
    37283742         * can be hoisted from the footer to be printed in the header by means of a filter below on the template enhancement
    3729          * output buffer. The `wp_print_styles` action is used to ensure that if the inline style gets replaced at
    3730          * `enqueue_block_assets` or `wp_enqueue_scripts` that the placeholder will be sure to be present.
     3743         * output buffer.
     3744         *
     3745         * Note that wp_maybe_inline_styles() prepends the inlined style to the extra 'after' array, which happens after
     3746         * this code runs. This ensures that the placeholder appears right after any inlined wp-block-library styles,
     3747         * which would be common.css.
    37313748         */
    37323749        $placeholder = sprintf( '/*%s*/', uniqid( 'wp_block_styles_on_demand_placeholder:' ) );
    3733         add_action(
    3734                 'wp_print_styles',
    3735                 static function () use ( $placeholder ) {
     3750        $dependency  = wp_styles()->query( 'wp-block-library', 'registered' );
     3751        if ( $dependency ) {
     3752                if ( ! isset( $dependency->extra['after'] ) ) {
    37363753                        wp_add_inline_style( 'wp-block-library', $placeholder );
    3737                 }
    3738         );
     3754                } else {
     3755                        array_unshift( $dependency->extra['after'], $placeholder );
     3756                }
     3757        }
    37393758
    37403759        /*
    … …  
    37663785
    37673786                /*
    3768                  * First print all styles related to blocks which should be inserted right after the wp-block-library stylesheet
     3787                 * First print all styles related to core blocks which should be inserted right after the wp-block-library stylesheet
    37693788                 * to preserve the CSS cascade. The logic in this `if` statement is derived from `wp_print_styles()`.
    37703789                 */
    … …  
    37733792                        ob_start();
    37743793                        wp_styles()->do_items( $enqueued_core_block_styles );
    3775                         $printed_core_block_styles = ob_get_clean();
    3776                 }
    3777 
    3778                 // Non-core block styles get printed after the classic-theme-styles stylesheet.
     3794                        $printed_core_block_styles = (string) ob_get_clean();
     3795                }
     3796
     3797                // Capture non-core block styles so they can get printed at the point where wp_enqueue_registered_block_scripts_and_styles() runs.
    37793798                $enqueued_other_block_styles = array_values( array_intersect( $all_other_block_style_handles, wp_styles()->queue ) );
    37803799                if ( count( $enqueued_other_block_styles ) > 0 ) {
    37813800                        ob_start();
    37823801                        wp_styles()->do_items( $enqueued_other_block_styles );
    3783                         $printed_other_block_styles = ob_get_clean();
    3784                 }
    3785 
    3786                 // Capture the global-styles so that it can be printed separately after classic-theme-styles and other styles enqueued at enqueue_block_assets.
     3802                        $printed_other_block_styles = (string) ob_get_clean();
     3803                }
     3804
     3805                // Capture the global-styles so that it can be printed at the point where wp_enqueue_global_styles() runs.
    37873806                if ( wp_style_is( 'global-styles' ) ) {
    37883807                        ob_start();
    37893808                        wp_styles()->do_items( array( 'global-styles' ) );
    3790                         $printed_global_styles = ob_get_clean();
     3809                        $printed_global_styles = (string) ob_get_clean();
    37913810                }
    37923811
    … …  
    37983817                ob_start();
    37993818                wp_styles()->do_footer_items();
    3800                 $printed_late_styles = ob_get_clean();
     3819                $printed_late_styles = (string) ob_get_clean();
    38013820        };
    38023821
    … …  
    38293848        add_filter(
    38303849                'wp_template_enhancement_output_buffer',
    3831                 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 ) {
     3850                static function ( $buffer ) use ( $placeholder, &$printed_core_block_styles, &$printed_other_block_styles, &$printed_global_styles, &$printed_late_styles ) {
    38323851
    38333852                        // Anonymous subclass of WP_HTML_Tag_Processor which exposes underlying bookmark spans.
    … …  
    38493868                                 * @param string $text Text to insert.
    38503869                                 */
    3851                                 public function insert_before( string $text ) {
     3870                                public function insert_before( string $text ): void {
    38523871                                        $this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->get_span()->start, 0, $text );
    38533872                                }
    … …  
    38583877                                 * @param string $text Text to insert.
    38593878                                 */
    3860                                 public function insert_after( string $text ) {
     3879                                public function insert_after( string $text ): void {
    38613880                                        $span = $this->get_span();
    38623881
    … …  
    38673886                                 * Removes the current token.
    38683887                                 */
    3869                                 public function remove() {
     3888                                public function remove(): void {
    38703889                                        $span = $this->get_span();
    38713890
    38723891                                        $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, '' );
     3892                                }
     3893
     3894                                /**
     3895                                 * Replaces the current token.
     3896                                 *
     3897                                 * @param string $text Text to replace with.
     3898                                 */
     3899                                public function replace( string $text ): void {
     3900                                        $span = $this->get_span();
     3901
     3902                                        $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, $text );
    38733903                                }
    38743904                        };
    … …  
    38783908                                if (
    38793909                                        'STYLE' === $processor->get_tag() &&
     3910                                        'wp-global-styles-placeholder-inline-css' === $processor->get_attribute( 'id' )
     3911                                ) {
     3912                                        /** This is added in {@see wp_enqueue_global_styles()} */
     3913                                        $processor->set_bookmark( 'wp_global_styles_placeholder' );
     3914                                } elseif (
     3915                                        'STYLE' === $processor->get_tag() &&
     3916                                        'wp-block-styles-placeholder-inline-css' === $processor->get_attribute( 'id' )
     3917                                ) {
     3918                                        /** This is added in {@see wp_enqueue_registered_block_scripts_and_styles()} */
     3919                                        $processor->set_bookmark( 'wp_block_styles_placeholder' );
     3920                                } elseif (
     3921                                        'STYLE' === $processor->get_tag() &&
    38803922                                        'wp-block-library-inline-css' === $processor->get_attribute( 'id' )
    38813923                                ) {
     3924                                        /** This is added here in {@see wp_hoist_late_printed_styles()} */
    38823925                                        $processor->set_bookmark( 'wp_block_library' );
    38833926                                } elseif ( 'HEAD' === $processor->get_tag() && $processor->is_tag_closer() ) {
    38843927                                        $processor->set_bookmark( 'head_end' );
    38853928                                        break;
    3886                                 } elseif ( ( 'STYLE' === $processor->get_tag() || 'LINK' === $processor->get_tag() ) && $processor->get_attribute( 'id' ) ) {
    3887                                         $id     = $processor->get_attribute( 'id' );
    3888                                         $handle = null;
    3889                                         if ( 'STYLE' === $processor->get_tag() ) {
    3890                                                 if ( preg_match( '/^(.+)-inline-css$/', $id, $matches ) ) {
    3891                                                         $handle = $matches[1];
    3892                                                 }
    3893                                         } elseif ( preg_match( '/^(.+)-css$/', $id, $matches ) ) {
    3894                                                 $handle = $matches[1];
    3895                                         }
    3896 
    3897                                         if ( 'classic-theme-styles' === $handle ) {
    3898                                                 $processor->set_bookmark( 'classic_theme_styles' );
    3899                                         }
    3900 
    3901                                         if ( $handle && in_array( $handle, $style_handles_at_enqueue_block_assets, true ) ) {
    3902                                                 if ( ! $processor->has_bookmark( 'first_style_at_enqueue_block_assets' ) ) {
    3903                                                         $processor->set_bookmark( 'first_style_at_enqueue_block_assets' );
    3904                                                 }
    3905                                                 $processor->set_bookmark( 'last_style_at_enqueue_block_assets' );
    3906                                         }
    39073929                                }
     3930                        }
     3931
     3932                        /**
     3933                         * Replace the placeholder for global styles enqueued during {@see wp_enqueue_global_styles()}. This is done
     3934                         * even if $printed_global_styles is empty.
     3935                         */
     3936                        if ( $processor->has_bookmark( 'wp_global_styles_placeholder' ) ) {
     3937                                $processor->seek( 'wp_global_styles_placeholder' );
     3938                                $processor->replace( $printed_global_styles );
     3939                                $printed_global_styles = '';
    39083940                        }
    39093941
    … …  
    39223954
    39233955                                /*
    3924                                  * A placeholder CSS comment is added to the inline style in order to force an inline STYLE tag to
    3925                                  * be printed. Now that we've located the inline style, the placeholder comment can be removed. If
    3926                                  * there is no CSS left in the STYLE tag after removing the placeholder (aside from the sourceURL
    3927                                  * comment), then remove the STYLE entirely.
     3956                                 * Split the block library inline style by the placeholder to identify the original inlined CSS, which
     3957                                 * likely would be common.css, followed by any inline styles which had been added by the theme or
     3958                                 * plugins via `wp_add_inline_style( 'wp-block-library', '...' )`. The separate block styles loaded on
     3959                                 * demand will get inserted after the inlined common.css and before the extra inline styles added by the
     3960                                 * user.
    39283961                                 */
    3929                                 $css_text = str_replace( $placeholder, '', $css_text );
    3930                                 if ( preg_match( ':^/\*# sourceURL=\S+? \*/$:', trim( $css_text ) ) ) {
     3962                                $css_text_around_placeholder = explode( $placeholder, $css_text, 2 );
     3963                                $extra_inline_styles         = '';
     3964                                if ( count( $css_text_around_placeholder ) === 2 ) {
     3965                                        $css_text = $css_text_around_placeholder[0];
     3966                                        if ( '' !== trim( $css_text ) ) {
     3967                                                $inlined_src = wp_styles()->get_data( 'wp-block-library', 'inlined_src' );
     3968                                                if ( $inlined_src ) {
     3969                                                        $css_text .= sprintf(
     3970                                                                "\n/*# sourceURL=%s */\n",
     3971                                                                esc_url_raw( $inlined_src )
     3972                                                        );
     3973                                                }
     3974                                        }
     3975                                        $extra_inline_styles = $css_text_around_placeholder[1];
     3976                                }
     3977
     3978                                /*
     3979                                 * The placeholder CSS comment was added to the inline style in order to force an inline STYLE tag to
     3980                                 * be printed. Now that the inline style has been located and the placeholder comment has been removed, if
     3981                                 * there is no CSS left in the STYLE tag after removal, then remove the STYLE tag entirely.
     3982                                 */
     3983                                if ( '' === trim( $css_text ) ) {
    39313984                                        $processor->remove();
    39323985                                } else {
    … …  
    39373990                                $printed_core_block_styles = '';
    39383991
    3939                                 // If the classic-theme-styles is absent, then the third-party block styles cannot be inserted after it, so they get inserted here.
    3940                                 if ( ! $processor->has_bookmark( 'classic_theme_styles' ) ) {
    3941                                         if ( '' !== $printed_other_block_styles ) {
    3942                                                 $inserted_after .= $printed_other_block_styles;
    3943                                         }
    3944                                         $printed_other_block_styles = '';
    3945 
    3946                                         // If there aren't any other styles printed at enqueue_block_assets either, then the global styles need to also be printed here.
    3947                                         if ( ! $processor->has_bookmark( 'last_style_at_enqueue_block_assets' ) ) {
    3948                                                 if ( '' !== $printed_global_styles ) {
    3949                                                         $inserted_after .= $printed_global_styles;
    3950                                                 }
    3951                                                 $printed_global_styles = '';
    3952                                         }
     3992                                /*
     3993                                 * Add a new inline style for any user styles added via wp_add_inline_style( 'wp-block-library', '...' ).
     3994                                 * This must be added here after $printed_core_block_styles to preserve the original CSS cascade when
     3995                                 * the combined block library stylesheet was used. The pattern here is checking to see if it is not just
     3996                                 * a sourceURL comment after the placeholder above is removed.
     3997                                 */
     3998                                if ( ! preg_match( ':^\s*(/\*# sourceURL=\S+? \*/\s*)?$:s', $extra_inline_styles ) ) {
     3999                                        $style_processor = new WP_HTML_Tag_Processor( '<style></style>' );
     4000                                        $style_processor->next_tag();
     4001                                        $style_processor->set_attribute( 'id', 'wp-block-library-inline-css-extra' );
     4002                                        $style_processor->set_modifiable_text( $extra_inline_styles );
     4003                                        $inserted_after .= "{$style_processor->get_updated_html()}\n";
    39534004                                }
    39544005
    … …  
    39584009                        }
    39594010
    3960                         // Insert global-styles after the styles enqueued at enqueue_block_assets.
    3961                         if ( '' !== $printed_global_styles && $processor->has_bookmark( 'last_style_at_enqueue_block_assets' ) ) {
    3962                                 $processor->seek( 'last_style_at_enqueue_block_assets' );
    3963 
    3964                                 $processor->insert_after( "\n" . $printed_global_styles );
    3965                                 $printed_global_styles = '';
    3966 
    3967                                 if ( ! $processor->has_bookmark( 'classic_theme_styles' ) && '' !== $printed_other_block_styles ) {
    3968                                         $processor->insert_after( "\n" . $printed_other_block_styles );
    3969                                         $printed_other_block_styles = '';
     4011                        // Insert block styles at the point where wp_enqueue_registered_block_scripts_and_styles() normally enqueues styles.
     4012                        if ( $processor->has_bookmark( 'wp_block_styles_placeholder' ) ) {
     4013                                $processor->seek( 'wp_block_styles_placeholder' );
     4014                                if ( '' !== $printed_other_block_styles ) {
     4015                                        $processor->replace( "\n" . $printed_other_block_styles );
     4016                                } else {
     4017                                        $processor->remove();
    39704018                                }
    3971                         }
    3972 
    3973                         // Insert third-party block styles right after the classic-theme-styles.
    3974                         if ( '' !== $printed_other_block_styles && $processor->has_bookmark( 'classic_theme_styles' ) ) {
    3975                                 $processor->seek( 'classic_theme_styles' );
    3976                                 $processor->insert_after( "\n" . $printed_other_block_styles );
    39774019                                $printed_other_block_styles = '';
    39784020                        }
  • trunk/tests/phpunit/tests/template.php

    r61668 r61945  
    152152                }
    153153
     154                unset( $GLOBALS['_wp_tests_development_mode'] );
    154155                parent::tear_down();
    155156        }
    … …  
    14781479         * Data provider.
    14791480         *
    1480          * @return array<string, array{set_up: Closure|null, inline_size_limit: int,  expected_styles: array{ HEAD: string[], BODY: string[] }}>
     1481         * @return array<string, array{
     1482         *     set_up: Closure|null,
     1483         *     content: string,
     1484         *     inline_size_limit: int,
     1485         *     expected_styles: array{ HEAD: string[], BODY: string[] },
     1486         *     assert?: Closure( string, string ): void,
     1487         * }>
    14811488         */
    14821489        public function data_wp_hoist_late_printed_styles(): array {
     1490                $blocks_content = '<!-- wp:separator --><hr class="wp-block-separator has-alpha-channel-opacity"/><!-- /wp:separator --><!-- wp:third-party/test --><div>This is only a test!</div><!-- /wp:third-party/test -->';
     1491
    14831492                $early_common_styles = array(
    14841493                        'wp-img-auto-sizes-contain-inline-css',
    … …  
    14881497                );
    14891498
    1490                 $common_late_in_head = array(
    1491                         // Styles enqueued at wp_enqueue_scripts (priority 10).
     1499                // Styles enqueued at wp_enqueue_scripts (priority 10).
     1500                $common_at_wp_enqueue_scripts = array(
    14921501                        'normal-css',
    14931502                        'normal-inline-css',
    1494 
    1495                         // Styles printed at wp_head priority 10.
     1503                );
     1504
     1505                $common_late_in_head = array(
     1506                        // Styles printed at wp_head priority 101.
    14961507                        'wp-custom-css',
    14971508                );
    … …  
    15221533                                'global-styles-inline-css',
    15231534                        ),
     1535                        $common_at_wp_enqueue_scripts,
    15241536                        $common_late_in_head,
    15251537                        $common_late_in_body
    … …  
    15291541                        'standard_classic_theme_config_with_min_styles_inlined' => array(
    15301542                                'set_up'            => null,
     1543                                'content'           => $blocks_content,
    15311544                                'inline_size_limit' => 0,
    15321545                                'expected_styles'   => array(
    … …  
    15351548                                ),
    15361549                        ),
     1550
    15371551                        'standard_classic_theme_config_with_max_styles_inlined' => array(
    15381552                                'set_up'            => null,
     1553                                'content'           => $blocks_content,
    15391554                                'inline_size_limit' => PHP_INT_MAX,
    15401555                                'expected_styles'   => array(
    … …  
    15491564                                                        'global-styles-inline-css',
    15501565                                                ),
     1566                                                $common_at_wp_enqueue_scripts,
    15511567                                                $common_late_in_head,
    15521568                                                $common_late_in_body
    … …  
    15551571                                ),
    15561572                        ),
     1573
    15571574                        'classic_theme_styles_omitted'                => array(
    15581575                                'set_up'            => static function () {
    … …  
    15661583                                        );
    15671584                                },
     1585                                'content'           => $blocks_content,
    15681586                                'inline_size_limit' => PHP_INT_MAX,
    15691587                                'expected_styles'   => array(
    … …  
    15771595                                                        'global-styles-inline-css',
    15781596                                                ),
     1597                                                $common_at_wp_enqueue_scripts,
    15791598                                                $common_late_in_head,
    15801599                                                $common_late_in_body
    … …  
    15831602                                ),
    15841603                        ),
     1604
    15851605                        'no_styles_at_enqueued_block_assets'          => array(
    15861606                                'set_up'            => static function () {
    … …  
    15941614                                        );
    15951615                                },
     1616                                'content'           => $blocks_content,
    15961617                                'inline_size_limit' => PHP_INT_MAX,
    15971618                                'expected_styles'   => array(
    … …  
    16041625                                                        'global-styles-inline-css',
    16051626                                                ),
     1627                                                $common_at_wp_enqueue_scripts,
    16061628                                                $common_late_in_head,
    16071629                                                $common_late_in_body
    … …  
    16101632                                ),
    16111633                        ),
     1634
    16121635                        'no_global_styles'                            => array(
    16131636                                'set_up'            => static function () {
    1614                                         add_filter(
    1615                                                 'print_styles_array',
    1616                                                 static function ( $handles ) {
    1617                                                         return array_values( array_diff( $handles, array( 'global-styles' ) ) );
    1618                                                 }
    1619                                         );
    1620                                 },
     1637                                        $dequeue = static function () {
     1638                                                wp_dequeue_style( 'global-styles' );
     1639                                        };
     1640                                        add_action( 'wp_enqueue_scripts', $dequeue, 1000 );
     1641                                        add_action( 'wp_footer', $dequeue, 2 );
     1642                                },
     1643                                'content'           => $blocks_content,
    16211644                                'inline_size_limit' => PHP_INT_MAX,
    16221645                                'expected_styles'   => array(
    … …  
    16301653                                                        'custom-block-styles-css',
    16311654                                                ),
     1655                                                $common_at_wp_enqueue_scripts,
    16321656                                                $common_late_in_head,
    16331657                                                $common_late_in_body
    … …  
    16361660                                ),
    16371661                        ),
    1638                         'standard_classic_theme_config_extra_block_library_inline_style' => array(
     1662
     1663                        'standard_classic_theme_config_extra_block_library_inline_style_none_inlined' => array(
    16391664                                'set_up'            => static function () {
    16401665                                        add_action(
    16411666                                                'enqueue_block_assets',
    16421667                                                static function () {
    1643                                                         wp_add_inline_style( 'wp-block-library', '/* Extra CSS which prevents empty inline style containing placeholder from being removed. */' );
     1668                                                        // Extra CSS which prevents empty inline style containing placeholder from being removed.
     1669                                                        wp_add_inline_style( 'wp-block-library', '.wp-block-separator{ outline:solid 1px lime; }' );
    16441670                                                }
    16451671                                        );
    16461672                                },
     1673                                'content'           => $blocks_content,
    16471674                                'inline_size_limit' => 0,
    16481675                                'expected_styles'   => array(
    1649                                         'HEAD' => ( function ( $expected_styles ) {
    1650                                                 // Insert 'wp-block-library-inline-css' right after 'wp-block-library-css'.
    1651                                                 $i = array_search( 'wp-block-library-css', $expected_styles, true );
    1652                                                 $this->assertIsInt( $i, 'Expected wp-block-library-css to be among the styles.' );
    1653                                                 array_splice( $expected_styles, $i + 1, 0, 'wp-block-library-inline-css' );
    1654                                                 return $expected_styles;
    1655                                         } )( $common_expected_head_styles ),
     1676                                        'HEAD' => array_merge(
     1677                                                $early_common_styles,
     1678                                                array(
     1679                                                        'wp-block-library-css',
     1680                                                        'wp-block-separator-css',
     1681                                                        'wp-block-library-inline-css-extra',
     1682                                                        'classic-theme-styles-css',
     1683                                                        'third-party-test-block-css',
     1684                                                        'custom-block-styles-css',
     1685                                                        'global-styles-inline-css',
     1686                                                ),
     1687                                                $common_at_wp_enqueue_scripts,
     1688                                                $common_late_in_head,
     1689                                                $common_late_in_body
     1690                                        ),
    16561691                                        'BODY' => array(),
    16571692                                ),
    1658                         ),
     1693                                'assert'            => function ( string $buffer, string $filtered_buffer ) {
     1694                                        $block_separator_core_style_span = null;
     1695                                        $block_separator_custom_style_span = null;
     1696                                        $processor = new class( $filtered_buffer ) extends WP_HTML_Tag_Processor {
     1697                                                public function get_span(): WP_HTML_Span {
     1698                                                        $this->set_bookmark( 'here' );
     1699                                                        return $this->bookmarks['here'];
     1700                                                }
     1701                                        };
     1702                                        while ( $processor->next_tag() ) {
     1703                                                if (
     1704                                                        $processor->get_tag() === 'LINK' &&
     1705                                                        $processor->get_attribute( 'rel' ) === 'stylesheet' &&
     1706                                                        $processor->get_attribute( 'id' ) === 'wp-block-separator-css'
     1707                                                ) {
     1708                                                        $block_separator_core_style_span = $processor->get_span();
     1709                                                } elseif (
     1710                                                        $processor->get_tag() === 'STYLE' &&
     1711                                                        $processor->get_attribute( 'id' ) === 'wp-block-library-inline-css-extra' &&
     1712                                                        str_contains( $processor->get_modifiable_text(), '.wp-block-separator{ outline:solid 1px lime; }' )
     1713                                                ) {
     1714                                                        $block_separator_custom_style_span = $processor->get_span();
     1715                                                }
     1716                                        }
     1717
     1718                                        $this->assertInstanceOf( WP_HTML_Span::class, $block_separator_core_style_span, 'Expected the block separator core style to be present.' );
     1719                                        $this->assertInstanceOf( WP_HTML_Span::class, $block_separator_custom_style_span, 'Expected the block separator custom style to be present.' );
     1720                                        $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.' );
     1721                                },
     1722                        ),
     1723
     1724                        'standard_classic_theme_config_extra_block_library_inline_style_all_inlined' => array(
     1725                                'set_up'            => static function () {
     1726                                        add_action(
     1727                                                'enqueue_block_assets',
     1728                                                static function () {
     1729                                                        // Extra CSS which prevents empty inline style containing placeholder from being removed.
     1730                                                        wp_add_inline_style( 'wp-block-library', '.wp-block-separator{ outline:solid 1px lime; }' );
     1731                                                }
     1732                                        );
     1733                                },
     1734                                'content'           => $blocks_content,
     1735                                'inline_size_limit' => PHP_INT_MAX,
     1736                                'expected_styles'   => array(
     1737                                        'HEAD' => array_merge(
     1738                                                $early_common_styles,
     1739                                                array(
     1740                                                        'wp-block-library-inline-css',
     1741                                                        'wp-block-separator-inline-css',
     1742                                                        'wp-block-library-inline-css-extra',
     1743                                                        'classic-theme-styles-inline-css',
     1744                                                        'third-party-test-block-css',
     1745                                                        'custom-block-styles-css',
     1746                                                        'global-styles-inline-css',
     1747                                                ),
     1748                                                $common_at_wp_enqueue_scripts,
     1749                                                $common_late_in_head,
     1750                                                $common_late_in_body
     1751                                        ),
     1752                                        'BODY' => array(),
     1753                                ),
     1754                                'assert'            => function ( string $buffer, string $filtered_buffer ) {
     1755                                        $block_separator_inline_style_start_tag = '<style id="wp-block-separator-inline-css">';
     1756                                        $block_separator_custom_style           = '.wp-block-separator{ outline:solid 1px lime; }';
     1757                                        $this->assertStringContainsString( $block_separator_inline_style_start_tag, $filtered_buffer );
     1758                                        $this->assertStringContainsString( $block_separator_custom_style, $filtered_buffer );
     1759                                        $block_separator_inline_style_position = strpos( $filtered_buffer, $block_separator_inline_style_start_tag );
     1760                                        $block_separator_custom_style_position = strpos( $filtered_buffer, $block_separator_custom_style );
     1761                                        $this->assertTrue( $block_separator_custom_style_position > $block_separator_inline_style_position, 'Expected the block separator custom style to appear after the block separator stylesheet.' );
     1762                                },
     1763                        ),
     1764
    16591765                        'classic_theme_opt_out_separate_block_styles' => array(
    16601766                                'set_up'            => static function () {
    16611767                                        add_filter( 'should_load_separate_core_block_assets', '__return_false' );
    16621768                                },
     1769                                'content'           => $blocks_content,
    16631770                                'inline_size_limit' => 0,
    16641771                                'expected_styles'   => array(
    … …  
    16721779                                                        'global-styles-inline-css',
    16731780                                                ),
     1781                                                $common_at_wp_enqueue_scripts,
    16741782                                                $common_late_in_head
    16751783                                        ),
    … …  
    16771785                                ),
    16781786                        ),
     1787
    16791788                        '_wp_footer_scripts_removed'                  => array(
    16801789                                'set_up'            => static function () {
    16811790                                        remove_action( 'wp_print_footer_scripts', '_wp_footer_scripts' );
    16821791                                },
     1792                                'content'           => $blocks_content,
    16831793                                'inline_size_limit' => 0,
    16841794                                'expected_styles'   => array(
    … …  
    16871797                                ),
    16881798                        ),
     1799
    16891800                        'wp_print_footer_scripts_removed'             => array(
    16901801                                'set_up'            => static function () {
    16911802                                        remove_action( 'wp_footer', 'wp_print_footer_scripts', 20 );
    16921803                                },
     1804                                'content'           => $blocks_content,
    16931805                                'inline_size_limit' => 0,
    16941806                                'expected_styles'   => array(
    … …  
    16971809                                ),
    16981810                        ),
     1811
    16991812                        'both_actions_removed'                        => array(
    17001813                                'set_up'            => static function () {
    … …  
    17021815                                        remove_action( 'wp_footer', 'wp_print_footer_scripts' );
    17031816                                },
     1817                                'content'           => $blocks_content,
    17041818                                'inline_size_limit' => 0,
    17051819                                'expected_styles'   => array(
    … …  
    17081822                                ),
    17091823                        ),
    1710                         'disable_block_library'                       => array(
     1824
     1825                        'disable_block_library_and_load_combined'     => array(
    17111826                                'set_up'            => static function () {
    17121827                                        add_action(
    … …  
    17191834                                        add_filter( 'should_load_separate_core_block_assets', '__return_false' );
    17201835                                },
     1836                                'content'           => $blocks_content,
    17211837                                'inline_size_limit' => 0,
    17221838                                'expected_styles'   => array(
    … …  
    17291845                                                        'global-styles-inline-css',
    17301846                                                ),
     1847                                                $common_at_wp_enqueue_scripts,
    17311848                                                $common_late_in_head
    17321849                                        ),
    … …  
    17341851                                ),
    17351852                        ),
    1736                         'override_block_library_inline_style_late'    => array(
     1853
     1854                        // This tests the Elementor scenario (e.g. Hello Elementor).
     1855                        'dequeue_block_library_but_with_theme_json_and_no_block_content' => array(
    17371856                                'set_up'            => static function () {
    17381857                                        add_action(
    1739                                                 'enqueue_block_assets',
    1740                                                 function (): void {
    1741                                                         // This tests what happens when the placeholder comment gets replaced unexpectedly.
    1742                                                         wp_styles()->registered['wp-block-library']->extra['after'] = array( '/* OVERRIDDEN! */' );
    1743                                                 }
     1858                                                'wp_enqueue_scripts',
     1859                                                static function () {
     1860                                                        wp_dequeue_style( 'wp-block-library' );
     1861                                                        wp_dequeue_style( 'wp-block-library-theme' );
     1862                                                        wp_dequeue_style( 'custom-block-styles' );
     1863                                                },
     1864                                                999
    17441865                                        );
    1745                                 },
     1866
     1867                                        /*
     1868                                         * Simulate the theme having a theme.json so that 'classic-theme-styles' is not enqueued. Note that
     1869                                         * when 'classic-theme-styles' is present, then 'global-styles' gets inserted after it by
     1870                                         * wp_hoist_late_printed_styles(). So by omitting 'classic-theme-styles', this can verify that
     1871                                         * 'global-styles' is still printed before other styles.
     1872                                         */
     1873                                        $GLOBALS['_wp_tests_development_mode'] = 'theme';
     1874                                        add_filter(
     1875                                                'theme_file_path',
     1876                                                static function ( $path, $file ) {
     1877                                                        if ( 'theme.json' === $file ) {
     1878                                                                $path = __DIR__ . '/../data/themedir1/block-theme/theme.json';
     1879                                                        }
     1880                                                        return $path;
     1881                                                },
     1882                                                10,
     1883                                                2
     1884                                        );
     1885                                },
     1886                                'content'           => 'Hello World!',
    17461887                                'inline_size_limit' => 0,
    17471888                                'expected_styles'   => array(
    … …  
    17491890                                                $early_common_styles,
    17501891                                                array(
    1751                                                         'wp-block-library-css',
    1752                                                         'wp-block-library-inline-css', // This contains the "OVERRIDDEN" text.
    1753                                                         'wp-block-separator-css',
    1754                                                         'classic-theme-styles-css',
    1755                                                         'third-party-test-block-css',
    1756                                                         'custom-block-styles-css',
    17571892                                                        'global-styles-inline-css',
    17581893                                                ),
     1894                                                $common_at_wp_enqueue_scripts,
    17591895                                                $common_late_in_head,
    17601896                                                $common_late_in_body
    … …  
    17631899                                ),
    17641900                        ),
     1901
     1902                        // This tests the Elementor scenario but a theme.json is not present.
     1903                        'dequeue_block_library_but_without_theme_json_and_no_block_content' => array(
     1904                                'set_up'            => static function () {
     1905                                        add_action(
     1906                                                'wp_enqueue_scripts',
     1907                                                static function () {
     1908                                                        wp_dequeue_style( 'wp-block-library' );
     1909                                                        wp_dequeue_style( 'wp-block-library-theme' );
     1910                                                        wp_dequeue_style( 'custom-block-styles' );
     1911                                                },
     1912                                                999
     1913                                        );
     1914
     1915                                        /*
     1916                                         * Simulate the theme NOT having a theme.json so that 'classic-theme-styles' is enqueued.
     1917                                         */
     1918                                        $GLOBALS['_wp_tests_development_mode'] = 'theme';
     1919                                        add_filter(
     1920                                                'theme_file_path',
     1921                                                static function ( $path, $file ) {
     1922                                                        if ( 'theme.json' === $file ) {
     1923                                                                $path = __DIR__ . '/does-not-exist.json';
     1924                                                        }
     1925                                                        return $path;
     1926                                                },
     1927                                                10,
     1928                                                2
     1929                                        );
     1930                                },
     1931                                'content'           => 'Hello World!',
     1932                                'inline_size_limit' => 0,
     1933                                'expected_styles'   => array(
     1934                                        'HEAD' => array_merge(
     1935                                                $early_common_styles,
     1936                                                array(
     1937                                                        'classic-theme-styles-css',
     1938                                                        'global-styles-inline-css',
     1939                                                ),
     1940                                                $common_at_wp_enqueue_scripts,
     1941                                                $common_late_in_head,
     1942                                                $common_late_in_body
     1943                                        ),
     1944                                        'BODY' => array(),
     1945                                ),
     1946                        ),
    17651947                );
    17661948        }
    … …  
    17751957         *
    17761958         * @dataProvider data_wp_hoist_late_printed_styles
    1777          */
    1778         public function test_wp_hoist_late_printed_styles( ?Closure $set_up, int $inline_size_limit, array $expected_styles ): void {
     1959         *
     1960         * @phpstan-param array{ HEAD: string[], BODY: string[] } $expected_styles
     1961         */
     1962        public function test_wp_hoist_late_printed_styles( ?Closure $set_up, string $content, int $inline_size_limit, array $expected_styles, ?Closure $assert = null ): void {
    17791963                // `_print_emoji_detection_script()` assumes `wp-includes/js/wp-emoji-loader.js` is present:
    17801964                self::touch( ABSPATH . WPINC . '/js/wp-emoji-loader.js' );
    … …  
    18682052
    18692053                // Simulate the_content().
    1870                 $content = apply_filters(
    1871                         'the_content',
    1872                         '<!-- wp:separator --><hr class="wp-block-separator has-alpha-channel-opacity"/><!-- /wp:separator -->' .
    1873                         '<!-- wp:third-party/test --><div>This is only a test!</div><!-- /wp:third-party/test -->'
    1874                 );
     2054                $content = apply_filters( 'the_content', $content );
    18752055
    18762056                // Simulate footer scripts.
    … …  
    18802060                $buffer = '<html lang="en"><head><meta charset="utf-8">' . $head_output . '</head><body><main>' . $content . '</main>' . $footer_output . '</body></html>';
    18812061
    1882                 $placeholder_regexp = '#/\*wp_block_styles_on_demand_placeholder:[a-f0-9]+\*/#';
     2062                $global_styles_placeholder_regexp = '#<style id="wp-global-styles-placeholder-inline-css">#';
     2063                $block_library_placeholder_regexp = '#/\*wp_block_styles_on_demand_placeholder:[a-f0-9]+\*/#';
    18832064                if ( has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) {
    1884                         $this->assertMatchesRegularExpression( $placeholder_regexp, $buffer, 'Expected the placeholder to be present in the buffer.' );
     2065                        if ( wp_style_is( 'global-styles', 'enqueued' ) ) {
     2066                                $this->assertMatchesRegularExpression( $global_styles_placeholder_regexp, $buffer, 'Expected the global-styles placeholder to be present in the buffer.' );
     2067                        }
     2068                        if ( wp_style_is( 'wp-block-library', 'enqueued' ) ) {
     2069                                $this->assertMatchesRegularExpression( $block_library_placeholder_regexp, $buffer, 'Expected the wp-block-library placeholder to be present in the buffer.' );
     2070                        }
    18852071                }
    18862072
    … …  
    18902076                $this->assertStringContainsString( '</head>', $filtered_buffer, 'Expected the closing HEAD tag to be in the response.' );
    18912077
    1892                 $this->assertDoesNotMatchRegularExpression( $placeholder_regexp, $filtered_buffer, 'Expected the placeholder to be removed.' );
     2078                $this->assertDoesNotMatchRegularExpression( $global_styles_placeholder_regexp, $filtered_buffer, 'Expected the global-styles placeholder to be removed.' );
     2079                $this->assertDoesNotMatchRegularExpression( $block_library_placeholder_regexp, $filtered_buffer, 'Expected the wp-block-library placeholder to be removed.' );
    18932080                $found_styles = array(
    18942081                        'HEAD' => array(),
    … …  
    18962083                );
    18972084                $processor    = WP_HTML_Processor::create_full_parser( $filtered_buffer );
     2085                $this->assertInstanceOf( WP_HTML_Processor::class, $processor );
    18982086                while ( $processor->next_tag() ) {
    18992087                        $group = in_array( 'HEAD', $processor->get_breadcrumbs(), true ) ? 'HEAD' : 'BODY';
    … …  
    19312119                        'Expected the same styles. Snapshot: ' . self::get_array_snapshot_export( $found_subset_styles )
    19322120                );
     2121
     2122                if ( $assert ) {
     2123                        $assert( $buffer, $filtered_buffer );
     2124                }
    19332125        }
    19342126
Note: See TracChangeset for help on using the changeset viewer.