From 12b88c188e52b2946975de697eb65ae7df276b1a Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Mon, 25 May 2026 13:36:18 +0530 Subject: [PATCH 1/2] Script Loader: Respect global styles opt-out in classic themes with on-demand assets. Since WordPress 6.9/7.0, classic themes enqueue global styles in the footer for hoisting. Previously common remove_action/wp_dequeue_style calls during wp_enqueue_scripts no longer prevented global-styles-inline-css from being injected into the head. Props edent. Fixes #65336. --- src/wp-includes/script-loader.php | 56 +++++++++++++++++++++++++++++++ tests/phpunit/tests/template.php | 31 ++++++++++++++++- 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 134d86c26a08a..c92f7bd84f9e0 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -2546,6 +2546,9 @@ static function ( $node ) { * @since 5.8.0 */ function wp_enqueue_global_styles() { + static $should_enqueue = true; + static $deferred_check_registered = false; + $assets_on_demand = wp_should_load_block_assets_on_demand(); $is_block_theme = wp_is_block_theme(); $is_classic_theme = ! $is_block_theme; @@ -2576,8 +2579,51 @@ function wp_enqueue_global_styles() { * HEAD, replacing the placeholder. * * @link https://core.trac.wordpress.org/ticket/64099 + * @link https://core.trac.wordpress.org/ticket/65336 */ if ( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) && $assets_on_demand ) { + $should_enqueue = true; + + /* + * Queue the handle early so themes and plugins can dequeue it during wp_enqueue_scripts, even though the + * stylesheet itself is not generated until wp_footer. + */ + wp_enqueue_style( 'global-styles' ); + + if ( ! $deferred_check_registered ) { + $deferred_check_registered = true; + add_action( + 'wp_enqueue_scripts', + static function () use ( &$should_enqueue ) { + if ( false === has_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' ) ) { + $should_enqueue = false; + return; + } + + if ( wp_style_is( 'global-styles', 'registered' ) ) { + $should_enqueue = wp_style_is( 'global-styles', 'enqueued' ); + return; + } + + /* + * The handle was queued before registration. Attempt registration so its enqueue state reflects + * whether it was dequeued during wp_enqueue_scripts. + */ + wp_register_style( 'global-styles', false ); + + if ( wp_style_is( 'global-styles', 'enqueued' ) ) { + wp_dequeue_style( 'global-styles' ); + wp_deregister_style( 'global-styles' ); + $should_enqueue = true; + } else { + wp_deregister_style( 'global-styles' ); + $should_enqueue = false; + } + }, + PHP_INT_MAX + ); + } + if ( has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) { wp_register_style( 'wp-global-styles-placeholder', false ); wp_add_inline_style( 'wp-global-styles-placeholder', ':root { --wp-internal-comment: "Placeholder for wp_hoist_late_printed_styles() to replace with the global-styles printed at wp_footer." }' ); @@ -2586,6 +2632,16 @@ function wp_enqueue_global_styles() { return; } + /* + * When loading block assets on demand in classic themes, global styles are generated in the footer. Respect + * opt-outs that removed the wp_enqueue_scripts callback or dequeued the handle during wp_enqueue_scripts. + */ + if ( doing_action( 'wp_footer' ) && $is_classic_theme && $assets_on_demand ) { + if ( false === has_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' ) || ! $should_enqueue ) { + return; + } + } + /* * If loading the CSS for each block separately, then load the theme.json CSS conditionally. * This removes the CSS from the global-styles stylesheet and adds it to the inline CSS for each block. diff --git a/tests/phpunit/tests/template.php b/tests/phpunit/tests/template.php index a79702554dc64..e02e22f5d29e8 100644 --- a/tests/phpunit/tests/template.php +++ b/tests/phpunit/tests/template.php @@ -1637,7 +1637,7 @@ static function () { $dequeue = static function () { wp_dequeue_style( 'global-styles' ); }; - add_action( 'wp_enqueue_scripts', $dequeue, 1000 ); + add_action( 'wp_enqueue_scripts', $dequeue, 100 ); add_action( 'wp_footer', $dequeue, 2 ); }, 'content' => $blocks_content, @@ -1660,6 +1660,30 @@ static function () { ), ), + 'no_global_styles_via_remove_action' => array( + 'set_up' => static function () { + remove_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' ); + }, + 'content' => $blocks_content, + 'inline_size_limit' => PHP_INT_MAX, + 'expected_styles' => array( + 'HEAD' => array_merge( + $early_common_styles, + array( + 'wp-block-library-inline-css', + 'wp-block-separator-inline-css', + 'classic-theme-styles-inline-css', + 'third-party-test-block-css', + 'custom-block-styles-css', + ), + $common_at_wp_enqueue_scripts, + $common_late_in_head, + $common_late_in_body + ), + 'BODY' => array(), + ), + ), + 'standard_classic_theme_config_extra_block_library_inline_style_none_inlined' => array( 'set_up' => static function () { add_action( @@ -1952,6 +1976,7 @@ static function ( $path, $file ) { * * @ticket 64099 * @ticket 64354 + * @ticket 65336 * @covers ::wp_load_classic_theme_block_styles_on_demand * @covers ::wp_hoist_late_printed_styles * @@ -2122,6 +2147,10 @@ static function () { if ( $assert ) { $assert( $buffer, $filtered_buffer ); } + + if ( false === has_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' ) ) { + add_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' ); + } } /** From b5a5d8cbfdaff977db8db231e42f8e3212b04eab Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Mon, 25 May 2026 13:42:30 +0530 Subject: [PATCH 2/2] Tests: Add @covers for wp_enqueue_global_styles in hoisted styles tests. --- tests/phpunit/tests/template.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/phpunit/tests/template.php b/tests/phpunit/tests/template.php index e02e22f5d29e8..074053eb94707 100644 --- a/tests/phpunit/tests/template.php +++ b/tests/phpunit/tests/template.php @@ -1979,6 +1979,7 @@ static function ( $path, $file ) { * @ticket 65336 * @covers ::wp_load_classic_theme_block_styles_on_demand * @covers ::wp_hoist_late_printed_styles + * @covers ::wp_enqueue_global_styles * * @dataProvider data_wp_hoist_late_printed_styles *