Skip to content

Commit 2d05d46

Browse files
committed
HTML API: Support more of the adoption agency algorithm.
1 parent ea3327a commit 2d05d46

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
@@ -414,12 +414,23 @@ public function remove_node( $token ) {
414414
* see WP_HTML_Open_Elements::walk_up().
415415
*
416416
* @since 6.4.0
417+
* @since 6.7.0 Accepts $below_this_node to start traversal below a given node, if it exists.
418+
*
419+
* @param ?WP_HTML_Token $below_this_node Start traversing below this node, if provided and if the node exists.
417420
*/
418-
public function walk_down() {
419-
$count = count( $this->stack );
421+
public function walk_down( $below_this_node = null ) {
422+
$has_found_node = null === $below_this_node;
423+
$count = count( $this->stack );
420424

421425
for ( $i = 0; $i < $count; $i++ ) {
422-
yield $this->stack[ $i ];
426+
$node = $this->stack[ $i ];
427+
428+
if ( ! $has_found_node ) {
429+
$has_found_node = $node === $below_this_node;
430+
continue;
431+
}
432+
433+
yield $node;
423434
}
424435
}
425436

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

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

21462146
if (
21472147
// > If the current node is an HTML element whose tag name is subject
2148-
$current_node && $subject === $current_node->node_name &&
2148+
isset( $current_node ) && $subject === $current_node->node_name &&
21492149
// > the current node is not in the list of active formatting elements
21502150
! $this->state->active_formatting_elements->contains_node( $current_node )
21512151
) {
21522152
$this->state->stack_of_open_elements->pop();
21532153
return;
21542154
}
21552155

2156-
$outer_loop_counter = 0;
2157-
while ( $budget-- > 0 ) {
2158-
if ( $outer_loop_counter++ >= 8 ) {
2159-
return;
2160-
}
2161-
2156+
for ( $outer_loop_counter = 0; $outer_loop_counter < 8; $outer_loop_counter++ ) {
21622157
/*
21632158
* > Let formatting element be the last element in the list of active formatting elements that:
21642159
* > - is between the end of the list and the last marker in the list,
@@ -2179,8 +2174,35 @@ private function run_adoption_agency_algorithm() {
21792174

21802175
// > If there is no such element, then return and instead act as described in the "any other end tag" entry above.
21812176
if ( null === $formatting_element ) {
2182-
$this->last_error = self::ERROR_UNSUPPORTED;
2183-
throw new WP_HTML_Unsupported_Exception( 'Cannot run adoption agency when "any other end tag" is required.' );
2177+
/*
2178+
* > Any other end tag
2179+
*/
2180+
2181+
/*
2182+
* Find the corresponding tag opener in the stack of open elements, if
2183+
* it exists before reaching a special element, which provides a kind
2184+
* of boundary in the stack. For example, a `</custom-tag>` should not
2185+
* close anything beyond its containing `P` or `DIV` element.
2186+
*/
2187+
foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) {
2188+
if ( $subject === $node->node_name ) {
2189+
break;
2190+
}
2191+
2192+
if ( self::is_special( $node->node_name ) ) {
2193+
// This is a parse error, ignore the token.
2194+
return;
2195+
}
2196+
}
2197+
2198+
$this->generate_implied_end_tags( $subject );
2199+
2200+
foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
2201+
$this->state->stack_of_open_elements->pop();
2202+
if ( $node === $item ) {
2203+
return;
2204+
}
2205+
}
21842206
}
21852207

21862208
// > 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.
@@ -2194,22 +2216,16 @@ private function run_adoption_agency_algorithm() {
21942216
return;
21952217
}
21962218

2219+
/*
2220+
* > If formatting element is not the current node, this is a parse error. (But do not return.)
2221+
*/
2222+
21972223
/*
21982224
* > Let furthest block be the topmost node in the stack of open elements that is lower in the stack
21992225
* > than formatting element, and is an element in the special category. There might not be one.
22002226
*/
2201-
$is_above_formatting_element = true;
2202-
$furthest_block = null;
2203-
foreach ( $this->state->stack_of_open_elements->walk_down() as $item ) {
2204-
if ( $is_above_formatting_element && $formatting_element->bookmark_name !== $item->bookmark_name ) {
2205-
continue;
2206-
}
2207-
2208-
if ( $is_above_formatting_element ) {
2209-
$is_above_formatting_element = false;
2210-
continue;
2211-
}
2212-
2227+
$furthest_block = null;
2228+
foreach ( $this->state->stack_of_open_elements->walk_down( $formatting_element ) as $item ) {
22132229
if ( self::is_special( $item->node_name ) ) {
22142230
$furthest_block = $item;
22152231
break;
@@ -2225,19 +2241,87 @@ private function run_adoption_agency_algorithm() {
22252241
foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
22262242
$this->state->stack_of_open_elements->pop();
22272243

2228-
if ( $formatting_element->bookmark_name === $item->bookmark_name ) {
2244+
if ( $formatting_element === $item ) {
22292245
$this->state->active_formatting_elements->remove_node( $formatting_element );
22302246
return;
22312247
}
22322248
}
22332249
}
22342250

2251+
/*
2252+
* > Let common ancestor be the element immediately above formatting element in the stack of open elements.
2253+
*/
2254+
$common_ancestor = null;
2255+
foreach ( $this->state->stack_of_open_elements->walk_up( $formatting_element ) as $item ) {
2256+
$common_ancestor = $item;
2257+
break;
2258+
}
2259+
2260+
/*
2261+
* 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.
2262+
*/
2263+
$formatting_element_index = 0;
2264+
foreach ( $this->state->active_formatting_elements->walk_down() as $item ) {
2265+
if ( $formatting_element === $item ) {
2266+
break;
2267+
}
2268+
2269+
++$formatting_element_index;
2270+
}
2271+
2272+
/*
2273+
* > Let node and last node be furthest block.
2274+
*/
2275+
$node = $furthest_block;
2276+
$last_node = $furthest_block;
2277+
2278+
$inner_loop_counter = 0;
2279+
while ( $budget-- > 0 ) {
2280+
++$inner_loop_counter;
2281+
2282+
if ( $this->state->stack_of_open_elements->contains_node( $node ) ) {
2283+
foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $item ) {
2284+
$node = $item;
2285+
break;
2286+
}
2287+
} else {
2288+
$this->last_error = self::ERROR_UNSUPPORTED;
2289+
throw new WP_HTML_Unsupported_Exception( 'Cannot adjust node pointer above removed node.' );
2290+
}
2291+
2292+
if ( $formatting_element === $node ) {
2293+
break;
2294+
}
2295+
2296+
if ( $inner_loop_counter > 3 && $this->state->active_formatting_elements->contains_node( $node ) ) {
2297+
$this->state->active_formatting_elements->remove_node( $node );
2298+
}
2299+
2300+
if ( ! $this->state->active_formatting_elements->contains_node( $node ) ) {
2301+
$this->state->stack_of_open_elements->remove_node( $node );
2302+
continue;
2303+
}
2304+
2305+
/*
2306+
* > Create an element for the token for which the element node was created,
2307+
* in the HTML namespace, with common ancestor as the intended parent;
2308+
* replace the entry for node in the list of active formatting elements
2309+
* with an entry for the new element, replace the entry for node in the
2310+
* stack of open elements with an entry for the new element, and let node
2311+
* be the new element.
2312+
*/
2313+
$this->last_error = self::ERROR_UNSUPPORTED;
2314+
throw new WP_HTML_Unsupported_Exception( 'Cannot create and reference new element for which no token exists.' );
2315+
}
2316+
2317+
/*
2318+
* > Insert whatever last node ended up being in the previous step at the appropriate
2319+
* > palce for inserting a node, but using common ancestor as the override target.
2320+
*/
2321+
22352322
$this->last_error = self::ERROR_UNSUPPORTED;
2236-
throw new WP_HTML_Unsupported_Exception( 'Cannot extract common ancestor in adoption agency algorithm.' );
2323+
throw new WP_HTML_Unsupported_Exception( 'Cannot create and reference new element for which no token exists.' );
22372324
}
2238-
2239-
$this->last_error = self::ERROR_UNSUPPORTED;
2240-
throw new WP_HTML_Unsupported_Exception( 'Cannot run adoption agency when looping required.' );
22412325
}
22422326

22432327
/**

0 commit comments

Comments
 (0)