Make WordPress Core

Changeset 62520


Ignore:
Timestamp:
06/18/2026 12:09:23 AM (7 weeks ago)
Author:
westonruter
Message:

Editor: Fix wp-elements-* CSS class name collisions for identical blocks.

The wp_get_elements_class_name() function previously generated CSS class names by hashing the serialized block data via md5(). Identical blocks received the same wp-elements-* class name and the Style Engine deduplicated their CSS rules into one, causing a parent block's element style (e.g. link color) to cascade down and override a child block's identical style due to CSS source order.

The function is updated to use wp_unique_prefixed_id() instead, generating sequential unique class names (wp-elements-1, wp-elements-2, etc.) that match the block editor's JavaScript implementation. The now-unused $parsed_block parameter is removed from the function signature.

PHPStan rule level 10 errors are also resolved in the related code. See #64898.

Developed in https://github.com/WordPress/wordpress-develop/pull/12126.
Follow-up to r53260, r58074.

Props tusharbharti, westonruter, wildworks.
Fixes #65435.

Location:
trunk
Files:
3 edited

Legend:

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

    r62475 r62520  
    1313 * @access private
    1414 *
    15  * @param array $block Block object.
    1615 * @return string The unique class name.
    1716 */
    18 function wp_get_elements_class_name( $block ) {
    19         return 'wp-elements-' . md5( serialize( $block ) );
     17function wp_get_elements_class_name(): string {
     18        return wp_unique_prefixed_id( 'wp-elements-' );
    2019}
    2120
     
    110109 * @param array $parsed_block The parsed block.
    111110 * @return array The same parsed block with elements classname added if appropriate.
     111 *
     112 * @phpstan-param array{
     113 *     blockName: string,
     114 *     attrs: array{
     115 *         className?: string,
     116 *         style?: array{
     117 *             elements?: array<string, array{
     118 *                 ":hover"?: array<string, string>,
     119 *                 ...
     120 *             }>,
     121 *         },
     122 *         ...
     123 *     },
     124 *     ...
     125 * } $parsed_block
     126 * @phpstan-return array{
     127 *     blockName: string,
     128 *     attrs: array{
     129 *         className?: string,
     130 *         ...
     131 *     },
     132 *     ...
     133 * }
    112134 */
    113135function wp_render_elements_support_styles( $parsed_block ) {
     
    130152        }
    131153
    132         $block_type           = WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] );
     154        $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] );
     155        if ( ! $block_type ) {
     156                return $parsed_block;
     157        }
     158
    133159        $element_block_styles = $parsed_block['attrs']['style']['elements'] ?? null;
    134 
    135160        if ( ! $element_block_styles ) {
    136161                return $parsed_block;
     
    158183        }
    159184
    160         $class_name         = wp_get_elements_class_name( $parsed_block );
     185        $class_name         = wp_get_elements_class_name();
    161186        $updated_class_name = isset( $parsed_block['attrs']['className'] ) ? $parsed_block['attrs']['className'] . " $class_name" : $class_name;
    162187
     
    198223                        );
    199224
    200                         if ( isset( $element_style_object[':hover'] ) ) {
     225                        if ( isset( $element_style_object[':hover'], $element_config['hover_selector'] ) ) {
    201226                                wp_style_engine_get_styles(
    202227                                        $element_style_object[':hover'],
  • trunk/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php

    r62475 r62520  
    161161
    162162        /**
     163         * Tests that duplicate blocks get distinct elements class names
     164         * on their rendered markup to avoid CSS cascade conflicts.
     165         *
     166         * @ticket 65435
     167         *
     168         * @covers ::wp_get_elements_class_name
     169         */
     170        public function test_elements_block_support_class_with_duplicate_blocks(): void {
     171                $this->test_block_name = 'test/element-block-supports';
     172
     173                register_block_type(
     174                        $this->test_block_name,
     175                        array(
     176                                'api_version' => 3,
     177                                'attributes'  => array(
     178                                        'style' => array(
     179                                                'type' => 'object',
     180                                        ),
     181                                ),
     182                                'supports'    => array(
     183                                        'color' => array(
     184                                                'link' => true,
     185                                        ),
     186                                ),
     187                        )
     188                );
     189
     190                $block = array(
     191                        'blockName' => $this->test_block_name,
     192                        'attrs'     => array(
     193                                'style' => array(
     194                                        'elements' => array(
     195                                                'link' => array(
     196                                                        'color' => array(
     197                                                                'text' => 'var:preset|color|vivid-red',
     198                                                        ),
     199                                                ),
     200                                        ),
     201                                ),
     202                        ),
     203                );
     204
     205                $block_markup         = '<p>Hello <a href="http://www.wordpress.org/">WordPress</a>!</p>';
     206                $elements_class_names = array();
     207                $count                = 2;
     208                for ( $i = 0; $i < $count; $i++ ) {
     209                        $rendered_block = wp_render_elements_class_name( $block_markup, wp_render_elements_support_styles( $block ) );
     210
     211                        $processor = new WP_HTML_Tag_Processor( $rendered_block );
     212                        $this->assertTrue( $processor->next_tag( 'P' ), "Expected paragraph in block #$i." );
     213                        $elements_class_name = array_first(
     214                                array_filter(
     215                                        iterator_to_array( $processor->class_list() ),
     216                                        fn( string $class_name ) => (bool) preg_match( '/^wp-elements-\d+$/', $class_name )
     217                                )
     218                        );
     219                        $this->assertIsString( $elements_class_name, "Expected wp-elements class in block #$i." );
     220                        $elements_class_names[] = $elements_class_name;
     221                }
     222
     223                $this->assertSame( $count, count( array_unique( $elements_class_names ) ), 'Expected each rendered block to have a unique wp-elements class name.' );
     224        }
     225
     226        /**
    163227         * Data provider.
    164228         *
     
    239303                                ),
    240304                                'block_markup'    => '<p>Hello <a href="http://www.wordpress.org/">WordPress</a>!</p>',
    241                                 'expected_markup' => '/^<p class="wp-elements-[a-f0-9]{32}">Hello <a href="http:\/\/www.wordpress.org\/">WordPress<\/a>!<\/p>$/',
     305                                'expected_markup' => '/^<p class="wp-elements-\d+">Hello <a href="http:\/\/www.wordpress.org\/">WordPress<\/a>!<\/p>$/',
    242306                        ),
    243307                        'link element styles apply class to wrapper'   => array(
     
    247311                                ),
    248312                                'block_markup'    => '<p>Hello <a href="http://www.wordpress.org/">WordPress</a>!</p>',
    249                                 'expected_markup' => '/^<p class="wp-elements-[a-f0-9]{32}">Hello <a href="http:\/\/www.wordpress.org\/">WordPress<\/a>!<\/p>$/',
     313                                'expected_markup' => '/^<p class="wp-elements-\d+">Hello <a href="http:\/\/www.wordpress.org\/">WordPress<\/a>!<\/p>$/',
    250314                        ),
    251315                        'heading element styles apply class to wrapper' => array(
     
    255319                                ),
    256320                                'block_markup'    => '<p>Hello <a href="http://www.wordpress.org/">WordPress</a>!</p>',
    257                                 'expected_markup' => '/^<p class="wp-elements-[a-f0-9]{32}">Hello <a href="http:\/\/www.wordpress.org\/">WordPress<\/a>!<\/p>$/',
     321                                'expected_markup' => '/^<p class="wp-elements-\d+">Hello <a href="http:\/\/www.wordpress.org\/">WordPress<\/a>!<\/p>$/',
    258322                        ),
    259323                        'element styles apply class to wrapper when it has other classes' => array(
     
    263327                                ),
    264328                                'block_markup'    => '<p class="has-dark-gray-background-color has-background">Hello <a href="http://www.wordpress.org/">WordPress</a>!</p>',
    265                                 'expected_markup' => '/^<p class="has-dark-gray-background-color has-background wp-elements-[a-f0-9]{32}">Hello <a href="http:\/\/www.wordpress.org\/">WordPress<\/a>!<\/p>$/',
     329                                'expected_markup' => '/^<p class="has-dark-gray-background-color has-background wp-elements-\d+">Hello <a href="http:\/\/www.wordpress.org\/">WordPress<\/a>!<\/p>$/',
    266330                        ),
    267331                        'element styles apply class to wrapper when it has other attributes' => array(
     
    271335                                ),
    272336                                'block_markup'    => '<p id="anchor">Hello <a href="http://www.wordpress.org/">WordPress</a>!</p>',
    273                                 'expected_markup' => '/^<p class="wp-elements-[a-f0-9]{32}" id="anchor">Hello <a href="http:\/\/www.wordpress.org\/">WordPress<\/a>!<\/p>$/',
     337                                'expected_markup' => '/^<p class="wp-elements-\d+" id="anchor">Hello <a href="http:\/\/www.wordpress.org\/">WordPress<\/a>!<\/p>$/',
    274338                        ),
    275339                );
  • trunk/tests/phpunit/tests/block-supports/wpRenderElementsSupportStyles.php

    r61008 r62520  
    7070
    7171        /**
     72         * Tests that identical blocks with different elements styles
     73         * generate distinct class names to avoid CSS cascade conflicts.
     74         *
     75         * @ticket 65435
     76         *
     77         * @covers ::wp_get_elements_class_name
     78         */
     79        public function test_elements_block_support_styles_with_duplicate_blocks(): void {
     80                $this->test_block_name = 'test/element-block-supports';
     81
     82                register_block_type(
     83                        $this->test_block_name,
     84                        array(
     85                                'api_version' => 3,
     86                                'attributes'  => array(
     87                                        'style' => array(
     88                                                'type' => 'object',
     89                                        ),
     90                                ),
     91                                'supports'    => array(
     92                                        'color' => array(
     93                                                'link' => true,
     94                                        ),
     95                                ),
     96                        )
     97                );
     98
     99                $block = array(
     100                        'blockName' => $this->test_block_name,
     101                        'attrs'     => array(
     102                                'style' => array(
     103                                        'elements' => array(
     104                                                'link' => array(
     105                                                        'color' => array(
     106                                                                'text' => 'blue',
     107                                                        ),
     108                                                ),
     109                                        ),
     110                                ),
     111                        ),
     112                );
     113
     114                // Process two identical blocks with the same elements styles.
     115                $count = 2;
     116                for ( $i = 0; $i < $count; $i++ ) {
     117                        wp_render_elements_support_styles( $block );
     118                }
     119                $actual_stylesheet = wp_style_engine_get_stylesheet_from_context( 'block-supports', array( 'prettify' => false ) );
     120
     121                // Count the number of distinct class names to confirm uniqueness.
     122                $this->assertSame( $count, preg_match_all( '/\.wp-elements-(\d+)/', $actual_stylesheet, $matches ) );
     123                $unique_classes = array_unique( $matches[1] );
     124                $this->assertCount( $count, $unique_classes, 'Both blocks should produce distinct class names' );
     125        }
     126
     127        /**
    72128         * Data provider.
    73129         *
     
    128184                                        'button' => array( 'color' => $color_styles ),
    129185                                ),
    130                                 'expected_styles' => '/^.wp-elements-[a-f0-9]{32} .wp-element-button, .wp-elements-[a-f0-9]{32} .wp-block-button__link' . $color_css_rules . '$/',
     186                                'expected_styles' => '/^.wp-elements-\d+ .wp-element-button, .wp-elements-\d+ .wp-block-button__link' . $color_css_rules . '$/',
    131187                        ),
    132188                        'link element styles are applied'            => array(
     
    140196                                        ),
    141197                                ),
    142                                 'expected_styles' => '/^.wp-elements-[a-f0-9]{32} a:where\(:not\(.wp-element-button\)\)' . $color_css_rules .
    143                                         '.wp-elements-[a-f0-9]{32} a:where\(:not\(.wp-element-button\)\):hover' . $color_css_rules . '$/',
     198                                'expected_styles' => '/^.wp-elements-\d+ a:where\(:not\(.wp-element-button\)\)' . $color_css_rules .
     199                                        '.wp-elements-\d+ a:where\(:not\(.wp-element-button\)\):hover' . $color_css_rules . '$/',
    144200                        ),
    145201                        'generic heading element styles are applied' => array(
     
    148204                                        'heading' => array( 'color' => $color_styles ),
    149205                                ),
    150                                 'expected_styles' => '/^.wp-elements-[a-f0-9]{32} h1, .wp-elements-[a-f0-9]{32} h2, .wp-elements-[a-f0-9]{32} h3, .wp-elements-[a-f0-9]{32} h4, .wp-elements-[a-f0-9]{32} h5, .wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/',
     206                                'expected_styles' => '/^.wp-elements-\d+ h1, .wp-elements-\d+ h2, .wp-elements-\d+ h3, .wp-elements-\d+ h4, .wp-elements-\d+ h5, .wp-elements-\d+ h6' . $color_css_rules . '$/',
    151207                        ),
    152208                        'individual heading element styles are applied' => array(
     
    160216                                        'h6' => array( 'color' => $color_styles ),
    161217                                ),
    162                                 'expected_styles' => '/^.wp-elements-[a-f0-9]{32} h1' . $color_css_rules .
    163                                         '.wp-elements-[a-f0-9]{32} h2' . $color_css_rules .
    164                                         '.wp-elements-[a-f0-9]{32} h3' . $color_css_rules .
    165                                         '.wp-elements-[a-f0-9]{32} h4' . $color_css_rules .
    166                                         '.wp-elements-[a-f0-9]{32} h5' . $color_css_rules .
    167                                         '.wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/',
     218                                'expected_styles' => '/^.wp-elements-\d+ h1' . $color_css_rules .
     219                                        '.wp-elements-\d+ h2' . $color_css_rules .
     220                                        '.wp-elements-\d+ h3' . $color_css_rules .
     221                                        '.wp-elements-\d+ h4' . $color_css_rules .
     222                                        '.wp-elements-\d+ h5' . $color_css_rules .
     223                                        '.wp-elements-\d+ h6' . $color_css_rules . '$/',
    168224                        ),
    169225                );
Note: See TracChangeset for help on using the changeset viewer.