Skip to content

Block Supports: Bail early in state styles when a block has no style attribute - #77

Draft
mukeshpanchal27 wants to merge 4 commits into
trunkfrom
perf/block-states-early-return
Draft

Block Supports: Bail early in state styles when a block has no style attribute#77
mukeshpanchal27 wants to merge 4 commits into
trunkfrom
perf/block-states-early-return

Conversation

@mukeshpanchal27

Copy link
Copy Markdown
Owner

What

Adds an early return to wp_render_block_states_support() when the block has no style attribute.

Why

wp_render_block_states_support() is registered on render_block:

add_filter( 'render_block', 'wp_render_block_states_support', 10, 2 );

so it executes once per block, per request. Before it can determine that a block has no state styles, it does all of this unconditionally:

$block_type               = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
$supported_pseudo_states  = WP_Theme_JSON::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ?? array();
$style                    = $block['attrs']['style'] ?? array();
$css_rules                = array();
$viewport_settings        = wp_get_global_settings( array( 'viewport' ) );
$responsive_media_queries = WP_Theme_JSON::get_viewport_media_queries( $viewport_settings );

The overwhelming majority of blocks on a page carry no style attribute, so all of this is discarded.

The viewport lookup is particularly costly. Core's theme.json defines no viewport key, and wp_get_global_settings() ends with _wp_array_get( $settings, $path, $settings ) — so a missing path returns the entire settings array. That array is then handed to sanitize_viewport_settings(), which finds no mobile/tablet members, discards it, and rebuilds DEFAULT_VIEWPORT_BREAKPOINTS, running two preg_match validations and building two media-query strings — for every block, on every request.

How

/*
 * Every CSS rule this function can produce is keyed off the block's `style`
 * attribute. Without it there is nothing to generate, so bail before doing
 * any of the lookups below — this runs for every block on every request.
 */
$style = $block['attrs']['style'] ?? array();
if ( empty( $style ) || ! is_array( $style ) ) {
	return $block_content;
}

$style is moved above the registry lookup and the guard added. Nothing else changes.

Why this is safe

Every write into $css_rules in this function is keyed off $style:

  • the pseudo-state loop guards on empty( $style[ $pseudo_state ] )
  • the responsive loop guards on empty( $style[ $breakpoint ] )
  • the element and nested pseudo-state loops read from $style[ $breakpoint ][...]

and the function already short-circuits with the content unchanged when nothing was produced:

if ( empty( $css_rules ) ) {
	return $block_content;
}

So when $style is absent, the pre-existing code path was guaranteed to fall through to that return. The new guard reaches the same outcome without the intervening work.

Testing

Existing suites — all green (MySQL 8.4, PHP 8.3.2):

Suite Result
tests/phpunit/tests/block-supports/states.php OK (46 tests, 72 assertions)
all of tests/phpunit/tests/block-supports/ OK — including layout (53), typography (81), duotone (30), block-visibility (14)
all of tests/phpunit/tests/blocks/ OK (999 tests)

states.php was also run against unmodified trunk first to confirm the 46 tests were green before the change, not just after.

Output equivalence. Ran a fixture matrix through wp_render_block_states_support() on trunk and on this branch, comparing md5 of the returned HTML:

Fixture Trunk This PR
core/button with :hover 985cbd40… 985cbd40…
core/button with @mobile 14e0f950… 14e0f950…
core/navigation-link with :focus 4e7eb3ad… 4e7eb3ad…
core/paragraph styled bdac64ce… bdac64ce…
empty style array bdac64ce… bdac64ce…
no attrs bdac64ce… bdac64ce…
unregistered block type bdac64ce… bdac64ce…

Byte-identical in every case.

Performance. Measured inside a booted WordPress, 20k iterations, warmed, core/paragraph with no style attribute:

ns/block per 1000 blocks
trunk 1396.6 ns 1.397 ms
this PR 100.4 ns 0.100 ms

~1.30 µs saved per style-less block (~14× on this path).

Context

This came out of a 7.0.4 → 7.1 RC3 benchmark comparison showing a server-side regression on both Block and Classic themes (Block wp-total p50 +9.88% / +10.53 ms; Classic +7.92% / +3.99 ms), with LCP − TTFB flat on both — i.e. entirely PHP execution time, not front-end or database. This filter was the largest single contributor identified.

Related follow-ups worth separate PRs, not included here to keep this reviewable:

  • Hoist the viewport computation — wp_get_global_settings( array( 'viewport' ) ) and get_viewport_media_queries() return the same value for every block in a request and could be memoized once.
  • Fix the _wp_array_get() fallback in wp_get_global_settings() so a missing path returns null rather than the whole settings tree. This looks like a latent correctness bug independent of performance.
  • _wp_register_default_icons() registers 88 icons plus 88 _x() calls on init for every front-end request (~0.10 ms), none of which is used to render a front-end page.

mukeshpanchal27 and others added 4 commits August 14, 2026 15:06
…attribute.

`wp_render_block_states_support()` is registered on `render_block`, so it runs
once per block on every request. Before determining that a block has no state
styles, it performed a block type registry lookup, resolved global settings via
`wp_get_global_settings()`, and computed responsive media queries through
`WP_Theme_JSON::get_viewport_media_queries()`.

Every CSS rule the function can produce is keyed off the block's `style`
attribute, and the function already returns the content unchanged when no rules
are generated. Bailing out as soon as `style` is absent is therefore
behaviour-preserving, and skips that work for the overwhelming majority of
blocks, which carry no `style` attribute at all.

Measured on trunk with a `core/paragraph` block that has no `style` attribute:
1396.6 ns/block before, 100.4 ns/block after. Output verified byte-identical
across styled and unstyled fixtures for `core/button`, `core/navigation-link`,
and `core/paragraph`, plus empty-style, no-attrs, and unregistered-block cases.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Updates the prompt builder constructor and proxied method calls to catch all `Throwable` instances. This ensures errors such as `TypeError` are converted to `WP_Error` objects instead of causing fatal errors.

Includes regression tests for throwable conversion and fluent method forwarding behavior.

Props khokansardar, gziolo, alaminfirdows, shanemuir, vedantere, r1k0, waneezashafiq32, ugyensupport, dhavalkapadane.
Fixes #65505.





git-svn-id: https://develop.svn.wordpress.org/trunk@63346 602fd350-edb4-49c9-b593-d223f7449a82
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants