Make WordPress Core

Changeset 62656


Ignore:
Timestamp:
07/07/2026 12:49:04 PM (5 weeks ago)
Author:
wildworks
Message:

Block Supports: Fix duplicate custom CSS for identical attributes.

wp_render_custom_css_support_styles() runs on every block render, and wp_unique_id_from_values() derives the class name from the block's attributes. Blocks with identical attributes therefore share the same processed CSS, which wp_add_inline_style() re-enqueued on each render, emitting the same rule once per occurrence.

Before enqueuing, skip the CSS if it is already present in the wp-block-custom-css handle's after inline styles`.

Developed in: https://github.com/WordPress/wordpress-develop/pull/11859.

Props masteradhoc, mukesh27, mustafabharmal, westonruter, wildworks.
Fixes #65268.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/block-supports/custom-css.php

    r62475 r62656  
    2626 *     ...
    2727 * } $parsed_block
     28 * @phpstan-return array{
     29 *     blockName: string|null,
     30 *     attrs: array{
     31 *         className?: string,
     32 *         style?: array{
     33 *             css?: string,
     34 *             ...
     35 *         },
     36 *         ...
     37 *     },
     38 *     ...
     39 * }
    2840 */
    2941function wp_render_custom_css_support_styles( $parsed_block ) {
     
    5062                : $class_name;
    5163
    52         _wp_array_set( $parsed_block, array( 'attrs', 'className' ), $updated_class_name );
     64        $parsed_block['attrs']['className'] = $updated_class_name;
    5365
    5466        // Process the custom CSS using the same method as global styles.
     
    5769
    5870        if ( ! empty( $processed_css ) ) {
    59                 /*
    60                  * Register and add inline style for block custom CSS.
    61                  * The style depends on global-styles to ensure custom CSS loads after
    62                  * and can override global styles.
     71                /**
     72                 * Skip CSS that has already been added. Blocks with identical attributes
     73                 * share the same class name and processed CSS via {@see wp_unique_id_from_values()},
     74                 * so the same style would otherwise be enqueued more than once (e.g. inside
     75                 * a Query Loop or when blocks share identical custom CSS).
    6376                 */
    64                 wp_register_style( 'wp-block-custom-css', false, array( 'global-styles' ) );
    65                 wp_add_inline_style( 'wp-block-custom-css', $processed_css );
     77                $handle = 'wp-block-custom-css';
     78                if ( ! wp_style_is( $handle, 'registered' ) ) {
     79                        wp_register_style( $handle, false, array( 'global-styles' ) );
     80                }
     81                $after_styles = wp_styles()->get_data( $handle, 'after' );
     82                if ( ! is_array( $after_styles ) ) {
     83                        $after_styles = array();
     84                }
     85                if ( ! in_array( $processed_css, $after_styles, true ) ) {
     86                        wp_add_inline_style( $handle, $processed_css );
     87                }
    6688        }
    6789
  • trunk/tests/phpunit/tests/block-supports/wpRenderCustomCssSupportStyles.php

    r61678 r62656  
    1515                parent::set_up();
    1616                $this->test_block_name = null;
     17
     18                global $wp_styles;
     19                $wp_styles = null;
    1720        }
    1821
     
    2225                }
    2326                $this->test_block_name = null;
     27
     28                global $wp_styles;
     29                $wp_styles = null;
     30
    2431                parent::tear_down();
    2532        }
     
    265272                );
    266273        }
     274
     275        /**
     276         * Tests that CSS is enqueued only once when the same block is rendered
     277         * multiple times, as happens inside a Query Loop.
     278         *
     279         * @ticket 65268
     280         *
     281         * @covers ::wp_render_custom_css_support_styles
     282         */
     283        public function test_css_not_duplicated_on_repeated_renders(): void {
     284                $this->test_block_name = 'test/custom-css-query-loop-dedup';
     285                register_block_type(
     286                        $this->test_block_name,
     287                        array(
     288                                'api_version' => 3,
     289                                'attributes'  => array(
     290                                        'style' => array(
     291                                                'type' => 'object',
     292                                        ),
     293                                ),
     294                                'supports'    => array( 'customCSS' => true ),
     295                        )
     296                );
     297
     298                $parsed_block = array(
     299                        'blockName' => 'test/custom-css-query-loop-dedup',
     300                        'attrs'     => array(
     301                                'style' => array(
     302                                        'css' => 'font-size: 2em; /* query-loop-dedup-test */',
     303                                ),
     304                        ),
     305                );
     306
     307                // Simulate the same block being rendered multiple times inside a Query Loop.
     308                $result = wp_render_custom_css_support_styles( $parsed_block );
     309                wp_render_custom_css_support_styles( $parsed_block );
     310                wp_render_custom_css_support_styles( $parsed_block );
     311
     312                // Extract the generated class name from the first render's result.
     313                $this->assertSame( 1, preg_match( '/(?:^|\s)(wp-custom-css-\S+)/', $result['attrs']['className'] ?? '', $matches ) );
     314                $class_name = $matches[1];
     315
     316                // Count how many times the CSS selector for this block appears in the enqueued inline styles.
     317                $inline_styles = (array) wp_styles()->get_data( 'wp-block-custom-css', 'after' );
     318                $occurrences   = 0;
     319                foreach ( $inline_styles as $style ) {
     320                        $this->assertIsString( $style );
     321                        $occurrences += substr_count( $style, '.' . $class_name );
     322                }
     323
     324                $this->assertSame(
     325                        1,
     326                        $occurrences,
     327                        'CSS should be enqueued exactly once even when the same block renders multiple times.'
     328                );
     329        }
    267330}
Note: See TracChangeset for help on using the changeset viewer.