Make WordPress Core


Ignore:
Timestamp:
07/07/2026 12:20:57 AM (8 weeks ago)
Author:
dmsnell
Message:

Editor: Skip parsing in prepend_to_selector() for simple selectors.

Splitting a CSS selector involves unnecessary computation and allocation when the selector could not possibly involve more than one selector. It was discovered in profiling that this function was being called 4000+ times in a single request, and adding a pre-condition before splitting saved meaningful time on a page render.

This patch adds the pre-condition to verify that any commas present in a selector string can only be “comma tokens,” which split selectors at the top level, making it safe to fall back to simpler parsing that’s more efficient than general separator splitting.

This commit synchronizes work from the following Gutenberg PRs:

Developed in: https://github.com/WordPress/wordpress-develop/pull/12306
Discussed in: https://core.trac.wordpress.org/ticket/65533

Follow-up to [62607].

Props dmsnell, josephscott.
See #65533.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-theme-json.php

    r62641 r62650  
    12971297        protected static function append_to_selector( $selector, $to_append ) {
    12981298                if ( ! str_contains( $selector, ',' ) ) {
    1299                         return $selector . $to_append;
    1300                 }
     1299                        return trim( $selector, " \t\n" ) . $to_append;
     1300                }
     1301
     1302                /**
     1303                 * Check for an opportunity to skip the more-costly selector splitting.
     1304                 * This should be possible if there are no comments, strings, functions,
     1305                 * URLs, escapes, or comment declaration openers (CDOs).
     1306                 *
     1307                 * Note that this means the fast-path will not apply for selectors like
     1308                 * the following incomplete list:
     1309                 *
     1310                 *  - `[class ~= "wide"]`
     1311                 *  - `.wp-block:is(.is-style-a, .is-style-b)`
     1312                 *  - `:nth-child(1)`
     1313                 *
     1314                 * These syntax forms all present opportunities where a comma may not
     1315                 * separate selectors. If none of the start characters are present,
     1316                 * there should be no way for a comma to mean anything other than a
     1317                 * comma token. The exception are syntax errors, which are not handled here.
     1318                 *
     1319                 * @see https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values
     1320                 */
     1321                if ( strlen( $selector ) === strcspn( $selector, '/\'"(<\\' ) ) {
     1322                        return preg_replace( '~[ \t\n]*,[ \t\n]*~', "{$to_append}, ", trim( $selector, " \t\n" ) ) . $to_append;
     1323                }
     1324
    13011325                $new_selectors = array();
    13021326                $selectors     = static::split_selector_list( $selector );
     
    13221346        protected static function prepend_to_selector( $selector, $to_prepend ) {
    13231347                if ( ! str_contains( $selector, ',' ) ) {
    1324                         return $to_prepend . $selector;
    1325                 }
     1348                        return $to_prepend . trim( $selector, " \t\n" );
     1349                }
     1350
     1351                /**
     1352                 * Check for an opportunity to skip the more-costly selector splitting.
     1353                 * This should be possible if there are no comments, strings, functions,
     1354                 * URLs, escapes, or comment declaration openers (CDOs).
     1355                 *
     1356                 * Note that this means the fast-path will not apply for selectors like
     1357                 * the following incomplete list:
     1358                 *
     1359                 *  - `[class ~= "wide"]`
     1360                 *  - `.wp-block:is(.is-style-a, .is-style-b)`
     1361                 *  - `:nth-child(1)`
     1362                 *
     1363                 * These syntax forms all present opportunities where a comma may not
     1364                 * separate selectors. If none of the start characters are present,
     1365                 * there should be no way for a comma to mean anything other than a
     1366                 * comma token. The exception are syntax errors, which are not handled here.
     1367                 *
     1368                 * @see https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values
     1369                 */
     1370                if ( strlen( $selector ) === strcspn( $selector, '/\'"(<\\' ) ) {
     1371                        return $to_prepend . preg_replace( '~[ \t\n]*,[ \t\n]*~', ", {$to_prepend}", trim( $selector, " \t\n" ) );
     1372                }
     1373
    13261374                $new_selectors = array();
    13271375                $selectors     = static::split_selector_list( $selector );
Note: See TracChangeset for help on using the changeset viewer.