Make WordPress Core

Changeset 61008


Ignore:
Timestamp:
10/21/2025 05:59:38 AM (11 months ago)
Author:
westonruter
Message:

Script Loader: Load block styles on demand in classic themes via the template enhancement output buffer.

  • This applies in classic themes when a site has not opted out of the template enhancement buffer by filtering wp_should_output_buffer_template_for_enhancement off.
  • Both should_load_separate_core_block_assets and should_load_block_assets_on_demand are filtered on, as otherwise they are only enabled by default in block themes.
  • Any style enqueued after wp_head and printed via print_late_styles() will get hoisted up to be inserted right after the wp-block-library inline style in the HEAD.
  • The result is a >10% benchmarked improvement in LCP for core classic themes due to a ~100KB reduction in the amount of CSS unconditionally being served with every page load.

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

Follow-up to [60936].

Props sjapaget, westonruter, peterwilsoncc, dmsnell, mindctrl.
See #43258.
Fixes #64099.

Location:
trunk
Files:
17 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/default-filters.php

    r60936 r61008  
    596596add_action( 'enqueue_block_assets', 'wp_enqueue_registered_block_scripts_and_styles' );
    597597add_action( 'enqueue_block_assets', 'enqueue_block_styles_assets', 30 );
     598add_action( 'init', 'wp_load_classic_theme_block_styles_on_demand', 8 ); // Must happen before register_core_block_style_handles() at priority 9.
    598599/*
    599600 * `wp_enqueue_registered_block_scripts_and_styles` is bound to both
  • trunk/src/wp-includes/script-loader.php

    r61007 r61008  
    22652265 * Private, for use in *_footer_scripts hooks
    22662266 *
     2267 * In classic themes, when block styles are loaded on demand via {@see wp_load_classic_theme_block_styles_on_demand()},
     2268 * this function is replaced by a closure in {@see wp_hoist_late_printed_styles()} which will capture the output of
     2269 * {@see print_late_styles()} before printing footer scripts as usual. The captured late-printed styles are then hoisted
     2270 * to the HEAD by means of the template enhancement output buffer.
     2271 *
    22672272 * @since 3.3.0
    22682273 */
     
    32333238 */
    32343239function wp_enqueue_stored_styles( $options = array() ) {
     3240        // Note: Styles printed at wp_footer for classic themes may still end up in the head due to wp_load_classic_theme_block_styles_on_demand().
    32353241        $is_block_theme   = wp_is_block_theme();
    32363242        $is_classic_theme = ! $is_block_theme;
     
    34713477
    34723478/**
     3479 * Adds hooks to load block styles on demand in classic themes.
     3480 *
     3481 * @since 6.9.0
     3482 */
     3483function wp_load_classic_theme_block_styles_on_demand() {
     3484        if ( wp_is_block_theme() ) {
     3485                return;
     3486        }
     3487
     3488        /*
     3489         * Make sure that wp_should_output_buffer_template_for_enhancement() returns true even if there aren't any
     3490         * `wp_template_enhancement_output_buffer` filters added, but do so at priority zero so that applications which
     3491         * wish to stream responses can more easily turn this off.
     3492         */
     3493        add_filter( 'wp_should_output_buffer_template_for_enhancement', '__return_true', 0 );
     3494
     3495        if ( ! wp_should_output_buffer_template_for_enhancement() ) {
     3496                return;
     3497        }
     3498
     3499        /*
     3500         * Load separate block styles so that the large block-library stylesheet is not enqueued unconditionally,
     3501         * and so that block-specific styles will only be enqueued when they are used on the page.
     3502         */
     3503        add_filter( 'should_load_separate_core_block_assets', '__return_true', 0 );
     3504
     3505        // Also ensure that block assets are loaded on demand (although the default value is from should_load_separate_core_block_assets).
     3506        add_filter( 'should_load_block_assets_on_demand', '__return_true', 0 );
     3507
     3508        // Add hooks which require the presence of the output buffer. Ideally the above two filters could be added here, but they run too early.
     3509        add_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' );
     3510}
     3511
     3512/**
     3513 * Adds the hooks needed for CSS output to be delayed until after the content of the page has been established.
     3514 *
     3515 * @since 6.9.0
     3516 *
     3517 * @see wp_load_classic_theme_block_styles_on_demand()
     3518 * @see _wp_footer_scripts()
     3519 */
     3520function wp_hoist_late_printed_styles() {
     3521        // Skip the embed template on-demand styles aren't relevant, and there is no wp_head action.
     3522        if ( is_embed() ) {
     3523                return;
     3524        }
     3525
     3526        /*
     3527         * While normally late styles are printed, there is a filter to disable prevent this, so this makes sure they are
     3528         * printed. Note that this filter was intended to control whether to print the styles queued too late for the HTML
     3529         * head. This filter was introduced in <https://core.trac.wordpress.org/ticket/9346>. However, with the template
     3530         * enhancement output buffer, essentially no style can be enqueued too late, because an output buffer filter can
     3531         * always hoist it to the HEAD.
     3532         */
     3533        add_filter( 'print_late_styles', '__return_true', PHP_INT_MAX );
     3534
     3535        /*
     3536         * Print a placeholder comment where the late styles can be hoisted from the footer to be printed in the header
     3537         * by means of a filter below on the template enhancement output buffer.
     3538         */
     3539        $placeholder = sprintf( '/*%s*/', uniqid( 'wp_late_styles_placeholder:' ) );
     3540
     3541        wp_add_inline_style( 'wp-block-library', $placeholder );
     3542
     3543        // Wrap print_late_styles() with a closure that captures the late-printed styles.
     3544        $printed_late_styles = '';
     3545        $capture_late_styles = static function () use ( &$printed_late_styles ) {
     3546                ob_start();
     3547                print_late_styles();
     3548                $printed_late_styles = ob_get_clean();
     3549        };
     3550
     3551        /*
     3552         * If _wp_footer_scripts() was unhooked from the wp_print_footer_scripts action, or if wp_print_footer_scripts()
     3553         * was unhooked from running at the wp_footer action, then only add a callback to wp_footer which will capture the
     3554         * late-printed styles.
     3555         *
     3556         * Otherwise, in the normal case where _wp_footer_scripts() will run at the wp_print_footer_scripts action, then
     3557         * swap out _wp_footer_scripts() with an alternative which captures the printed styles (for hoisting to HEAD) before
     3558         * proceeding with printing the footer scripts.
     3559         */
     3560        $wp_print_footer_scripts_priority = has_action( 'wp_print_footer_scripts', '_wp_footer_scripts' );
     3561        if ( false === $wp_print_footer_scripts_priority || false === has_action( 'wp_footer', 'wp_print_footer_scripts' ) ) {
     3562                // The normal priority for wp_print_footer_scripts() is to run at 20.
     3563                add_action( 'wp_footer', $capture_late_styles, 20 );
     3564        } else {
     3565                remove_action( 'wp_print_footer_scripts', '_wp_footer_scripts', $wp_print_footer_scripts_priority );
     3566                add_action(
     3567                        'wp_print_footer_scripts',
     3568                        static function () use ( $capture_late_styles ) {
     3569                                $capture_late_styles();
     3570                                print_footer_scripts();
     3571                        },
     3572                        $wp_print_footer_scripts_priority
     3573                );
     3574        }
     3575
     3576        // Replace placeholder with the captured late styles.
     3577        add_filter(
     3578                'wp_template_enhancement_output_buffer',
     3579                function ( $buffer ) use ( $placeholder, &$printed_late_styles ) {
     3580
     3581                        // Anonymous subclass of WP_HTML_Tag_Processor which exposes underlying bookmark spans.
     3582                        $processor = new class( $buffer ) extends WP_HTML_Tag_Processor {
     3583                                public function get_span(): WP_HTML_Span {
     3584                                        $instance = $this; // phpcs:ignore PHPCompatibility.FunctionDeclarations.NewClosure.ThisFoundOutsideClass -- It is inside an anonymous class.
     3585                                        $instance->set_bookmark( 'here' );
     3586                                        return $instance->bookmarks['here'];
     3587                                }
     3588                        };
     3589
     3590                        // Loop over STYLE tags.
     3591                        while ( $processor->next_tag( array( 'tag_name' => 'STYLE' ) ) ) {
     3592                                // Skip to the next if this is not the inline style for the wp-block-library stylesheet (which contains the placeholder).
     3593                                if ( 'wp-block-library-inline-css' !== $processor->get_attribute( 'id' ) ) {
     3594                                        continue;
     3595                                }
     3596
     3597                                // If the inline style lacks the placeholder comment, then something went wrong and we need to abort.
     3598                                $css_text = $processor->get_modifiable_text();
     3599                                if ( ! str_contains( $css_text, $placeholder ) ) {
     3600                                        break;
     3601                                }
     3602
     3603                                // Remove the placeholder now that we've located the inline style.
     3604                                $processor->set_modifiable_text( str_replace( $placeholder, '', $css_text ) );
     3605                                $buffer = $processor->get_updated_html();
     3606
     3607                                // Insert the $printed_late_styles immediately after the closing inline STYLE tag. This preserves the CSS cascade.
     3608                                $span   = $processor->get_span();
     3609                                $buffer = implode(
     3610                                        '',
     3611                                        array(
     3612                                                substr( $buffer, 0, $span->start + $span->length ),
     3613                                                $printed_late_styles,
     3614                                                substr( $buffer, $span->start + $span->length ),
     3615                                        )
     3616                                );
     3617                                break;
     3618                        }
     3619
     3620                        return $buffer;
     3621                }
     3622        );
     3623}
     3624
     3625/**
    34733626 * Return the corresponding JavaScript `dataset` name for an attribute
    34743627 * if it represents a custom data attribute, or `null` if not.
  • trunk/tests/phpunit/includes/abstract-testcase.php

    r60989 r61008  
    219219
    220220                $this->reset_lazyload_queue();
     221
     222                WP_Style_Engine_CSS_Rules_Store::remove_all_stores();
    221223        }
    222224
  • trunk/tests/phpunit/tests/block-supports/duotone.php

    r60729 r61008  
    1010
    1111class Tests_Block_Supports_Duotone extends WP_UnitTestCase {
    12         /**
    13          * Cleans up CSS added to block-supports from duotone styles. We need to do this
    14          * in order to avoid impacting other tests.
    15          */
    16         public static function wpTearDownAfterClass() {
    17                 WP_Style_Engine_CSS_Rules_Store::remove_all_stores();
    18         }
    19 
    2012        /**
    2113         * Tests whether the duotone preset class is added to the block.
  • trunk/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php

    r60919 r61008  
    4141                wp_clean_themes_cache();
    4242                unset( $GLOBALS['wp_themes'] );
    43                 WP_Style_Engine_CSS_Rules_Store::remove_all_stores();
    4443        }
    4544
     
    5453                wp_clean_themes_cache();
    5554                unset( $GLOBALS['wp_themes'] );
    56                 WP_Style_Engine_CSS_Rules_Store::remove_all_stores();
    5755                unregister_block_type( $this->test_block_name );
    5856                $this->test_block_name = null;
  • trunk/tests/phpunit/tests/block-supports/wpRenderDimensionsSupport.php

    r58181 r61008  
    4141                wp_clean_themes_cache();
    4242                unset( $GLOBALS['wp_themes'] );
    43                 WP_Style_Engine_CSS_Rules_Store::remove_all_stores();
    4443        }
    4544
     
    5453                wp_clean_themes_cache();
    5554                unset( $GLOBALS['wp_themes'] );
    56                 WP_Style_Engine_CSS_Rules_Store::remove_all_stores();
    5755                unregister_block_type( $this->test_block_name );
    5856                $this->test_block_name = null;
  • trunk/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php

    r58074 r61008  
    1313
    1414        public function tear_down() {
    15                 WP_Style_Engine_CSS_Rules_Store::remove_all_stores();
    1615                unregister_block_type( $this->test_block_name );
    1716                $this->test_block_name = null;
  • trunk/tests/phpunit/tests/block-supports/wpRenderElementsSupportStyles.php

    r58074 r61008  
    1313
    1414        public function tear_down() {
    15                 WP_Style_Engine_CSS_Rules_Store::remove_all_stores();
    1615                unregister_block_type( $this->test_block_name );
    1716                $this->test_block_name = null;
  • trunk/tests/phpunit/tests/block-supports/wpRenderPositionSupport.php

    r55286 r61008  
    4242                wp_clean_themes_cache();
    4343                unset( $GLOBALS['wp_themes'] );
    44                 WP_Style_Engine_CSS_Rules_Store::remove_all_stores();
    4544        }
    4645
     
    5554                wp_clean_themes_cache();
    5655                unset( $GLOBALS['wp_themes'] );
    57                 WP_Style_Engine_CSS_Rules_Store::remove_all_stores();
    5856                unregister_block_type( $this->test_block_name );
    5957                $this->test_block_name = null;
  • trunk/tests/phpunit/tests/blocks/register.php

    r59938 r61008  
    1212
    1313        /**
     14         * @var WP_Scripts|null
     15         */
     16        protected $original_wp_scripts;
     17
     18        /**
     19         * @var WP_Styles|null
     20         */
     21        protected $original_wp_styles;
     22
     23        /**
    1424         * ID for a test post.
    1525         *
     
    4656         */
    4757        public function render_stub() {}
     58
     59        /**
     60         * Set up.
     61         */
     62        public function set_up() {
     63                parent::set_up();
     64
     65                global $wp_scripts, $wp_styles;
     66                $this->original_wp_scripts = $wp_scripts;
     67                $this->original_wp_styles  = $wp_styles;
     68                $wp_scripts                = null;
     69                $wp_styles                 = null;
     70                wp_scripts();
     71                wp_styles();
     72        }
    4873
    4974        /**
     
    6792                        }
    6893                }
     94
     95                global $wp_scripts, $wp_styles;
     96                $wp_scripts = $this->original_wp_scripts;
     97                $wp_styles  = $this->original_wp_styles;
    6998
    7099                parent::tear_down();
     
    10041033                );
    10051034
     1035                // Register the styles not included in the metadata above.
     1036                $metadata = array(
     1037                        'file'      => DIR_TESTDATA . '/blocks/notice/block.json',
     1038                        'name'      => 'tests/notice',
     1039                        'style'     => 'file:./block.css',
     1040                        'viewStyle' => 'file:./block-view.css',
     1041                );
     1042                $this->assertSame( 'tests-notice-style', register_block_style_handle( $metadata, 'style' ), 'Style handle is expected to be tests-notice-style' );
     1043                $this->assertSame( 'tests-notice-view-style', register_block_style_handle( $metadata, 'viewStyle' ), 'View style handle is expected to be tests-notice-view-style' );
     1044                $this->assertTrue( wp_style_is( 'tests-notice-style', 'registered' ), 'Expected "tests-notice-style" style to be registered.' );
     1045                $this->assertTrue( wp_style_is( 'tests-notice-view-style', 'registered' ), 'Expected "tests-notice-view-style" style to be registered.' );
     1046
    10061047                $this->assertInstanceOf( 'WP_Block_Type', $result );
    10071048                $this->assertSame( 2, $result->api_version );
     
    11221163                $this->assertSame(
    11231164                        wp_normalize_path( realpath( DIR_TESTDATA . '/blocks/notice/block.css' ) ),
    1124                         wp_normalize_path( wp_styles()->get_data( 'tests-test-block-style', 'path' ) )
     1165                        wp_normalize_path( wp_styles()->get_data( 'tests-notice-style', 'path' ) )
    11251166                );
    11261167
     
    11281169                $this->assertSame(
    11291170                        wp_normalize_path( realpath( DIR_TESTDATA . '/blocks/notice/block-view.css' ) ),
    1130                         wp_normalize_path( wp_styles()->get_data( 'tests-test-block-view-style', 'path' ) ),
     1171                        wp_normalize_path( wp_styles()->get_data( 'tests-notice-view-style', 'path' ) ),
    11311172                        'viewStyle asset path is not correct'
    11321173                );
  • trunk/tests/phpunit/tests/blocks/registerCoreBlockStyleHandles.php

    r57028 r61008  
    1616
    1717        /**
    18          * @var WP_Styles
     18         * @var WP_Styles|null
    1919         */
    20         private $old_wp_styles;
     20        protected $original_wp_styles;
    2121
    2222        /**
     
    3333                parent::set_up();
    3434
    35                 $this->old_wp_styles = $GLOBALS['wp_styles'];
     35                global $wp_styles;
     36                $this->original_wp_styles = $wp_styles;
     37                $wp_styles                = null;
     38                wp_styles();
    3639
    3740                $this->includes_url = includes_url();
    3841
    3942                remove_action( 'wp_default_styles', 'wp_default_styles' );
    40 
    41                 if ( empty( $GLOBALS['wp_styles'] ) ) {
    42                         $GLOBALS['wp_styles'] = null;
    43                 }
    4443        }
    4544
    4645        public function tear_down() {
    47                 $GLOBALS['wp_styles'] = $this->old_wp_styles;
     46                global $wp_styles;
     47                $wp_styles = $this->original_wp_styles;
    4848
    4949                add_action( 'wp_default_styles', 'wp_default_styles' );
     
    5757         * @dataProvider data_block_data
    5858         *
     59         * @covers ::register_core_block_style_handles
     60         * @covers ::wp_should_load_separate_core_block_assets
     61         *
    5962         * @param string $name   The block name.
    6063         * @param array  $schema The block's schema.
    6164         */
    6265        public function test_wp_should_load_separate_core_block_assets_false( $name, $schema ) {
     66                add_filter( 'should_load_separate_core_block_assets', '__return_false' );
     67                $this->assertFalse( wp_should_load_separate_core_block_assets(), 'Core blocks are not expected to load separate assets' );
    6368                register_core_block_style_handles();
    6469
     
    7984         * @dataProvider data_block_data
    8085         *
     86         * @covers ::register_core_block_style_handles
     87         * @covers ::wp_should_load_separate_core_block_assets
     88         *
    8189         * @param string $name   The block name.
    8290         * @param array  $schema The block's schema.
     
    8492        public function test_wp_should_load_separate_core_block_assets_true( $name, $schema ) {
    8593                add_filter( 'should_load_separate_core_block_assets', '__return_true' );
     94                $this->assertTrue( wp_should_load_separate_core_block_assets(), 'Core assets are expected to load separately' );
    8695                register_core_block_style_handles();
    8796
  • trunk/tests/phpunit/tests/dependencies/styles.php

    r60948 r61008  
    437437         */
    438438        public function test_block_styles_for_editing_with_theme_support() {
     439                // Override wp_load_classic_theme_block_styles_on_demand().
     440                add_filter( 'should_load_separate_core_block_assets', '__return_false' );
     441
    439442                add_theme_support( 'wp-block-styles' );
    440443
     
    473476         */
    474477        public function test_block_styles_for_viewing_with_theme_support() {
     478                // Override wp_load_classic_theme_block_styles_on_demand().
     479                add_filter( 'should_load_separate_core_block_assets', '__return_false' );
     480
    475481                add_theme_support( 'wp-block-styles' );
    476482
  • trunk/tests/phpunit/tests/style-engine/styleEngine.php

    r60892 r61008  
    1414 */
    1515class Tests_wpStyleEngine extends WP_UnitTestCase {
    16         /**
    17          * Cleans up stores after each test.
    18          */
    19         public function tear_down() {
    20                 WP_Style_Engine_CSS_Rules_Store::remove_all_stores();
    21                 parent::tear_down();
    22         }
    23 
    2416        /**
    2517         * Tests generating block styles and classnames based on various manifestations of the $block_styles argument.
  • trunk/tests/phpunit/tests/style-engine/wpStyleEngineCssRulesStore.php

    r58089 r61008  
    1717class Tests_Style_Engine_wpStyleEngineCSSRulesStore extends WP_UnitTestCase {
    1818        /**
    19          * Cleans up stores after each test.
    20          */
    21         public function tear_down() {
    22                 WP_Style_Engine_CSS_Rules_Store::remove_all_stores();
    23                 parent::tear_down();
    24         }
    25 
    26         /**
    2719         * Tests creating a new store on instantiation.
    2820         *
  • trunk/tests/phpunit/tests/template.php

    r60998 r61008  
    6969        protected $original_default_mimetype;
    7070
     71        /**
     72         * @var WP_Scripts|null
     73         */
     74        protected $original_wp_scripts;
     75
     76        /**
     77         * @var WP_Styles|null
     78         */
     79        protected $original_wp_styles;
     80
    7181        public function set_up() {
    7282                parent::set_up();
     
    8797                );
    8898                $this->set_permalink_structure( '/%year%/%monthnum%/%day%/%postname%/' );
     99
     100                // Remove hooks which are added by wp_load_classic_theme_block_styles_on_demand() during bootstrapping.
     101                remove_filter( 'wp_should_output_buffer_template_for_enhancement', '__return_true', 0 );
     102                remove_filter( 'should_load_separate_core_block_assets', '__return_true', 0 );
     103                remove_filter( 'should_load_block_assets_on_demand', '__return_true', 0 );
     104                remove_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' );
     105
     106                global $wp_scripts, $wp_styles;
     107                $this->original_wp_scripts = $wp_scripts;
     108                $this->original_wp_styles  = $wp_styles;
     109                $wp_scripts                = null;
     110                $wp_styles                 = null;
     111                wp_scripts();
     112                wp_styles();
    89113        }
    90114
    91115        public function tear_down() {
     116                global $wp_scripts, $wp_styles;
     117                $wp_scripts = $this->original_wp_scripts;
     118                $wp_styles  = $this->original_wp_styles;
     119
    92120                ini_set( 'default_mimetype', $this->original_default_mimetype );
    93121                unregister_post_type( 'cpt' );
     
    504532
    505533        /**
    506          * Tests that wp_start_template_enhancement_output_buffer() does not start a buffer when no filters are present.
     534         * Tests that wp_start_template_enhancement_output_buffer() does not start a buffer in a block theme when no filters are present.
    507535         *
    508536         * @ticket 43258
     537         * @ticket 64099
     538         *
    509539         * @covers ::wp_should_output_buffer_template_for_enhancement
    510540         * @covers ::wp_start_template_enhancement_output_buffer
    511          */
    512         public function test_wp_start_template_enhancement_output_buffer_without_filters_and_no_override(): void {
    513                 remove_all_filters( 'wp_template_enhancement_output_buffer' );
     541         * @covers ::wp_load_classic_theme_block_styles_on_demand
     542         */
     543        public function test_wp_start_template_enhancement_output_buffer_without_filters_and_no_override_in_block_theme(): void {
     544                switch_theme( 'block-theme' );
     545                wp_load_classic_theme_block_styles_on_demand();
     546
    514547                $level = ob_get_level();
    515548                $this->assertFalse( wp_should_output_buffer_template_for_enhancement(), 'Expected wp_should_output_buffer_template_for_enhancement() to return false when there are no wp_template_enhancement_output_buffer filters added.' );
     
    520553
    521554        /**
     555         * Tests that wp_start_template_enhancement_output_buffer() does start a buffer in classic theme.
     556         *
     557         * @ticket 43258
     558         * @ticket 64099
     559         *
     560         * @covers ::wp_should_output_buffer_template_for_enhancement
     561         * @covers ::wp_start_template_enhancement_output_buffer
     562         * @covers ::wp_load_classic_theme_block_styles_on_demand
     563         */
     564        public function test_wp_start_template_enhancement_output_buffer_in_classic_theme(): void {
     565                switch_theme( 'default' );
     566                wp_load_classic_theme_block_styles_on_demand();
     567
     568                $level = ob_get_level();
     569                $this->assertTrue( wp_should_output_buffer_template_for_enhancement(), 'Expected wp_should_output_buffer_template_for_enhancement() to return true because wp_load_classic_theme_block_styles_on_demand() adds wp_template_enhancement_output_buffer filters.' );
     570                $this->assertTrue( wp_start_template_enhancement_output_buffer(), 'Expected wp_start_template_enhancement_output_buffer() to return true because the output buffer should be started.' );
     571                $this->assertSame( 1, did_action( 'wp_template_enhancement_output_buffer_started' ), 'Expected the wp_template_enhancement_output_buffer_started action to have fired.' );
     572                $this->assertSame( $level + 1, ob_get_level(), 'Expected the initial output buffer level to be incremented by one.' );
     573                ob_end_clean();
     574        }
     575
     576        /**
    522577         * Tests that wp_start_template_enhancement_output_buffer() does start a buffer when no filters are present but there is an override.
    523578         *
     
    527582         */
    528583        public function test_wp_start_template_enhancement_output_buffer_begins_without_filters_but_overridden(): void {
    529                 remove_all_filters( 'wp_template_enhancement_output_buffer' );
    530584                $level = ob_get_level();
    531585                add_filter( 'wp_should_output_buffer_template_for_enhancement', '__return_true' );
     
    843897        }
    844898
     899        /**
     900         * Tests that wp_load_classic_theme_block_styles_on_demand() does not add hooks for block themes.
     901         *
     902         * @ticket 64099
     903         * @covers ::wp_load_classic_theme_block_styles_on_demand
     904         */
     905        public function test_wp_load_classic_theme_block_styles_on_demand_in_block_theme(): void {
     906                switch_theme( 'block-theme' );
     907
     908                wp_load_classic_theme_block_styles_on_demand();
     909
     910                $this->assertFalse( has_filter( 'should_load_separate_core_block_assets' ), 'Expect should_load_separate_core_block_assets filter NOT to be added for block themes.' );
     911                $this->assertFalse( has_filter( 'should_load_block_assets_on_demand', '__return_true' ), 'Expect should_load_block_assets_on_demand filter NOT to be added for block themes.' );
     912                $this->assertFalse( has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ), 'Expect wp_template_enhancement_output_buffer_started action NOT to be added for block themes.' );
     913        }
     914
     915        /**
     916         * Tests that wp_load_classic_theme_block_styles_on_demand() does not add hooks for classic themes when output buffering is blocked.
     917         *
     918         * @ticket 64099
     919         * @covers ::wp_load_classic_theme_block_styles_on_demand
     920         */
     921        public function test_wp_load_classic_theme_block_styles_on_demand_in_classic_theme_but_output_buffering_blocked(): void {
     922                add_filter( 'wp_should_output_buffer_template_for_enhancement', '__return_false' );
     923                switch_theme( 'default' );
     924
     925                wp_load_classic_theme_block_styles_on_demand();
     926
     927                $this->assertFalse( has_filter( 'should_load_separate_core_block_assets' ), 'Expect should_load_separate_core_block_assets filter NOT to be added for block themes.' );
     928                $this->assertFalse( has_filter( 'should_load_block_assets_on_demand', '__return_true' ), 'Expect should_load_block_assets_on_demand filter NOT to be added for block themes.' );
     929                $this->assertFalse( has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ), 'Expect wp_template_enhancement_output_buffer_started action NOT to be added for block themes.' );
     930        }
     931
     932        /**
     933         * Tests that wp_load_classic_theme_block_styles_on_demand() adds the expected hooks for classic themes.
     934         *
     935         * @ticket 64099
     936         * @covers ::wp_load_classic_theme_block_styles_on_demand
     937         */
     938        public function test_wp_load_classic_theme_block_styles_on_demand_in_classic_theme(): void {
     939                switch_theme( 'default' );
     940
     941                $this->assertFalse( wp_should_load_separate_core_block_assets(), 'Expected wp_should_load_separate_core_block_assets() to return false initially.' );
     942                $this->assertFalse( wp_should_load_block_assets_on_demand(), 'Expected wp_should_load_block_assets_on_demand() to return true' );
     943                $this->assertFalse( has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ), 'Expected wp_template_enhancement_output_buffer_started action to be added for classic themes.' );
     944
     945                wp_load_classic_theme_block_styles_on_demand();
     946
     947                $this->assertTrue( wp_should_load_separate_core_block_assets(), 'Expected wp_should_load_separate_core_block_assets() filters to return true' );
     948                $this->assertTrue( wp_should_load_block_assets_on_demand(), 'Expected wp_should_load_block_assets_on_demand() to return true' );
     949                $this->assertNotFalse( has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ), 'Expected wp_template_enhancement_output_buffer_started action to be added for classic themes.' );
     950        }
     951
     952        /**
     953         * Data provider.
     954         *
     955         * @return array<string, array{set_up?: Closure}>
     956         */
     957        public function data_wp_hoist_late_printed_styles(): array {
     958                return array(
     959                        'no_actions_removed'              => array(
     960                                'set_up' => null,
     961                        ),
     962                        '_wp_footer_scripts_removed'      => array(
     963                                'set_up' => static function () {
     964                                        remove_action( 'wp_print_footer_scripts', '_wp_footer_scripts' );
     965                                },
     966                        ),
     967                        'wp_print_footer_scripts_removed' => array(
     968                                'set_up' => static function () {
     969                                        remove_action( 'wp_footer', 'wp_print_footer_scripts', 20 );
     970                                },
     971                        ),
     972                        'both_actions_removed'            => array(
     973                                'set_up' => static function () {
     974                                        remove_action( 'wp_print_footer_scripts', '_wp_footer_scripts' );
     975                                        remove_action( 'wp_footer', 'wp_print_footer_scripts' );
     976                                },
     977                        ),
     978                );
     979        }
     980
     981        /**
     982         * Tests that wp_hoist_late_printed_styles() adds a placeholder for delayed CSS, then removes it and adds all CSS to the head including late enqueued styles.
     983         *
     984         * @ticket 64099
     985         * @covers ::wp_hoist_late_printed_styles
     986         *
     987         * @dataProvider data_wp_hoist_late_printed_styles
     988         */
     989        public function test_wp_hoist_late_printed_styles( ?Closure $set_up ): void {
     990                if ( $set_up ) {
     991                        $set_up();
     992                }
     993
     994                switch_theme( 'default' );
     995
     996                // Enqueue a style
     997                wp_enqueue_style( 'early', 'http://example.com/style.css' );
     998                wp_add_inline_style( 'early', '/* EARLY */' );
     999
     1000                wp_hoist_late_printed_styles();
     1001
     1002                // Ensure late styles are printed.
     1003                add_filter( 'print_late_styles', '__return_false', 1000 );
     1004                $this->assertTrue( apply_filters( 'print_late_styles', true ), 'Expected late style printing to be forced.' );
     1005
     1006                // Simulate wp_head.
     1007                $head_output = get_echo( 'wp_head' );
     1008
     1009                $placeholder_pattern = '#/\*wp_late_styles_placeholder:[a-f0-9-]+\*/#';
     1010
     1011                $this->assertMatchesRegularExpression( $placeholder_pattern, $head_output, 'Expected the placeholder to be present' );
     1012                $this->assertStringContainsString( 'early', $head_output, 'Expected the early-enqueued stylesheet to be present.' );
     1013
     1014                // Enqueue a late style (after wp_head).
     1015                wp_enqueue_style( 'late', 'http://example.com/late-style.css', array(), null );
     1016                wp_add_inline_style( 'late', '/* EARLY */' );
     1017
     1018                // Simulate footer scripts.
     1019                $footer_output = get_echo( 'wp_footer' );
     1020
     1021                // Create a simulated output buffer.
     1022                $buffer = '<html><head>' . $head_output . '</head><body><main>Content</main>' . $footer_output . '</body></html>';
     1023
     1024                // Apply the output buffer filter.
     1025                $filtered_buffer = apply_filters( 'wp_template_enhancement_output_buffer', $buffer );
     1026
     1027                $this->assertDoesNotMatchRegularExpression( $placeholder_pattern, $filtered_buffer, 'Expected the placeholder to be removed.' );
     1028                $found_styles = array(
     1029                        'HEAD' => array(),
     1030                        'BODY' => array(),
     1031                );
     1032                $processor    = WP_HTML_Processor::create_full_parser( $filtered_buffer );
     1033                while ( $processor->next_tag() ) {
     1034                        $group = in_array( 'HEAD', $processor->get_breadcrumbs(), true ) ? 'HEAD' : 'BODY';
     1035                        if (
     1036                                'LINK' === $processor->get_tag() &&
     1037                                $processor->get_attribute( 'rel' ) === 'stylesheet'
     1038                        ) {
     1039                                $found_styles[ $group ][] = $processor->get_attribute( 'id' );
     1040                        } elseif ( 'STYLE' === $processor->get_tag() ) {
     1041                                $found_styles[ $group ][] = $processor->get_attribute( 'id' );
     1042                        }
     1043                }
     1044
     1045                $expected = array(
     1046                        'early-css',
     1047                        'early-inline-css',
     1048                        'late-css',
     1049                        'late-inline-css',
     1050                );
     1051                foreach ( $expected as $style_id ) {
     1052                        $this->assertContains( $style_id, $found_styles['HEAD'], 'Expected stylesheet with ID to be in the HEAD.' );
     1053                }
     1054                $this->assertSame(
     1055                        $expected,
     1056                        array_values( array_intersect( $found_styles['HEAD'], $expected ) ),
     1057                        'Expected styles to be printed in the same order.'
     1058                );
     1059                $this->assertCount( 0, $found_styles['BODY'], 'Expected no styles to be present in the footer.' );
     1060        }
     1061
    8451062        public function assertTemplateHierarchy( $url, array $expected, $message = '' ) {
    8461063                $this->go_to( $url );
  • trunk/tests/phpunit/tests/theme/wpAddGlobalStylesForBlocks.php

    r59256 r61008  
    288288         * @ticket 56915
    289289         * @ticket 61165
     290         *
     291         * @covers ::wp_add_global_styles_for_blocks
    290292         */
    291293        public function test_blocks_inline_styles_get_rendered() {
     294                // Override wp_load_classic_theme_block_styles_on_demand().
     295                add_filter( 'should_load_block_assets_on_demand', '__return_false' ); // Needed for the .wp-block-post-featured-image assertion below.
     296
    292297                $this->set_up_third_party_block();
    293298                wp_register_style( 'global-styles', false, array(), true, true );
     
    312317         * @ticket 57868
    313318         * @ticket 61165
     319         *
     320         * @covers ::wp_add_global_styles_for_blocks
    314321         */
    315322        public function test_third_party_blocks_inline_styles_for_elements_get_rendered_when_per_block() {
    316323                $this->set_up_third_party_block();
    317324                add_filter( 'should_load_separate_core_block_assets', '__return_true' );
     325                $this->assertTrue( wp_should_load_separate_core_block_assets(), 'Core assets are expected to load separately' );
    318326
    319327                wp_register_style( 'global-styles', false, array(), true, true );
  • trunk/tests/phpunit/tests/theme/wpEnqueueStoredStyles.php

    r55567 r61008  
    1111 */
    1212class Tests_Themes_WpEnqueueStoredStyles extends WP_Theme_UnitTestCase {
     13        /**
     14         * @var WP_Scripts|null
     15         */
     16        protected $original_wp_scripts;
     17
     18        /**
     19         * @var WP_Styles|null
     20         */
     21        protected $original_wp_styles;
     22
     23        public function set_up() {
     24                parent::set_up();
     25
     26                global $wp_scripts, $wp_styles;
     27                $this->original_wp_scripts = $wp_scripts;
     28                $this->original_wp_styles  = $wp_styles;
     29                $wp_scripts                = null;
     30                $wp_styles                 = null;
     31                wp_scripts();
     32                wp_styles();
     33        }
     34
     35        public function tear_down() {
     36                global $wp_scripts, $wp_styles;
     37                $wp_scripts = $this->original_wp_scripts;
     38                $wp_styles  = $this->original_wp_styles;
     39
     40                parent::tear_down();
     41        }
    1342
    1443        /**
     
    1645         *
    1746         * @ticket 56467
     47         *
     48         * @covers ::wp_style_engine_get_stylesheet_from_css_rules
     49         * @covers ::wp_enqueue_stored_styles
    1850         */
    1951        public function test_should_enqueue_stored_styles() {
Note: See TracChangeset for help on using the changeset viewer.