Make WordPress Core

Changeset 65371


Ignore:
Timestamp:
11/10/2026 05:22:33 AM (15 hours ago)
Author:
luisherranz
Message:

Interactivity API: Choose the data-wp-html placeholder marker in linear time.

Replace the repeated input-search loop with a UUID-based marker, generated
when the first data-wp-html host renders and reused by subsequent hosts
in the same processing call.

This removes input-dependent searching, keeps placeholder length fixed,
and avoids generating a UUID when no host renders.

Props jonsurrell.
Fixes #66498.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/interactivity-api/class-wp-interactivity-api.php

    r65343 r65371  
    184184        /**
    185185         * Marker that makes the placeholders of the current `process_directives()`
    186          * call recognizable. It occurs nowhere in the HTML that call received, and
    187          * it differs from the marker of any call that the current one runs inside.
     186         * call recognizable, or an empty string until the first `data-wp-html` host
     187         * of the call renders and draws it. It holds a random UUID, so no text
     188         * written before the call can realistically hold it.
    188189         *
    189190         * @since 7.2.0
    … …  
    537538                 * The trusted HTML of `data-wp-html` hosts never takes part in the
    538539                 * processing: each host receives a placeholder comment, and the comments
    539                  * are replaced by their HTML once the result is known. The marker that
    540                  * identifies them is chosen so that it occurs nowhere in the input, which
    541                  * makes it impossible for the input to contain a placeholder. It is also
    542                  * longer than the marker of any call this one runs inside, so their
    543                  * placeholders never coincide.
     540                 * are replaced by their HTML once the result is known. The call starts
     541                 * without the marker that identifies them: the first host that renders
     542                 * draws it, so a call without such a host does no marker work.
    544543                 */
    545                 $marker = '' === $this->html_placeholder_marker ? 'wp-interactivity-dangerous-html' : $this->html_placeholder_marker . '_';
    546                 while ( str_contains( $html, $marker ) ) {
    547                         $marker .= '_';
    548                 }
    549                 $this->html_placeholder_marker = $marker;
     544                $this->html_placeholder_marker = '';
    550545                $this->html_placeholders       = array();
    551546
    … …  
    16991694                 * The HTML is held back and a placeholder comment takes its place, so no
    17001695                 * later step of the call scans it. A comment is a single token for the
    1701                  * processors, and the marker in it occurs nowhere in the input.
     1696                 * processors. Its marker holds a random UUID, drawn once per call by the
     1697                 * first host that renders, so no text written before the call can
     1698                 * realistically contain a placeholder.
    17021699                 */
     1700                if ( '' === $this->html_placeholder_marker ) {
     1701                        $this->html_placeholder_marker = 'wp-interactivity-dangerous-html-' . wp_generate_uuid4();
     1702                }
    17031703                $placeholder = '<!--' . $this->html_placeholder_marker . ':' . count( $this->html_placeholders ) . '-->';
    17041704                if ( $p->skip_to_balanced_tag_closer( $placeholder ) ) {
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-html.php

    r65343 r65371  
    10941094
    10951095        /**
    1096          * Tests that markup in the input that looks like a placeholder is not
    1097          * mistaken for one, however many marker variants it holds.
     1096         * Tests that markup in the input that looks like a placeholder, but holds a
     1097         * marker other than the call's own, is not mistaken for one. That includes
     1098         * the form of the call's own placeholders with a UUID other than the call's.
    10981099         *
    10991100         * @ticket 66246
    … …  
    11021103         */
    11031104        public function test_substitution_cannot_be_forged() {
    1104                 $lookalikes = '<!--wp-interactivity-dangerous-html:0--><!--wp-interactivity-dangerous-html_:0-->';
     1105                $lookalikes = '<!--wp-interactivity-dangerous-html:0--><!--wp-interactivity-dangerous-html_:0-->' .
     1106                        '<!--wp-interactivity-dangerous-html-3f2b8c1e-7d4a-4e9b-a5c6-0d1e2f3a4b5c:0-->';
    11051107                $this->set_state( array( 'html' => $this->token() ) );
    11061108
    … …  
    11111113                $this->assertSame(
    11121114                        '<div data-wp-interactive="test"><i>' . $lookalikes . '</i><p data-wp-html="state.html">' . self::MARKER_HTML . '</p></div>',
    1113                         $output
    1114                 );
    1115         }
    1116 
    1117         /**
    1118          * Tests that HTML that looks like the placeholder of another host is
    1119          * inserted as given, not replaced by that host's HTML.
    1120          *
    1121          * @ticket 66246
    1122          *
    1123          * @covers ::process_directives
    1124          */
    1125         public function test_inserted_html_is_not_searched_for_placeholders() {
    1126                 $lookalike = '<!--wp-interactivity-dangerous-html:1-->';
    1127                 $this->set_state(
    1128                         array(
    1129                                 'first'  => wp_interactivity_as_dangerous_html( $lookalike ),
    1130                                 'second' => wp_interactivity_as_dangerous_html( '<u>second</u>' ),
    1131                         )
    1132                 );
    1133 
    1134                 $output = wp_interactivity_process_directives(
    1135                         '<div data-wp-interactive="test"><p data-wp-html="state.first">One</p><p data-wp-html="state.second">Two</p></div>'
    1136                 );
    1137 
    1138                 $this->assertSame(
    1139                         '<div data-wp-interactive="test"><p data-wp-html="state.first">' . $lookalike . '</p><p data-wp-html="state.second"><u>second</u></p></div>',
    11401115                        $output
    11411116                );
Note: See TracChangeset for help on using the changeset viewer.