From 57585d1d420749f742bc41bf1547716065a5237e Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Wed, 20 May 2026 10:11:07 +0530 Subject: [PATCH 1/4] Script Loader: Restore init hook for classic theme block styles on demand. Register on-demand block asset filters at init priority 8 again so register_core_block_style_handles() can opt in before WP_Styles is constructed. Keep the wp_default_styles hook from [61981] for early WP_Styles instantiation and guard against duplicate hook registration. Fixes #65272. --- src/wp-includes/default-filters.php | 3 ++- src/wp-includes/script-loader.php | 21 ++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index a4ecf677e2af1..a4758c3c02374 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -606,7 +606,8 @@ add_action( 'enqueue_block_assets', 'wp_enqueue_classic_theme_styles' ); add_action( 'enqueue_block_assets', 'wp_enqueue_registered_block_scripts_and_styles' ); add_action( 'enqueue_block_assets', 'enqueue_block_styles_assets', 30 ); -add_action( 'wp_default_styles', 'wp_load_classic_theme_block_styles_on_demand', 0 ); // Must happen before wp_default_styles() and register_core_block_style_handles(). +add_action( 'init', 'wp_load_classic_theme_block_styles_on_demand', 8 ); // Must happen before register_core_block_style_handles() at priority 9. +add_action( 'wp_default_styles', 'wp_load_classic_theme_block_styles_on_demand', 0 ); // Must happen before wp_default_styles() when WP_Styles is constructed before init priority 9. /* * `wp_enqueue_registered_block_scripts_and_styles` is bound to both * `enqueue_block_editor_assets` and `enqueue_block_assets` hooks diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 134d86c26a08a..ca20e5ded60f2 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -3677,7 +3677,10 @@ function wp_remove_surrounding_empty_script_tags( $contents ) { * the filters are added to cause {@see wp_should_load_separate_core_block_assets()} to return true. * * @since 6.9.0 - * @since 7.0.0 This is now invoked at the `wp_default_styles` action with priority 0 instead of at `init` with priority 8. + * @since 7.0.0 This is also invoked at the `wp_default_styles` action with priority 0 so that filters are present when + * `WP_Styles` is constructed before `init` priority 9. The `init` action at priority 8 is retained so + * `register_core_block_style_handles()` can opt in to separate block assets without having to construct + * `WP_Styles` first. * * @see _add_default_theme_supports() */ @@ -3692,7 +3695,9 @@ function wp_load_classic_theme_block_styles_on_demand(): void { * `wp_template_enhancement_output_buffer` filters added, but do so at priority zero so that applications which * wish to stream responses can more easily turn this off. */ - add_filter( 'wp_should_output_buffer_template_for_enhancement', '__return_true', 0 ); + if ( ! has_filter( 'wp_should_output_buffer_template_for_enhancement', '__return_true' ) ) { + add_filter( 'wp_should_output_buffer_template_for_enhancement', '__return_true', 0 ); + } // If a site has opted out of the template enhancement output buffer, then bail. if ( ! wp_should_output_buffer_template_for_enhancement() ) { @@ -3707,7 +3712,9 @@ function wp_load_classic_theme_block_styles_on_demand(): void { * this to be easily overridden by themes which wish to opt out. If a site has explicitly opted out of loading * separate block styles, then abort. */ - add_filter( 'should_load_separate_core_block_assets', '__return_true', 0 ); + if ( ! has_filter( 'should_load_separate_core_block_assets', '__return_true' ) ) { + add_filter( 'should_load_separate_core_block_assets', '__return_true', 0 ); + } if ( ! wp_should_load_separate_core_block_assets() ) { return; } @@ -3717,13 +3724,17 @@ function wp_load_classic_theme_block_styles_on_demand(): void { * As above, a priority of zero allows for this to be easily overridden by themes which wish to opt out. If a site * has explicitly opted out of loading block styles on demand, then abort. */ - add_filter( 'should_load_block_assets_on_demand', '__return_true', 0 ); + if ( ! has_filter( 'should_load_block_assets_on_demand', '__return_true' ) ) { + add_filter( 'should_load_block_assets_on_demand', '__return_true', 0 ); + } if ( ! wp_should_load_block_assets_on_demand() ) { return; } // Add hooks which require the presence of the output buffer. Ideally the above two filters could be added here, but they run too early. - add_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ); + if ( ! has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) { + add_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ); + } } /** From 0e370e324707c6cd9f8e5c53dc96b577d2687f48 Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Wed, 20 May 2026 10:11:09 +0530 Subject: [PATCH 2/4] Tests: Add regression test for classic theme block theme styles on demand. Props Khokan Sardar. Fixes #65272. --- .../classicThemeBlockStylesOnDemand.php | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 tests/phpunit/tests/blocks/classicThemeBlockStylesOnDemand.php diff --git a/tests/phpunit/tests/blocks/classicThemeBlockStylesOnDemand.php b/tests/phpunit/tests/blocks/classicThemeBlockStylesOnDemand.php new file mode 100644 index 0000000000000..f7fece6f3ba03 --- /dev/null +++ b/tests/phpunit/tests/blocks/classicThemeBlockStylesOnDemand.php @@ -0,0 +1,65 @@ +original_wp_styles = $wp_styles; + } + + public function tear_down() { + global $wp_styles; + $wp_styles = $this->original_wp_styles; + + parent::tear_down(); + } + + /** + * @ticket 65272 + * + * @covers ::wp_load_classic_theme_block_styles_on_demand + * @covers ::register_core_block_style_handles + */ + public function test_register_core_block_style_handles_without_prior_wp_styles() { + global $wp_styles; + $wp_styles = null; + + remove_all_filters( 'should_load_separate_core_block_assets' ); + remove_all_filters( 'should_load_block_assets_on_demand' ); + remove_all_filters( 'wp_should_output_buffer_template_for_enhancement' ); + + remove_all_actions( 'init' ); + remove_all_actions( 'wp_default_styles' ); + + add_action( 'init', 'wp_load_classic_theme_block_styles_on_demand', 8 ); + add_action( 'init', 'register_core_block_style_handles', 9 ); + add_action( 'wp_default_styles', 'wp_load_classic_theme_block_styles_on_demand', 0 ); + add_action( 'wp_default_styles', 'wp_default_styles' ); + + add_theme_support( 'wp-block-styles' ); + + $this->assertFalse( $wp_styles instanceof WP_Styles, 'Expected WP_Styles to not be constructed yet.' ); + $this->assertFalse( wp_should_load_separate_core_block_assets(), 'Expected separate core block assets to be disabled before init.' ); + + do_action( 'init' ); + + $this->assertTrue( wp_should_load_separate_core_block_assets(), 'Expected separate core block assets to be enabled after init.' ); + $this->assertTrue( wp_style_is( 'wp-block-quote-theme', 'registered' ), 'Expected the Quote block theme stylesheet to be registered.' ); + } + +} From 1ee63f26993de45b1c45d455a8361be3e434ce06 Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Wed, 20 May 2026 10:13:22 +0530 Subject: [PATCH 3/4] Tests: Fix PHPCS class closing brace spacing for #65272 regression test. --- tests/phpunit/tests/blocks/classicThemeBlockStylesOnDemand.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/phpunit/tests/blocks/classicThemeBlockStylesOnDemand.php b/tests/phpunit/tests/blocks/classicThemeBlockStylesOnDemand.php index f7fece6f3ba03..15c5f3c709b00 100644 --- a/tests/phpunit/tests/blocks/classicThemeBlockStylesOnDemand.php +++ b/tests/phpunit/tests/blocks/classicThemeBlockStylesOnDemand.php @@ -61,5 +61,4 @@ public function test_register_core_block_style_handles_without_prior_wp_styles() $this->assertTrue( wp_should_load_separate_core_block_assets(), 'Expected separate core block assets to be enabled after init.' ); $this->assertTrue( wp_style_is( 'wp-block-quote-theme', 'registered' ), 'Expected the Quote block theme stylesheet to be registered.' ); } - } From e8c4dabf4c9bfafedf745e15dc1b7268898fdb7f Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Thu, 21 May 2026 12:43:33 +0530 Subject: [PATCH 4/4] Script Loader: Use strict has_filter/has_action checks for on-demand hooks. has_filter() and has_action() return the priority when a callback is registered, so priority 0 must be compared with false === instead of !. Adds regression tests for duplicate hook registration and for #64846 when WP_Styles is constructed before init. Props westonruter. Fixes #65272. --- src/wp-includes/script-loader.php | 8 +-- .../classicThemeBlockStylesOnDemand.php | 64 +++++++++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index ca20e5ded60f2..3ce18d4429236 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -3695,7 +3695,7 @@ function wp_load_classic_theme_block_styles_on_demand(): void { * `wp_template_enhancement_output_buffer` filters added, but do so at priority zero so that applications which * wish to stream responses can more easily turn this off. */ - if ( ! has_filter( 'wp_should_output_buffer_template_for_enhancement', '__return_true' ) ) { + if ( false === has_filter( 'wp_should_output_buffer_template_for_enhancement', '__return_true' ) ) { add_filter( 'wp_should_output_buffer_template_for_enhancement', '__return_true', 0 ); } @@ -3712,7 +3712,7 @@ function wp_load_classic_theme_block_styles_on_demand(): void { * this to be easily overridden by themes which wish to opt out. If a site has explicitly opted out of loading * separate block styles, then abort. */ - if ( ! has_filter( 'should_load_separate_core_block_assets', '__return_true' ) ) { + if ( false === has_filter( 'should_load_separate_core_block_assets', '__return_true' ) ) { add_filter( 'should_load_separate_core_block_assets', '__return_true', 0 ); } if ( ! wp_should_load_separate_core_block_assets() ) { @@ -3724,7 +3724,7 @@ function wp_load_classic_theme_block_styles_on_demand(): void { * As above, a priority of zero allows for this to be easily overridden by themes which wish to opt out. If a site * has explicitly opted out of loading block styles on demand, then abort. */ - if ( ! has_filter( 'should_load_block_assets_on_demand', '__return_true' ) ) { + if ( false === has_filter( 'should_load_block_assets_on_demand', '__return_true' ) ) { add_filter( 'should_load_block_assets_on_demand', '__return_true', 0 ); } if ( ! wp_should_load_block_assets_on_demand() ) { @@ -3732,7 +3732,7 @@ function wp_load_classic_theme_block_styles_on_demand(): void { } // Add hooks which require the presence of the output buffer. Ideally the above two filters could be added here, but they run too early. - if ( ! has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) { + if ( false === has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) { add_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ); } } diff --git a/tests/phpunit/tests/blocks/classicThemeBlockStylesOnDemand.php b/tests/phpunit/tests/blocks/classicThemeBlockStylesOnDemand.php index 15c5f3c709b00..ceaa1c35097cf 100644 --- a/tests/phpunit/tests/blocks/classicThemeBlockStylesOnDemand.php +++ b/tests/phpunit/tests/blocks/classicThemeBlockStylesOnDemand.php @@ -61,4 +61,68 @@ public function test_register_core_block_style_handles_without_prior_wp_styles() $this->assertTrue( wp_should_load_separate_core_block_assets(), 'Expected separate core block assets to be enabled after init.' ); $this->assertTrue( wp_style_is( 'wp-block-quote-theme', 'registered' ), 'Expected the Quote block theme stylesheet to be registered.' ); } + + /** + * @ticket 65272 + * + * @covers ::wp_load_classic_theme_block_styles_on_demand + */ + public function test_wp_load_classic_theme_block_styles_on_demand_does_not_duplicate_hooks() { + switch_theme( 'default' ); + + remove_all_filters( 'should_load_separate_core_block_assets' ); + remove_all_filters( 'should_load_block_assets_on_demand' ); + remove_all_filters( 'wp_should_output_buffer_template_for_enhancement' ); + remove_all_actions( 'wp_template_enhancement_output_buffer_started' ); + + wp_load_classic_theme_block_styles_on_demand(); + wp_load_classic_theme_block_styles_on_demand(); + + global $wp_filter; + + $this->assertSame( 0, has_filter( 'should_load_separate_core_block_assets', '__return_true' ) ); + $this->assertCount( 1, $wp_filter['should_load_separate_core_block_assets']->callbacks[0] ); + $this->assertCount( 1, $wp_filter['should_load_block_assets_on_demand']->callbacks[0] ); + $this->assertCount( 1, $wp_filter['wp_should_output_buffer_template_for_enhancement']->callbacks[0] ); + $this->assertCount( 1, $wp_filter['wp_template_enhancement_output_buffer_started']->callbacks[10] ); + } + + /** + * @ticket 64846 + * @ticket 65272 + * + * @covers ::wp_load_classic_theme_block_styles_on_demand + * @covers ::wp_default_styles + */ + public function test_wp_block_library_uses_common_css_when_wp_styles_constructed_before_init() { + global $wp_styles; + $wp_styles = null; + + remove_all_filters( 'should_load_separate_core_block_assets' ); + remove_all_filters( 'should_load_block_assets_on_demand' ); + remove_all_filters( 'wp_should_output_buffer_template_for_enhancement' ); + + remove_all_actions( 'init' ); + remove_all_actions( 'wp_default_styles' ); + + add_action( 'init', 'wp_load_classic_theme_block_styles_on_demand', 8 ); + add_action( 'init', 'register_core_block_style_handles', 9 ); + add_action( 'wp_default_styles', 'wp_load_classic_theme_block_styles_on_demand', 0 ); + add_action( 'wp_default_styles', 'wp_default_styles' ); + + add_theme_support( 'wp-block-styles' ); + + wp_styles(); + + $this->assertSame( + '/' . WPINC . '/css/dist/block-library/common.css', + wp_styles()->registered['wp-block-library']->src, + 'Expected wp-block-library to use common.css when separate core block assets are enabled.' + ); + + do_action( 'init' ); + + $this->assertTrue( wp_should_load_separate_core_block_assets(), 'Expected separate core block assets to remain enabled after init.' ); + $this->assertTrue( wp_style_is( 'wp-block-quote-theme', 'registered' ), 'Expected the Quote block theme stylesheet to be registered after init.' ); + } }