Skip to content

Commit 5b28781

Browse files
committed
Script Loader: Improve hoisted stylesheet ordering (in classic themes) to preserve CSS cascade.
This ensures that on-demand block styles are inserted right after the `wp-block-library` inline style whereas other stylesheets not related to blocks are appended to the end of the `HEAD`. This helps ensure the expected cascade is preserved. If no `wp-block-library` inline style is present, then all styles get appended to the `HEAD` regardless. The handling of the CSS placeholder comment added to the `wp-block-library` inline style is also improved. It is now inserted later to ensure the inline style is printed. Additionally, when the CSS placeholder comment is removed from the `wp-block-library` inline style, the entire `STYLE` tag is now removed if there are no styles left (aside from the `sourceURL` comment). Lastly, the use of the HTML Tag Processor is significantly improved to leverage `WP_HTML_Text_Replacement`. Developed in #10436 Follow-up to [61008]. Props westonruter, peterwilsoncc, dmsnell. Fixes #64099. git-svn-id: https://develop.svn.wordpress.org/trunk@61174 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 98b12d1 commit 5b28781

File tree

3 files changed

+489
-110
lines changed

3 files changed

+489
-110
lines changed

phpcompat.xml.dist

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,12 @@
114114
<exclude-pattern>/sodium_compat/src/PHP52/SplFixedArray\.php$</exclude-pattern>
115115
</rule>
116116

117+
<!--
118+
Excluded while waiting for PHPCompatibility v10.
119+
See <https://github.com/PHPCompatibility/PHPCompatibility/issues/1481>.
120+
-->
121+
<rule ref="PHPCompatibility.FunctionDeclarations.NewClosure.ThisFoundInStatic">
122+
<exclude-pattern>/src/wp-includes/script-loader\.php$</exclude-pattern>
123+
</rule>
124+
117125
</ruleset>

src/wp-includes/script-loader.php

