Skip to content

Commit 779e594

Browse files
committed
HTML API: Queue virtual closers after non-current removals
1 parent c424557 commit 779e594

2 files changed

Lines changed: 90 additions & 5 deletions

File tree

src/wp-includes/html-api/class-wp-html-open-elements.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ public function remove_node( WP_HTML_Token $token ): bool {
588588

589589
$position_from_start = $this->count() - $position_from_end - 1;
590590
array_splice( $this->stack, $position_from_start, 1 );
591-
$this->after_element_pop( $item );
591+
$this->after_element_pop( $item, 0 === $position_from_end );
592592
return true;
593593
}
594594

@@ -731,9 +731,10 @@ public function after_element_push( WP_HTML_Token $item ): void {
731731
*
732732
* @since 6.4.0
733733
*
734-
* @param WP_HTML_Token $item Element that was removed from the stack of open elements.
734+
* @param WP_HTML_Token $item Element that was removed from the stack of open elements.
735+
* @param bool $invoke_pop_handler Whether to call the pop handler.
735736
*/
736-
public function after_element_pop( WP_HTML_Token $item ): void {
737+
public function after_element_pop( WP_HTML_Token $item, bool $invoke_pop_handler = true ): void {
737738
/*
738739
* When adding support for new elements, expand this switch to trap
739740
* cases where the precalculated value needs to change.
@@ -767,7 +768,7 @@ public function after_element_pop( WP_HTML_Token $item ): void {
767768
break;
768769
}
769770

770-
if ( null !== $this->pop_handler ) {
771+
if ( $invoke_pop_handler && null !== $this->pop_handler ) {
771772
call_user_func( $this->pop_handler, $item );
772773
}
773774
}

src/wp-includes/html-api/class-wp-html-processor.php

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,15 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor {
251251
*/
252252
private $current_element = null;
253253

254+
/**
255+
* Elements removed from the stack of open elements without a normal pop event.
256+
*
257+
* @since 7.1.0
258+
*
259+
* @var array[]
260+
*/
261+
private $non_lifo_breadcrumb_removals = array();
262+
254263
/**
255264
* Context node if created as a fragment parser.
256265
*
@@ -814,6 +823,10 @@ private function next_visitable_token(): bool {
814823
* tokens works in the meantime and isn't obviously wrong.
815824
*/
816825
if ( empty( $this->element_queue ) ) {
826+
if ( $this->queue_virtual_closer_after_non_lifo_removal() ) {
827+
return $this->next_visitable_token();
828+
}
829+
817830
if ( $this->step() ) {
818831
return $this->next_visitable_token();
819832
}
@@ -823,6 +836,10 @@ private function next_visitable_token(): bool {
823836
}
824837
}
825838

839+
if ( $this->queue_virtual_closer_after_non_lifo_removal() ) {
840+
return $this->next_visitable_token();
841+
}
842+
826843
// Process the next event on the queue.
827844
$this->current_element = array_shift( $this->element_queue );
828845
if ( ! isset( $this->current_element ) ) {
@@ -860,6 +877,61 @@ private function next_visitable_token(): bool {
860877
return true;
861878
}
862879

880+
/**
881+
* Queues a virtual closer for a removed node once its subtree closes.
882+
*
883+
* Non-LIFO removals from the stack of open elements do not emit a normal
884+
* pop event because those events blindly pop the current breadcrumb. The
885+
* removed node remains an ancestor of the currently open subtree, but must
886+
* be reported as a virtual closer before visiting the next token after
887+
* that subtree closes.
888+
*
889+
* @since 7.1.0
890+
*
891+
* @return bool Whether a virtual closer was queued.
892+
*/
893+
private function queue_virtual_closer_after_non_lifo_removal(): bool {
894+
if ( empty( $this->non_lifo_breadcrumb_removals ) ) {
895+
return false;
896+
}
897+
898+
$removed_node = end( $this->non_lifo_breadcrumb_removals );
899+
$removed_token = $removed_node['token'];
900+
$breadcrumb_depth = $removed_node['breadcrumb_depth'];
901+
902+
if (
903+
count( $this->breadcrumbs ) !== $breadcrumb_depth ||
904+
empty( $this->breadcrumbs ) ||
905+
end( $this->breadcrumbs ) !== $removed_token->node_name
906+
) {
907+
return false;
908+
}
909+
910+
// At EOF, normal stack pops may be queued and processed after the stack is empty.
911+
$adjusted_current_node = $this->get_adjusted_current_node();
912+
913+
if ( isset( $adjusted_current_node ) && end( $this->breadcrumbs ) === $adjusted_current_node->node_name ) {
914+
return false;
915+
}
916+
917+
$next_event = reset( $this->element_queue );
918+
if (
919+
false !== $next_event &&
920+
WP_HTML_Stack_Event::POP === $next_event->operation &&
921+
$next_event->token !== $removed_token &&
922+
$next_event->token->node_name === $removed_token->node_name
923+
) {
924+
return false;
925+
}
926+
927+
array_pop( $this->non_lifo_breadcrumb_removals );
928+
array_unshift(
929+
$this->element_queue,
930+
new WP_HTML_Stack_Event( $removed_token, WP_HTML_Stack_Event::POP, 'virtual' )
931+
);
932+
return true;
933+
}
934+
863935
/**
864936
* Indicates if the current tag token is a tag closer.
865937
*
@@ -2848,7 +2920,18 @@ private function step_in_body(): bool {
28482920
case 'A':
28492921
$this->run_adoption_agency_algorithm();
28502922
$this->state->active_formatting_elements->remove_node( $item );
2851-
$this->state->stack_of_open_elements->remove_node( $item );
2923+
$is_current_node = $item === $this->state->stack_of_open_elements->current_node();
2924+
if ( $this->state->stack_of_open_elements->remove_node( $item ) && ! $is_current_node ) {
2925+
$breadcrumb_depth = count( $this->breadcrumbs );
2926+
while ( 0 < $breadcrumb_depth && $this->breadcrumbs[ $breadcrumb_depth - 1 ] !== $item->node_name ) {
2927+
--$breadcrumb_depth;
2928+
}
2929+
2930+
$this->non_lifo_breadcrumb_removals[] = array(
2931+
'token' => $item,
2932+
'breadcrumb_depth' => $breadcrumb_depth,
2933+
);
2934+
}
28522935
break 2;
28532936
}
28542937
}
@@ -5675,6 +5758,7 @@ public function seek( $bookmark_name ): bool {
56755758
$this->state->current_token = null;
56765759
$this->current_element = null;
56775760
$this->element_queue = array();
5761+
$this->non_lifo_breadcrumb_removals = array();
56785762

56795763
/*
56805764
* The absence of a context node indicates a full parse.

0 commit comments

Comments
 (0)