Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions src/wp-includes/html-api/class-wp-html-open-elements.php
Original file line number Diff line number Diff line change
Expand Up @@ -470,12 +470,23 @@ public function remove_node( $token ) {
* see WP_HTML_Open_Elements::walk_up().
*
* @since 6.4.0
* @since 6.7.0 Accepts $below_this_node to start traversal below a given node, if it exists.
*
* @param ?WP_HTML_Token $below_this_node Start traversing below this node, if provided and if the node exists.
*/
public function walk_down() {
$count = count( $this->stack );
public function walk_down( $below_this_node = null ) {
$has_found_node = null === $below_this_node;
$count = count( $this->stack );

for ( $i = 0; $i < $count; $i++ ) {
yield $this->stack[ $i ];
$node = $this->stack[ $i ];

if ( ! $has_found_node ) {
$has_found_node = $node === $below_this_node;
continue;
}

yield $node;
}
}

Expand Down
136 changes: 110 additions & 26 deletions src/wp-includes/html-api/class-wp-html-processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -3062,20 +3062,15 @@ private function run_adoption_agency_algorithm() {

if (
// > If the current node is an HTML element whose tag name is subject
$current_node && $subject === $current_node->node_name &&
isset( $current_node ) && $subject === $current_node->node_name &&
// > the current node is not in the list of active formatting elements
! $this->state->active_formatting_elements->contains_node( $current_node )
) {
$this->state->stack_of_open_elements->pop();
return;
}

$outer_loop_counter = 0;
while ( $budget-- > 0 ) {
if ( $outer_loop_counter++ >= 8 ) {
return;
}

for ( $outer_loop_counter = 0; $outer_loop_counter < 8; $outer_loop_counter++ ) {
/*
* > Let formatting element be the last element in the list of active formatting elements that:
* > - is between the end of the list and the last marker in the list,
Expand All @@ -3096,8 +3091,35 @@ private function run_adoption_agency_algorithm() {

// > If there is no such element, then return and instead act as described in the "any other end tag" entry above.
if ( null === $formatting_element ) {
$this->last_error = self::ERROR_UNSUPPORTED;
throw new WP_HTML_Unsupported_Exception( 'Cannot run adoption agency when "any other end tag" is required.' );
/*
* > Any other end tag
*/

/*
* Find the corresponding tag opener in the stack of open elements, if
* it exists before reaching a special element, which provides a kind
* of boundary in the stack. For example, a `</custom-tag>` should not
* close anything beyond its containing `P` or `DIV` element.
*/
foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) {
if ( $subject === $node->node_name ) {
break;
}

if ( self::is_special( $node->node_name ) ) {
// This is a parse error, ignore the token.
return;
}
}

$this->generate_implied_end_tags( $subject );

foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
$this->state->stack_of_open_elements->pop();
if ( $node === $item ) {
return;
}
}
}

// > If formatting element is not in the stack of open elements, then this is a parse error; remove the element from the list, and return.
Expand All @@ -3111,22 +3133,16 @@ private function run_adoption_agency_algorithm() {
return;
}

/*
* > If formatting element is not the current node, this is a parse error. (But do not return.)
*/

/*
* > Let furthest block be the topmost node in the stack of open elements that is lower in the stack
* > than formatting element, and is an element in the special category. There might not be one.
*/
$is_above_formatting_element = true;
$furthest_block = null;
foreach ( $this->state->stack_of_open_elements->walk_down() as $item ) {
if ( $is_above_formatting_element && $formatting_element->bookmark_name !== $item->bookmark_name ) {
continue;
}

if ( $is_above_formatting_element ) {
$is_above_formatting_element = false;
continue;
}

$furthest_block = null;
foreach ( $this->state->stack_of_open_elements->walk_down( $formatting_element ) as $item ) {
if ( self::is_special( $item->node_name ) ) {
$furthest_block = $item;
break;
Expand All @@ -3142,19 +3158,87 @@ private function run_adoption_agency_algorithm() {
foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
$this->state->stack_of_open_elements->pop();

if ( $formatting_element->bookmark_name === $item->bookmark_name ) {
if ( $formatting_element === $item ) {
$this->state->active_formatting_elements->remove_node( $formatting_element );
return;
}
}
}

/*
* > Let common ancestor be the element immediately above formatting element in the stack of open elements.
*/
$common_ancestor = null;
foreach ( $this->state->stack_of_open_elements->walk_up( $formatting_element ) as $item ) {
$common_ancestor = $item;
break;
}

/*
* Let a bookmark note the position of formatting element in the list of active formatting elements relative to the elements on either side of it in the list.
*/
$formatting_element_index = 0;
foreach ( $this->state->active_formatting_elements->walk_down() as $item ) {
if ( $formatting_element === $item ) {
break;
}

++$formatting_element_index;
}

/*
* > Let node and last node be furthest block.
*/
$node = $furthest_block;
$last_node = $furthest_block;

$inner_loop_counter = 0;
while ( $budget-- > 0 ) {
++$inner_loop_counter;

if ( $this->state->stack_of_open_elements->contains_node( $node ) ) {
foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $item ) {
$node = $item;
break;
}
} else {
$this->last_error = self::ERROR_UNSUPPORTED;
throw new WP_HTML_Unsupported_Exception( 'Cannot adjust node pointer above removed node.' );
}

if ( $formatting_element === $node ) {
break;
}

if ( $inner_loop_counter > 3 && $this->state->active_formatting_elements->contains_node( $node ) ) {
$this->state->active_formatting_elements->remove_node( $node );
}

if ( ! $this->state->active_formatting_elements->contains_node( $node ) ) {
$this->state->stack_of_open_elements->remove_node( $node );
continue;
}

/*
* > Create an element for the token for which the element node was created,
* in the HTML namespace, with common ancestor as the intended parent;
* replace the entry for node in the list of active formatting elements
* with an entry for the new element, replace the entry for node in the
* stack of open elements with an entry for the new element, and let node
* be the new element.
*/
$this->last_error = self::ERROR_UNSUPPORTED;
throw new WP_HTML_Unsupported_Exception( 'Cannot create and reference new element for which no token exists.' );
}

/*
* > Insert whatever last node ended up being in the previous step at the appropriate
* > palce for inserting a node, but using common ancestor as the override target.
*/

$this->last_error = self::ERROR_UNSUPPORTED;
throw new WP_HTML_Unsupported_Exception( 'Cannot extract common ancestor in adoption agency algorithm.' );
throw new WP_HTML_Unsupported_Exception( 'Cannot create and reference new element for which no token exists.' );
}

$this->last_error = self::ERROR_UNSUPPORTED;
throw new WP_HTML_Unsupported_Exception( 'Cannot run adoption agency when looping required.' );
}

/**
Expand Down