Lines changed: 145 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2264,10 +2264,14 @@ function wp_print_head_scripts() {
22642264
/**
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.
2267+
* In classic themes, when block styles are loaded on demand via wp_load_classic_theme_block_styles_on_demand(),
2268+
* this function is replaced by a closure in wp_hoist_late_printed_styles() which will capture the printing of
2269+
* two sets of "late" styles to be hoisted to the HEAD by means of the template enhancement output buffer:
2270+
*
2271+
* 1. Styles related to blocks are inserted right after the wp-block-library stylesheet.
2272+
* 2. All other styles are appended to the end of the HEAD.
2273+
*
2274+
* The closure calls print_footer_scripts() to print scripts in the footer as usual.
22712275
*
22722276
* @since 3.3.0
22732277
*/
@@ -3601,20 +3605,23 @@ function wp_load_classic_theme_block_styles_on_demand() {
36013605
// The following two filters are added by default for block themes in _add_default_theme_supports().
36023606

36033607
/*
3604-
* Load separate block styles so that the large block-library stylesheet is not enqueued unconditionally,
3605-
* and so that block-specific styles will only be enqueued when they are used on the page.
3606-
* A priority of zero allows for this to be easily overridden by themes which wish to opt out.
3608+
* Load separate block styles so that the large block-library stylesheet is not enqueued unconditionally, and so
3609+
* that block-specific styles will only be enqueued when they are used on the page. A priority of zero allows for
3610+
* this to be easily overridden by themes which wish to opt out. If a site has explicitly opted out of loading
3611+
* separate block styles, then abort.
36073612
*/
36083613
add_filter( 'should_load_separate_core_block_assets', '__return_true', 0 );
3614+
if ( ! wp_should_load_separate_core_block_assets() ) {
3615+
return;
3616+
}
36093617

36103618
/*
36113619
* Also ensure that block assets are loaded on demand (although the default value is from should_load_separate_core_block_assets).
3612-
* As above, a priority of zero allows for this to be easily overridden by themes which wish to opt out.
3620+
* As above, a priority of zero allows for this to be easily overridden by themes which wish to opt out. If a site
3621+
* has explicitly opted out of loading block styles on demand, then abort.
36133622
*/
36143623
add_filter( 'should_load_block_assets_on_demand', '__return_true', 0 );
3615-
3616-
// If a site has explicitly opted out of loading block styles on demand via filters with priorities higher than above, then abort.
3617-
if ( ! wp_should_load_separate_core_block_assets() || ! wp_should_load_block_assets_on_demand() ) {
3624+
if ( ! wp_should_load_block_assets_on_demand() ) {
36183625
return;
36193626
}
36203627

@@ -3637,37 +3644,73 @@ function wp_hoist_late_printed_styles() {
36373644
}
36383645

36393646
/*
3640-
* While normally late styles are printed, there is a filter to disable prevent this, so this makes sure they are
3641-
* printed. Note that this filter was intended to control whether to print the styles queued too late for the HTML
3642-
* head. This filter was introduced in <https://core.trac.wordpress.org/ticket/9346>. However, with the template
3643-
* enhancement output buffer, essentially no style can be enqueued too late, because an output buffer filter can
3644-
* always hoist it to the HEAD.
3647+
* Add a placeholder comment into the inline styles for wp-block-library, after which where the late block styles
3648+
* can be hoisted from the footer to be printed in the header by means of a filter below on the template enhancement
3649+
* output buffer. The `wp_print_styles` action is used to ensure that if the inline style gets replaced at
3650+
* `enqueue_block_assets` or `wp_enqueue_scripts` that the placeholder will be sure to be present.
36453651
*/
3646-
add_filter( 'print_late_styles', '__return_true', PHP_INT_MAX );
3652+
$placeholder = sprintf( '/*%s*/', uniqid( 'wp_block_styles_on_demand_placeholder:' ) );
3653+
add_action(
3654+
'wp_print_styles',
3655+
static function () use ( $placeholder ) {
3656+
wp_add_inline_style( 'wp-block-library', $placeholder );
3657+
}
3658+
);
36473659

36483660
/*
3649-
* Print a placeholder comment where the late styles can be hoisted from the footer to be printed in the header
3650-
* by means of a filter below on the template enhancement output buffer.
3661+
* Create a substitute for `print_late_styles()` which is aware of block styles. This substitute does not print
3662+
* the styles, but it captures what would be printed for block styles and non-block styles so that they can be
3663+
* later hoisted to the HEAD in the template enhancement output buffer. This will run at `wp_print_footer_scripts`
3664+
* before `print_footer_scripts()` is called.
36513665
*/
3652-
$placeholder = sprintf( '/*%s*/', uniqid( 'wp_late_styles_placeholder:' ) );
3666+
$printed_block_styles = '';
3667+
$printed_late_styles = '';
3668+
$capture_late_styles = static function () use ( &$printed_block_styles, &$printed_late_styles ) {
3669+
// Gather the styles related to on-demand block enqueues.
3670+
$all_block_style_handles = array();
3671+
foreach ( WP_Block_Type_Registry::get_instance()->get_all_registered() as $block_type ) {
3672+
foreach ( $block_type->style_handles as $style_handle ) {
3673+
$all_block_style_handles[] = $style_handle;
3674+
}
3675+
}
3676+
$all_block_style_handles = array_merge(
3677+
$all_block_style_handles,
3678+
array(
3679+
'global-styles',
3680+
'block-style-variation-styles',
3681+
'core-block-supports',
3682+
'core-block-supports-duotone',
3683+
)
3684+
);
36533685

3654-
wp_add_inline_style( 'wp-block-library', $placeholder );
3686+
/*
3687+
* First print all styles related to blocks which should inserted right after the wp-block-library stylesheet
3688+
* to preserve the CSS cascade. The logic in this `if` statement is derived from `wp_print_styles()`.
3689+
*/
3690+
$enqueued_block_styles = array_values( array_intersect( $all_block_style_handles, wp_styles()->queue ) );
3691+
if ( count( $enqueued_block_styles ) > 0 ) {
3692+
ob_start();
3693+
wp_styles()->do_items( $enqueued_block_styles );
3694+
$printed_block_styles = ob_get_clean();
3695+
}
36553696

3656-
// Wrap print_late_styles() with a closure that captures the late-printed styles.
3657-
$printed_late_styles = '';
3658-
$capture_late_styles = static function () use ( &$printed_late_styles ) {
3697+
/*
3698+
* Print all remaining styles not related to blocks. This contains a subset of the logic from
3699+
* `print_late_styles()`, without admin-specific logic and the `print_late_styles` filter to control whether
3700+
* late styles are printed (since they are being hoisted anyway).
3701+
*/
36593702
ob_start();
3660-
print_late_styles();
3703+
wp_styles()->do_footer_items();
36613704
$printed_late_styles = ob_get_clean();
36623705
};
36633706

36643707
/*
3665-
* If _wp_footer_scripts() was unhooked from the wp_print_footer_scripts action, or if wp_print_footer_scripts()
3666-
* was unhooked from running at the wp_footer action, then only add a callback to wp_footer which will capture the
3708+
* If `_wp_footer_scripts()` was unhooked from the `wp_print_footer_scripts` action, or if `wp_print_footer_scripts()`
3709+
* was unhooked from running at the `wp_footer` action, then only add a callback to `wp_footer` which will capture the
36673710
* late-printed styles.
36683711
*
3669-
* Otherwise, in the normal case where _wp_footer_scripts() will run at the wp_print_footer_scripts action, then
3670-
* swap out _wp_footer_scripts() with an alternative which captures the printed styles (for hoisting to HEAD) before
3712+
* Otherwise, in the normal case where `_wp_footer_scripts()` will run at the `wp_print_footer_scripts` action, then
3713+
* swap out `_wp_footer_scripts()` with an alternative which captures the printed styles (for hoisting to HEAD) before
36713714
* proceeding with printing the footer scripts.
36723715
*/
36733716
$wp_print_footer_scripts_priority = has_action( 'wp_print_footer_scripts', '_wp_footer_scripts' );
@@ -3689,65 +3732,99 @@ static function () use ( $capture_late_styles ) {
36893732
// Replace placeholder with the captured late styles.
36903733
add_filter(
36913734
'wp_template_enhancement_output_buffer',
3692-
function ( $buffer ) use ( $placeholder, &$printed_late_styles ) {
3735+
static function ( $buffer ) use ( $placeholder, &$printed_block_styles, &$printed_late_styles ) {
36933736

36943737
// Anonymous subclass of WP_HTML_Tag_Processor which exposes underlying bookmark spans.
36953738
$processor = new class( $buffer ) extends WP_HTML_Tag_Processor {
3696-
public function get_span(): WP_HTML_Span {
3697-
$instance = $this; // phpcs:ignore PHPCompatibility.FunctionDeclarations.NewClosure.ThisFoundOutsideClass -- It is inside an anonymous class.
3698-
$instance->set_bookmark( 'here' );
3699-
return $instance->bookmarks['here'];
3739+
/**
3740+
* Gets the span for the current token.
3741+
*
3742+
* @return WP_HTML_Span Current token span.
3743+
*/
3744+
private function get_span(): WP_HTML_Span {
3745+
// Note: This call will never fail according to the usage of this class, given it is always called after ::next_tag() is true.
3746+
$this->set_bookmark( 'here' );
3747+
return $this->bookmarks['here'];
3748+
}
3749+
3750+
/**
3751+
* Inserts text before the current token.
3752+
*
3753+
* @param string $text Text to insert.
3754+
*/
3755+
public function insert_before( string $text ) {
3756+
$this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->get_span()->start, 0, $text );
3757+
}
3758+
3759+
/**
3760+
* Inserts text after the current token.
3761+
*
3762+
* @param string $text Text to insert.
3763+
*/
3764+
public function insert_after( string $text ) {
3765+
$span = $this->get_span();
3766+
3767+
$this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start + $span->length, 0, $text );
3768+
}
3769+
3770+
/**
3771+
* Removes the current token.
3772+
*/
3773+
public function remove() {
3774+
$span = $this->get_span();
3775+
3776+
$this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, '' );
37003777
}
37013778
};
37023779

3703-
// Loop over STYLE tags.
3780+
/*
3781+
* Insert block styles right after wp-block-library (if it is present), and then insert any remaining styles
3782+
* at </head> (or else print everything there). The placeholder CSS comment will always be added to the
3783+
* wp-block-library inline style since it gets printed at `wp_head` before the blocks are rendered.
3784+
* This means that there may not actually be any block styles to hoist from the footer to insert after this
3785+
* inline style. The placeholder CSS comment needs to be added so that the inline style gets printed, but
3786+
* if the resulting inline style is empty after the placeholder is removed, then the inline style is
3787+
* removed.
3788+
*/
37043789
while ( $processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
3705-
3706-
// We've encountered the inline style for the 'wp-block-library' stylesheet which probably has the placeholder comment.
37073790
if (
3708-
! $processor->is_tag_closer() &&
37093791
'STYLE' === $processor->get_tag() &&
37103792
'wp-block-library-inline-css' === $processor->get_attribute( 'id' )
37113793
) {
3712-
// If the inline style lacks the placeholder comment, then we have to continue until we get to </HEAD> to append the styles there.
37133794
$css_text = $processor->get_modifiable_text();
3714-
if ( ! str_contains( $css_text, $placeholder ) ) {
3715-
continue;
3716-
}
37173795

3718-
// Remove the placeholder now that we've located the inline style.
3719-
$processor->set_modifiable_text( str_replace( $placeholder, '', $css_text ) );
3720-
$buffer = $processor->get_updated_html();
3796+
/*
3797+
* A placeholder CSS comment is added to the inline style in order to force an inline STYLE tag to
3798+
* be printed. Now that we've located the inline style, the placeholder comment can be removed. If
3799+
* there is no CSS left in the STYLE tag after removing the placeholder (aside from the sourceURL
3800+
* comment, then remove the STYLE entirely.)
3801+
*/
3802+
$css_text = str_replace( $placeholder, '', $css_text );
3803+
if ( preg_match( ':^/\*# sourceURL=\S+? \*/$:', trim( $css_text ) ) ) {
3804+
$processor->remove();
3805+
} else {
3806+
$processor->set_modifiable_text( $css_text );
3807+
}
37213808

37223809
// Insert the $printed_late_styles immediately after the closing inline STYLE tag. This preserves the CSS cascade.
3723-
$span = $processor->get_span();
3724-
$buffer = implode(
3725-
'',
3726-
array(
3727-
substr( $buffer, 0, $span->start + $span->length ),
3728-
$printed_late_styles,
3729-
substr( $buffer, $span->start + $span->length ),
3730-
)
3731-
);
3732-
break;
3733-
}
3810+
if ( '' !== $printed_block_styles ) {
3811+
$processor->insert_after( $printed_block_styles );
37343812

3735-
// As a fallback, append the hoisted late styles to the end of the HEAD.
3736-
if ( $processor->is_tag_closer() && 'HEAD' === $processor->get_tag() ) {
3737-
$span = $processor->get_span();
3738-
$buffer = implode(
3739-
'',
3740-
array(
3741-
substr( $buffer, 0, $span->start ),
3742-
$printed_late_styles,
3743-
substr( $buffer, $span->start ),
3744-
)
3745-
);
3813+
// Prevent printing them again at </head>.
3814+
$printed_block_styles = '';
3815+
}
3816+
3817+
// If there aren't any late styles, there's no need to continue to finding </head>.
3818+
if ( '' === $printed_late_styles ) {
3819+
break;
3820+
}
3821+
} elseif ( 'HEAD' === $processor->get_tag() && $processor->is_tag_closer() ) {
3822+
$processor->insert_before( $printed_block_styles . $printed_late_styles );
37463823
break;
37473824
}
37483825
}
37493826

3750-
return $buffer;
3827+
return $processor->get_updated_html();
37513828
}
37523829
);
37533830
}

0 commit comments

Comments
 (0)