Skip to content

Commit 1dd112a

Browse files
committed
Merge PR #41: HTML API: Fix nested anchors in MathML text integration points
2 parents dfa18d5 + b19c465 commit 1dd112a

2 files changed

Lines changed: 60 additions & 5 deletions

File tree

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2945,15 +2945,25 @@ private function step_in_body(): bool {
29452945
$this->run_adoption_agency_algorithm();
29462946
$this->state->active_formatting_elements->remove_node( $item );
29472947
$is_current_node = $item === $this->state->stack_of_open_elements->current_node();
2948-
if ( $this->state->stack_of_open_elements->remove_node( $item ) && ! $is_current_node ) {
2949-
$breadcrumb_depth = count( $this->breadcrumbs );
2950-
while ( 0 < $breadcrumb_depth && $this->breadcrumbs[ $breadcrumb_depth - 1 ] !== $item->node_name ) {
2951-
--$breadcrumb_depth;
2948+
2949+
/*
2950+
* The removed node's breadcrumb sits at its position in the
2951+
* stack of open elements: one crumb for each open element at
2952+
* or below it. Fragment parsers carry an extra crumb for the
2953+
* context node, which never appears on the stack.
2954+
*/
2955+
$stack_position = 0;
2956+
foreach ( $this->state->stack_of_open_elements->walk_down() as $node ) {
2957+
++$stack_position;
2958+
if ( $node === $item ) {
2959+
break;
29522960
}
2961+
}
29532962

2963+
if ( $this->state->stack_of_open_elements->remove_node( $item ) && ! $is_current_node ) {
29542964
$this->non_lifo_breadcrumb_removals[] = array(
29552965
'token' => $item,
2956-
'breadcrumb_depth' => $breadcrumb_depth,
2966+
'breadcrumb_depth' => isset( $this->context_node ) ? $stack_position + 1 : $stack_position,
29572967
);
29582968
}
29592969
break 2;

tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,51 @@ public function test_removes_outer_anchor_breadcrumb_after_mathml_text_integrati
492492
);
493493
}
494494

495+
/**
496+
* Ensures that a removed outer A element's breadcrumb is not confused with
497+
* a same-named foreign element between it and the integration point.
498+
*
499+
* Foreign A elements never participate in the active formatting elements,
500+
* so the removed node is the outer HTML A element, not the foreign one.
501+
*
502+
* @ticket 61576
503+
*
504+
* @covers WP_HTML_Processor::get_breadcrumbs
505+
* @covers WP_HTML_Processor::matches_breadcrumbs
506+
*
507+
* @dataProvider data_intervening_foreign_anchor_html
508+
*
509+
* @param string $html HTML with a foreign A element between the removed outer A element and the integration point.
510+
*/
511+
public function test_removes_outer_anchor_breadcrumb_with_intervening_foreign_anchor( string $html ) {
512+
$processor = WP_HTML_Processor::create_fragment( $html );
513+
514+
$this->assertTrue( $processor->next_tag( 'SPAN' ), 'Failed to find the SPAN element after the foreign subtree.' );
515+
516+
$this->assertSame(
517+
array( 'HTML', 'BODY', 'SPAN' ),
518+
$processor->get_breadcrumbs(),
519+
'The SPAN element after the foreign subtree should not remain nested inside the removed outer A element.'
520+
);
521+
522+
$this->assertFalse(
523+
$processor->matches_breadcrumbs( array( 'A', 'SPAN' ) ),
524+
'The SPAN element should not match breadcrumbs inside the removed outer A element.'
525+
);
526+
}
527+
528+
/**
529+
* Data provider.
530+
*
531+
* @return array[]
532+
*/
533+
public static function data_intervening_foreign_anchor_html() {
534+
return array(
535+
'MathML A before text integration point' => array( '<a><math><a><mtext>x<a>y</a></mtext></a></math>z<span>t' ),
536+
'SVG A before integration point' => array( '<a><svg><a><foreignObject>x<a>y</a></foreignObject></a></svg>z<span>t' ),
537+
);
538+
}
539+
495540
/**
496541
* Ensures that an outer A element removed from the stack of open elements
497542
* remains visitable as a virtual closer after its existing child subtree closes.

0 commit comments

Comments
 (0)