From 9cbaee349c69baf48e303dfb4a8ad0b0f71ead53 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Thu, 27 Jun 2024 20:47:38 +0000 Subject: [PATCH] HTML API: Report breadcrumbs properly when visiting virtual nodes. When [58304] introduced the abililty to visit virtual nodes in the HTML document, those being the nodes which are implied by the HTML but no explicitly present in the raw text, a bug was introduced in the `get_breadcrumbs()` method because it wasn't updated to be aware of the virtual nodes. Therefore it would report the wrong breadcrumbs for virtual nodes. Since the new `get_depth()` method is based on the same logic it was also broken for virtual nodes. In this patch, the breadcrumbs have been updated to account for the virtual nodes and the depth method has been updated to rely on the fixed breadcrumb logic. Developed in https://github.com/WordPress/wordpress-develop/pull/6914 Discussed in https://core.trac.wordpress.org/ticket/61348 Follow-up to [58304]. Props dmsnell, jonsurrell, zieladam. See #61348. git-svn-id: https://develop.svn.wordpress.org/trunk@58588 602fd350-edb4-49c9-b593-d223f7449a82 --- .../html-api/class-wp-html-open-elements.php | 14 ++++- .../html-api/class-wp-html-processor.php | 52 ++++++++++++++++--- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-open-elements.php b/src/wp-includes/html-api/class-wp-html-open-elements.php index 7f148a7af706..15479801dda1 100644 --- a/src/wp-includes/html-api/class-wp-html-open-elements.php +++ b/src/wp-includes/html-api/class-wp-html-open-elements.php @@ -308,11 +308,15 @@ public function has_p_in_button_scope() { */ public function pop() { $item = array_pop( $this->stack ); - if ( null === $item ) { return false; } + if ( 'context-node' === $item->bookmark_name ) { + $this->stack[] = $item; + return false; + } + $this->after_element_pop( $item ); return true; } @@ -329,6 +333,10 @@ public function pop() { */ public function pop_until( $tag_name ) { foreach ( $this->walk_up() as $item ) { + if ( 'context-node' === $item->bookmark_name ) { + return true; + } + $this->pop(); if ( @@ -369,6 +377,10 @@ public function push( $stack_item ) { * @return bool Whether the node was found and removed from the stack of open elements. */ public function remove_node( $token ) { + if ( 'context-node' === $token->bookmark_name ) { + return false; + } + foreach ( $this->walk_up() as $position_from_end => $item ) { if ( $token->bookmark_name !== $item->bookmark_name ) { continue; diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 0eb597edcd80..29f1c7ac6d4c 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -519,10 +519,12 @@ public function next_token() { return false; } - if ( 0 === count( $this->element_queue ) && ! $this->step() ) { - while ( $this->state->stack_of_open_elements->pop() ) { + if ( 'done' !== $this->has_seen_context_node && 0 === count( $this->element_queue ) && ! $this->step() ) { + while ( 'context-node' !== $this->state->stack_of_open_elements->current_node()->bookmark_name && $this->state->stack_of_open_elements->pop() ) { continue; } + $this->has_seen_context_node = 'done'; + return $this->next_token(); } $this->current_element = array_shift( $this->element_queue ); @@ -537,7 +539,11 @@ public function next_token() { } if ( ! isset( $this->current_element ) ) { - return $this->next_token(); + if ( 'done' === $this->has_seen_context_node ) { + return false; + } else { + return $this->next_token(); + } } if ( isset( $this->context_node ) && WP_HTML_Stack_Event::POP === $this->current_element->operation && $this->context_node === $this->current_element->token ) { @@ -782,16 +788,48 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { * * @since 6.4.0 * - * @todo make aware of queue of elements, because stack operations have already been done by now. - * * @return string[]|null Array of tag names representing path to matched node, if matched, otherwise NULL. */ public function get_breadcrumbs() { $breadcrumbs = array(); + foreach ( $this->state->stack_of_open_elements->walk_down() as $stack_item ) { $breadcrumbs[] = $stack_item->node_name; } + if ( ! $this->is_virtual() ) { + return $breadcrumbs; + } + + foreach ( $this->element_queue as $queue_item ) { + if ( $this->current_element->token->bookmark_name === $queue_item->token->bookmark_name ) { + break; + } + + if ( 'context-node' === $queue_item->token->bookmark_name ) { + break; + } + + if ( 'real' === $queue_item->provenance ) { + break; + } + + if ( WP_HTML_Stack_Event::PUSH === $queue_item->operation ) { + $breadcrumbs[] = $queue_item->token->node_name; + } else { + array_pop( $breadcrumbs ); + } + } + + if ( null !== parent::get_token_name() && ! parent::is_tag_closer() ) { + array_pop( $breadcrumbs ); + } + + // Add the virtual node we're at. + if ( WP_HTML_Stack_Event::PUSH === $this->current_element->operation ) { + $breadcrumbs[] = $this->current_element->token->node_name; + } + return $breadcrumbs; } @@ -821,7 +859,9 @@ public function get_breadcrumbs() { * @return int Nesting-depth of current location in the document. */ public function get_current_depth() { - return $this->state->stack_of_open_elements->count(); + return $this->is_virtual() + ? count( $this->get_breadcrumbs() ) + : $this->state->stack_of_open_elements->count(); } /**