Skip to content
Closed
29 changes: 28 additions & 1 deletion src/wp-includes/html-api/class-wp-html-tag-processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -3337,7 +3337,34 @@ public function has_self_closing_flag(): bool {
* <figure />
* ^ this appears one character before the end of the closing ">".
*/
return '/' === $this->html[ $this->token_starts_at + $this->token_length - 2 ];
$self_closing_flag_at = $this->token_starts_at + $this->token_length - 2;
if ( '/' !== $this->html[ $self_closing_flag_at ] ) {
return false;
}

foreach ( $this->attributes as $attribute ) {
$attribute_ends_at = $attribute->start + $attribute->length;
if (
$self_closing_flag_at >= $attribute->start &&
$self_closing_flag_at < $attribute_ends_at
) {
return false;
}
}

foreach ( $this->duplicate_attributes ?? array() as $duplicate_attributes ) {
foreach ( $duplicate_attributes as $attribute ) {
$attribute_ends_at = $attribute->start + $attribute->length;
if (
$self_closing_flag_at >= $attribute->start &&
$self_closing_flag_at < $attribute_ends_at
) {
return false;
}
}
}

return true;
}

/**
Expand Down
32 changes: 32 additions & 0 deletions tests/phpunit/tests/html-api/wpHtmlProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,38 @@ public function test_expects_closer_foreign_content_self_closing() {
$this->assertTrue( $processor->expects_closer() );
}

/**
* Ensures a trailing slash in an unquoted attribute value does not close foreign content.
*
* @ticket 61576
*/
public function test_trailing_slash_in_unquoted_attribute_value_does_not_self_close_foreign_content() {
$processor = WP_HTML_Processor::create_fragment( '<math><mi disabled=abc/>text</math>' );

$this->assertTrue( $processor->next_tag( 'MI' ), 'Could not find MI tag: check test setup.' );
$this->assertSame(
'abc/',
$processor->get_attribute( 'disabled' ),
'Trailing slash in unquoted attribute value should belong to the attribute value.'
);
$this->assertFalse(
$processor->has_self_closing_flag(),
'Trailing slash in unquoted attribute value should not be interpreted as a self-closing flag.'
);
$this->assertTrue(
$processor->expects_closer(),
'MI with a trailing slash in an unquoted attribute value should still expect a closer.'
);

$this->assertTrue( $processor->next_token(), 'Could not find text following MI tag: check test setup.' );
$this->assertSame( '#text', $processor->get_token_name(), 'Should have found the text node following the MI tag.' );
$this->assertSame(
array( 'HTML', 'BODY', 'MATH', 'MI', '#text' ),
$processor->get_breadcrumbs(),
'Text following the MI tag should remain inside the MI element.'
);
}

/**
* Ensures that expects_closer works for void-like elements in foreign content.
*
Expand Down
27 changes: 26 additions & 1 deletion tests/phpunit/tests/html-api/wpHtmlTagProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,38 @@ public static function data_has_self_closing_flag() {
'No self-closing flag on a foreign element' => array( '<circle>', false ),
// These involve syntax peculiarities.
'Self-closing flag after extra spaces' => array( '<div />', true ),
'Self-closing flag after attribute' => array( '<div id=test/>', true ),
'Self-closing flag after attribute' => array( '<div id=test />', true ),
'Slash inside unquoted attribute value' => array( '<div id=test/>', false ),
'Self-closing flag after quoted attribute' => array( '<div id="test"/>', true ),
'Self-closing flag after boolean attribute' => array( '<div enabled/>', true ),
'Boolean attribute that looks like a self-closer' => array( '<div / >', false ),
);
}

/**
* Ensures a trailing slash in an unquoted attribute value is part of the value.
*
* @ticket 61576
*
* @covers WP_HTML_Tag_Processor::get_attribute
* @covers WP_HTML_Tag_Processor::has_self_closing_flag
*/
public function test_trailing_slash_in_unquoted_attribute_value_is_not_self_closing_flag() {
$processor = new WP_HTML_Tag_Processor( '<mi disabled=abc/>text' );
$this->assertTrue( $processor->next_tag(), 'Could not find MI tag: check test setup.' );

$this->assertSame(
'abc/',
$processor->get_attribute( 'disabled' ),
'Trailing slash in unquoted attribute value should belong to the attribute value.'
);

$this->assertFalse(
$processor->has_self_closing_flag(),
'Trailing slash in unquoted attribute value should not be interpreted as a self-closing flag.'
);
}

/**
* @ticket 56299
*
Expand Down
Loading