Make WordPress Core

Changeset 61945


Ignore:
Timestamp:
03/11/2026 08:56:01 PM (3 weeks 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.