Make WordPress Core

Changeset 58713


Ignore:
Timestamp:
07/12/2024 10:18:16 PM (2 years ago)
Author:
dmsnell
Message:

HTML API: Simplify breadcrumb accounting.

Since the HTML Processor started visiting all nodes in a document, both
real and virtual, the breadcrumb accounting became a bit complicated
and it's not entirely clear that it is fully reliable.

In this patch the breadcrumbs are rebuilt separately from the stack of
open elements in order to eliminate the problem of the stateful stack
interactions and the post-hoc event queue.

Breadcrumbs are greatly simplified as a result, and more verifiably
correct, in this construction.

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

Follow-up to [58590].

Props bernhard-reiter, dmsnell.
See #61576.

Location:
trunk
Files:
2 edited

Legend:

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

    r58702 r58713  
    213213
    214214        /**
     215         * Stores the current breadcrumbs.
     216         *
     217         * @since 6.7.0
     218         *
     219         * @var string[]
     220         */
     221        private $breadcrumbs = array();
     222
     223        /**
    215224         * Current stack event, if set, representing a matched token.
    216225         *
     
    311320                );
    312321
    313                 $processor->state->stack_of_open_elements->push( $context_node );
    314322                $processor->context_node = $context_node;
     323                $processor->breadcrumbs  = array( 'HTML', $context_node->node_name );
    315324
    316325                return $processor;
     
    524533                }
    525534
    526                 if ( 'done' !== $this->has_seen_context_node && 0 === count( $this->element_queue ) && ! $this->step() ) {
    527                         while ( 'context-node' !== $this->state->stack_of_open_elements->current_node()->bookmark_name && $this->state->stack_of_open_elements->pop() ) {
    528                                 continue;
    529                         }
    530                         $this->has_seen_context_node = 'done';
    531                         return $this->next_token();
    532                 }
    533 
     535                /*
     536                 * Prime the events if there are none.
     537                 *
     538                 * @todo In some cases, probably related to the adoption agency
     539                 *       algorithm, this call to step() doesn't create any new
     540                 *       events. Calling it again creates them. Figure out why
     541                 *       this is and if it's inherent or if it's a bug. Looping
     542                 *       until there are events or until there are no more
     543                 *       tokens works in the meantime and isn't obviously wrong.
     544                 */
     545                while ( empty( $this->element_queue ) && $this->step() ) {
     546                        continue;
     547                }
     548
     549                // Process the next event on the queue.
    534550                $this->current_element = array_shift( $this->element_queue );
    535                 while ( isset( $this->context_node ) && ! $this->has_seen_context_node ) {
    536                         if ( isset( $this->current_element ) ) {
    537                                 if ( $this->context_node === $this->current_element->token && WP_HTML_Stack_Event::PUSH === $this->current_element->operation ) {
    538                                         $this->has_seen_context_node = true;
    539                                         return $this->next_token();
    540                                 }
    541                         }
    542                         $this->current_element = array_shift( $this->element_queue );
    543                 }
    544 
    545551                if ( ! isset( $this->current_element ) ) {
    546                         if ( 'done' === $this->has_seen_context_node ) {
    547                                 return false;
    548                         } else {
    549                                 return $this->next_token();
    550                         }
    551                 }
    552 
    553                 if ( isset( $this->context_node ) && WP_HTML_Stack_Event::POP === $this->current_element->operation && $this->context_node === $this->current_element->token ) {
    554                         $this->element_queue   = array();
    555                         $this->current_element = null;
    556552                        return false;
    557553                }
    558554
     555                $is_pop = WP_HTML_Stack_Event::POP === $this->current_element->operation;
     556
     557                /*
     558                 * The root node only exists in the fragment parser, and closing it
     559                 * indicates that the parse is complete. Stop before popping if from
     560                 * the breadcrumbs.
     561                 */
     562                if ( 'root-node' === $this->current_element->token->bookmark_name ) {
     563                        return ! $is_pop && $this->next_token();
     564                }
     565
     566                // Adjust the breadcrumbs for this event.
     567                if ( $is_pop ) {
     568                        array_pop( $this->breadcrumbs );
     569                } else {
     570                        $this->breadcrumbs[] = $this->current_element->token->node_name;
     571                }
     572
    559573                // Avoid sending close events for elements which don't expect a closing.
    560                 if (
    561                         WP_HTML_Stack_Event::POP === $this->current_element->operation &&
    562                         ! static::expects_closer( $this->current_element->token )
    563                 ) {
     574                if ( $is_pop && ! static::expects_closer( $this->current_element->token ) ) {
    564575                        return $this->next_token();
    565576                }
     
    644655                }
    645656
    646                 foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) {
     657                for ( $i = count( $this->breadcrumbs ) - 1; $i >= 0; $i-- ) {
     658                        $node  = $this->breadcrumbs[ $i ];
    647659                        $crumb = strtoupper( current( $breadcrumbs ) );
    648660
    649                         if ( '*' !== $crumb && $node->node_name !== $crumb ) {
     661                        if ( '*' !== $crumb && $node !== $crumb ) {
    650662                                return false;
    651663                        }
     
    863875         */
    864876        public function get_breadcrumbs() {
    865                 $breadcrumbs = array();
    866 
    867                 foreach ( $this->state->stack_of_open_elements->walk_down() as $stack_item ) {
    868                         $breadcrumbs[] = $stack_item->node_name;
    869                 }
    870 
    871                 if ( ! $this->is_virtual() ) {
    872                         return $breadcrumbs;
    873                 }
    874 
    875                 foreach ( $this->element_queue as $queue_item ) {
    876                         if ( $this->current_element->token->bookmark_name === $queue_item->token->bookmark_name ) {
    877                                 break;
    878                         }
    879 
    880                         if ( 'context-node' === $queue_item->token->bookmark_name ) {
    881                                 break;
    882                         }
    883 
    884                         if ( 'real' === $queue_item->provenance ) {
    885                                 break;
    886                         }
    887 
    888                         if ( WP_HTML_Stack_Event::PUSH === $queue_item->operation ) {
    889                                 $breadcrumbs[] = $queue_item->token->node_name;
    890                         } else {
    891                                 array_pop( $breadcrumbs );
    892                         }
    893                 }
    894 
    895                 if ( null !== parent::get_token_name() && ! parent::is_tag_closer() ) {
    896                         array_pop( $breadcrumbs );
    897                 }
    898 
    899                 // Add the virtual node we're at.
    900                 if ( WP_HTML_Stack_Event::PUSH === $this->current_element->operation ) {
    901                         $breadcrumbs[] = $this->current_element->token->node_name;
    902                 }
    903 
    904                 return $breadcrumbs;
     877                return $this->breadcrumbs;
    905878        }
    906879
     
    931904         */
    932905        public function get_current_depth() {
    933                 return $this->is_virtual()
    934                         ? count( $this->get_breadcrumbs() )
    935                         : $this->state->stack_of_open_elements->count();
     906                return count( $this->breadcrumbs );
    936907        }
    937908
     
    25532524                        : 0;
    25542525                $bookmark_starts_at   = $this->bookmarks[ $actual_bookmark_name ]->start;
    2555                 $bookmark_length      = $this->bookmarks[ $actual_bookmark_name ]->length;
    25562526                $direction            = $bookmark_starts_at > $processor_started_at ? 'forward' : 'backward';
    25572527
     
    26112581                        $this->element_queue         = array();
    26122582                        $this->current_element       = null;
     2583
     2584                        if ( isset( $this->context_node ) ) {
     2585                                $this->breadcrumbs = array_slice( $this->breadcrumbs, 0, 2 );
     2586                        } else {
     2587                                $this->breadcrumbs = array();
     2588                        }
    26132589                }
    26142590
  • trunk/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php

    r58304 r58713  
    388388                $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'SPAN', 'CODE' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting.' );
    389389
    390                 $this->assertTrue( $processor->next_token(), 'Failed to advance past CODE tag to expected SPAN closer.' );
     390                $this->assertTrue(
     391                        $processor->next_tag(
     392                                array(
     393                                        'tag_name'    => 'SPAN',
     394                                        'tag_closers' => 'visit',
     395                                )
     396                        ),
     397                        'Failed to advance past CODE tag to expected SPAN closer.'
     398                );
     399                $this->assertSame( 'SPAN', $processor->get_tag() );
    391400                $this->assertTrue( $processor->is_tag_closer(), 'Expected to find closing SPAN, but found opener instead.' );
    392401                $this->assertSame( array( 'HTML', 'BODY', 'DIV' ), $processor->get_breadcrumbs(), 'Failed to advance past CODE tag to expected DIV opener.' );
Note: See TracChangeset for help on using the changeset viewer.