Skip to content

Commit 8cbfce1

Browse files
committed
HTML API: Support more of the adoption agency algorithm.
1 parent e11f9ee commit 8cbfce1

2 files changed

Lines changed: 124 additions & 29 deletions

File tree

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -470,12 +470,23 @@ public function remove_node( $token ) {
470470
* see WP_HTML_Open_Elements::walk_up().
471471
*
472472
* @since 6.4.0
473+
* @since 6.7.0 Accepts $below_this_node to start traversal below a given node, if it exists.
474+
*
475+
* @param ?WP_HTML_Token $below_this_node Start traversing below this node, if provided and if the node exists.
473476
*/
474-
public function walk_down() {
475-
$count = count( $this->stack );
477+
public function walk_down( $below_this_node = null ) {
478+
$has_found_node = null === $below_this_node;
479+
$count = count( $this->stack );
476480

477481
for ( $i = 0; $i < $count; $i++ ) {
478-
yield $this->stack[ $i ];
482+
$node = $this->stack[ $i ];
483+
484+
if ( ! $has_found_node ) {
485+
$has_found_node = $node === $below_this_node;
486+
continue;
487+
}
488+
489+
yield $node;
479490
}
480491
}
481492

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

Lines changed: 110 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3103,20 +3103,15 @@ private function run_adoption_agency_algorithm() {
31033103

31043104
if (
31053105
// > If the current node is an HTML element whose tag name is subject
3106-
$current_node && $subject === $current_node->node_name &&
3106+
isset( $current_node ) && $subject === $current_node->node_name &&
31073107
// > the current node is not in the list of active formatting elements
31083108
! $this->state->active_formatting_elements->contains_node( $current_node )
31093109
) {
31103110
$this->state->stack_of_open_elements->pop();
31113111
return;
31123112
}
31133113

3114-
$outer_loop_counter = 0;
3115-
while ( $budget-- > 0 ) {
3116-
if ( $outer_loop_counter++ >= 8 ) {
3117-
return;
3118-
}
3119-
3114+
for ( $outer_loop_counter = 0; $outer_loop_counter < 8; $outer_loop_counter++ ) {
31203115
/*
31213116
* > Let formatting element be the last element in the list of active formatting elements that:
31223117
* > - is between the end of the list and the last marker in the list,
@@ -3137,8 +3132,35 @@ private function run_adoption_agency_algorithm() {
31373132

31383133
// > If there is no such element, then return and instead act as described in the "any other end tag" entry above.
31393134
if ( null === $formatting_element ) {
3140-
$this->last_error = self::ERROR_UNSUPPORTED;
3141-
throw new WP_HTML_Unsupported_Exception( 'Cannot run adoption agency when "any other end tag" is required.' );
3135+
/*
3136+
* > Any other end tag
3137+
*/
3138+
3139+
/*
3140+
* Find the corresponding tag opener in the stack of open elements, if
3141+
* it exists before reaching a special element, which provides a kind
3142+
* of boundary in the stack. For example, a `</custom-tag>` should not
3143+
* close anything beyond its containing `P` or `DIV` element.
3144+
*/
3145+
foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) {
3146+
if ( $subject === $node->node_name ) {
3147+
break;
3148+
}
3149+
3150+
if ( self::is_special( $node->node_name ) ) {
3151+
// This is a parse error, ignore the token.
3152+
return;
3153+
}
3154+
}
3155+
3156+
$this->generate_implied_end_tags( $subject );
3157+
3158+
foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
3159+
$this->state->stack_of_open_elements->pop();
3160+
if ( $node === $item ) {
3161+
return;
3162+
}
3163+
}
31423164
}
31433165

31443166
// > 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.
@@ -3152,22 +3174,16 @@ private function run_adoption_agency_algorithm() {
31523174
return;
31533175
}
31543176

3177+
/*
3178+
* > If formatting element is not the current node, this is a parse error. (But do not return.)
3179+
*/
3180+
31553181
/*
31563182
* > Let furthest block be the topmost node in the stack of open elements that is lower in the stack
31573183
* > than formatting element, and is an element in the special category. There might not be one.
31583184
*/
3159-
$is_above_formatting_element = true;
3160-
$furthest_block = null;
3161-
foreach ( $this->state->stack_of_open_elements->walk_down() as $item ) {
3162-
if ( $is_above_formatting_element && $formatting_element->bookmark_name !== $item->bookmark_name ) {
3163-
continue;
3164-
}
3165-
3166-
if ( $is_above_formatting_element ) {
3167-
$is_above_formatting_element = false;
3168-
continue;
3169-
}
3170-
3185+
$furthest_block = null;
3186+
foreach ( $this->state->stack_of_open_elements->walk_down( $formatting_element ) as $item ) {
31713187
if ( self::is_special( $item->node_name ) ) {
31723188
$furthest_block = $item;
31733189
break;
@@ -3183,19 +3199,87 @@ private function run_adoption_agency_algorithm() {
31833199
foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
31843200
$this->state->stack_of_open_elements->pop();
31853201

3186-
if ( $formatting_element->bookmark_name === $item->bookmark_name ) {
3202+
if ( $formatting_element === $item ) {
31873203
$this->state->active_formatting_elements->remove_node( $formatting_element );
31883204
return;
31893205
}
31903206
}
31913207
}
31923208

3209+
/*
3210+
* > Let common ancestor be the element immediately above formatting element in the stack of open elements.
3211+
*/
3212+
$common_ancestor = null;
3213+
foreach ( $this->state->stack_of_open_elements->walk_up( $formatting_element ) as $item ) {
3214+
$common_ancestor = $item;
3215+
break;
3216+
}
3217+
3218+
/*
3219+
* 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.
3220+
*/
3221+
$formatting_element_index = 0;
3222+
foreach ( $this->state->active_formatting_elements->walk_down() as $item ) {
3223+
if ( $formatting_element === $item ) {
3224+
break;
3225+
}
3226+
3227+
++$formatting_element_index;
3228+
}
3229+
3230+
/*
3231+
* > Let node and last node be furthest block.
3232+
*/
3233+
$node = $furthest_block;
3234+
$last_node = $furthest_block;
3235+
3236+
$inner_loop_counter = 0;
3237+
while ( $budget-- > 0 ) {
3238+
++$inner_loop_counter;
3239+
3240+
if ( $this->state->stack_of_open_elements->contains_node( $node ) ) {
3241+
foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $item ) {
3242+
$node = $item;
3243+
break;
3244+
}
3245+
} else {
3246+
$this->last_error = self::ERROR_UNSUPPORTED;
3247+
throw new WP_HTML_Unsupported_Exception( 'Cannot adjust node pointer above removed node.' );
3248+
}
3249+
3250+
if ( $formatting_element === $node ) {
3251+
break;
3252+
}
3253+
3254+
if ( $inner_loop_counter > 3 && $this->state->active_formatting_elements->contains_node( $node ) ) {
3255+
$this->state->active_formatting_elements->remove_node( $node );
3256+
}
3257+
3258+
if ( ! $this->state->active_formatting_elements->contains_node( $node ) ) {
3259+
$this->state->stack_of_open_elements->remove_node( $node );
3260+
continue;
3261+
}
3262+
3263+
/*
3264+
* > Create an element for the token for which the element node was created,
3265+
* in the HTML namespace, with common ancestor as the intended parent;
3266+
* replace the entry for node in the list of active formatting elements
3267+
* with an entry for the new element, replace the entry for node in the
3268+
* stack of open elements with an entry for the new element, and let node
3269+
* be the new element.
3270+
*/
3271+
$this->last_error = self::ERROR_UNSUPPORTED;
3272+
throw new WP_HTML_Unsupported_Exception( 'Cannot create and reference new element for which no token exists.' );
3273+
}
3274+
3275+
/*
3276+
* > Insert whatever last node ended up being in the previous step at the appropriate
3277+
* > palce for inserting a node, but using common ancestor as the override target.
3278+
*/
3279+
31933280
$this->last_error = self::ERROR_UNSUPPORTED;
3194-
throw new WP_HTML_Unsupported_Exception( 'Cannot extract common ancestor in adoption agency algorithm.' );
3281+
throw new WP_HTML_Unsupported_Exception( 'Cannot create and reference new element for which no token exists.' );
31953282
}
3196-
3197-
$this->last_error = self::ERROR_UNSUPPORTED;
3198-
throw new WP_HTML_Unsupported_Exception( 'Cannot run adoption agency when looping required.' );
31993283
}
32003284

32013285
/**

0 commit comments

Comments
 (0)