Skip to content

Commit

Permalink
HTML API: Support more of the adoption agency algorithm.
Browse files Browse the repository at this point in the history
  • Loading branch information
dmsnell committed Jul 6, 2024
1 parent e11f9ee commit 8cbfce1
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 29 deletions.
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 @@ -3103,20 +3103,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 @@ -3137,8 +3132,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 @@ -3152,22 +3174,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 @@ -3183,19 +3199,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

0 comments on commit 8cbfce1

Please sign in to comment.