diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php
index 910c1f24f1b18..34443a50bb5dd 100644
--- a/src/wp-includes/html-api/class-wp-html-processor.php
+++ b/src/wp-includes/html-api/class-wp-html-processor.php
@@ -102,17 +102,17 @@
* - Containers: ADDRESS, BLOCKQUOTE, DETAILS, DIALOG, DIV, FOOTER, HEADER, MAIN, MENU, SPAN, SUMMARY.
* - Custom elements: All custom elements are supported. :)
* - Form elements: BUTTON, DATALIST, FIELDSET, LABEL, LEGEND, METER, PROGRESS, SEARCH.
- * - Formatting elements: B, BIG, CODE, EM, FONT, I, SMALL, STRIKE, STRONG, TT, U.
+ * - Formatting elements: B, BIG, CODE, EM, FONT, I, SMALL, STRIKE, STRONG, TT, U, WBR.
* - Heading elements: H1, H2, H3, H4, H5, H6, HGROUP.
* - Links: A.
* - Lists: DD, DL, DT, LI, OL, LI.
- * - Media elements: AUDIO, CANVAS, FIGCAPTION, FIGURE, IMG, MAP, PICTURE, VIDEO.
- * - Paragraph: P.
- * - Phrasing elements: ABBR, BDI, BDO, CITE, DATA, DEL, DFN, INS, MARK, OUTPUT, Q, SAMP, SUB, SUP, TIME, VAR.
+ * - Media elements: AUDIO, CANVAS, EMBED, FIGCAPTION, FIGURE, IMG, MAP, PICTURE, VIDEO.
+ * - Paragraph: BR, P.
+ * - Phrasing elements: AREA, ABBR, BDI, BDO, CITE, DATA, DEL, DFN, INS, MARK, OUTPUT, Q, SAMP, SUB, SUP, TIME, VAR.
* - Sectioning elements: ARTICLE, ASIDE, HR, NAV, SECTION.
* - Templating elements: SLOT.
* - Text decoration: RUBY.
- * - Deprecated elements: ACRONYM, BLINK, CENTER, DIR, ISINDEX, MULTICOL, NEXTID, SPACER.
+ * - Deprecated elements: ACRONYM, BLINK, CENTER, DIR, ISINDEX, KEYGEN, MULTICOL, NEXTID, SPACER.
*
* ### Supported markup
*
@@ -934,12 +934,28 @@ private function step_in_body() {
$this->run_adoption_agency_algorithm();
return true;
+ /*
+ * > An end tag whose tag name is "br"
+ * > Parse error. Drop the attributes from the token, and act as described in the next
+ * > entry; i.e. act as if this was a "br" start tag token with no attributes, rather
+ * > than the end tag token that it actually is.
+ */
+ case '-BR':
+ $this->last_error = self::ERROR_UNSUPPORTED;
+ throw new WP_HTML_Unsupported_Exception( 'Closing BR tags require unimplemented special handling.' );
+
/*
* > A start tag whose tag name is one of: "area", "br", "embed", "img", "keygen", "wbr"
*/
+ case '+AREA':
+ case '+BR':
+ case '+EMBED':
case '+IMG':
+ case '+KEYGEN':
+ case '+WBR':
$this->reconstruct_active_formatting_elements();
$this->insert_html_element( $this->state->current_token );
+ $this->state->frameset_ok = false;
return true;
/*
@@ -977,13 +993,11 @@ private function step_in_body() {
case 'BASEFONT':
case 'BGSOUND':
case 'BODY':
- case 'BR':
case 'CAPTION':
case 'COL':
case 'COLGROUP':
case 'DD':
case 'DT':
- case 'EMBED':
case 'FORM':
case 'FRAME':
case 'FRAMESET':
@@ -991,7 +1005,6 @@ private function step_in_body() {
case 'HTML':
case 'IFRAME':
case 'INPUT':
- case 'KEYGEN':
case 'LI':
case 'LINK':
case 'LISTING':
@@ -1031,7 +1044,6 @@ private function step_in_body() {
case 'TR':
case 'TRACK':
case 'UL':
- case 'WBR':
case 'XMP':
$this->last_error = self::ERROR_UNSUPPORTED;
throw new WP_HTML_Unsupported_Exception( "Cannot process {$tag_name} element." );
@@ -1692,6 +1704,7 @@ public static function is_void( $tag_name ) {
'IMG' === $tag_name ||
'INPUT' === $tag_name ||
'LINK' === $tag_name ||
+ 'KEYGEN' === $tag_name || // Obsolete but still treated as void.
'META' === $tag_name ||
'SOURCE' === $tag_name ||
'TRACK' === $tag_name ||
diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor.php b/tests/phpunit/tests/html-api/wpHtmlProcessor.php
index d1f0767e9ce13..c556d7e36a76f 100644
--- a/tests/phpunit/tests/html-api/wpHtmlProcessor.php
+++ b/tests/phpunit/tests/html-api/wpHtmlProcessor.php
@@ -132,6 +132,86 @@ public function test_fails_to_reconstruct_formatting_elements() {
$this->assertFalse( $p->next_tag( 'EM' ), 'Should have aborted before finding second EM as it required reconstructing the first EM.' );
}
+ /**
+ * Ensure non-nesting tags do not nest.
+ *
+ * @ticket 60283
+ *
+ * @covers WP_HTML_Processor::step_in_body
+ * @covers WP_HTML_Processor::is_void
+ *
+ * @dataProvider data_void_tags
+ *
+ * @param string $tag_name Name of void tag under test.
+ */
+ public function test_cannot_nest_void_tags( $tag_name ) {
+ $processor = WP_HTML_Processor::create_fragment( "<{$tag_name}>
" );
+
+ /*
+ * This HTML represents the same as the following HTML,
+ * assuming that it were provided `
![]()
` as the tag:
+ *
+ *
+ *
+ *
![]()
+ *
+ *
+ *
+ */
+
+ $found_tag = $processor->next_tag();
+
+ if ( WP_HTML_Processor::ERROR_UNSUPPORTED === $processor->get_last_error() ) {
+ $this->markTestSkipped( "Tag {$tag_name} is not supported." );
+ }
+
+ $this->assertTrue(
+ $found_tag,
+ "Could not find first {$tag_name}."
+ );
+
+ $this->assertSame(
+ array( 'HTML', 'BODY', $tag_name ),
+ $processor->get_breadcrumbs(),
+ 'Found incorrect nesting of first element.'
+ );
+
+ $this->assertTrue(
+ $processor->next_tag(),
+ 'Should have found the DIV as the second tag.'
+ );
+
+ $this->assertSame(
+ array( 'HTML', 'BODY', 'DIV' ),
+ $processor->get_breadcrumbs(),
+ "DIV should have been a sibling of the {$tag_name}."
+ );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_void_tags() {
+ return array(
+ 'AREA' => array( 'AREA' ),
+ 'BASE' => array( 'BASE' ),
+ 'BR' => array( 'BR' ),
+ 'COL' => array( 'COL' ),
+ 'EMBED' => array( 'EMBED' ),
+ 'HR' => array( 'HR' ),
+ 'IMG' => array( 'IMG' ),
+ 'INPUT' => array( 'INPUT' ),
+ 'KEYGEN' => array( 'KEYGEN' ),
+ 'LINK' => array( 'LINK' ),
+ 'META' => array( 'META' ),
+ 'SOURCE' => array( 'SOURCE' ),
+ 'TRACK' => array( 'TRACK' ),
+ 'WBR' => array( 'WBR' ),
+ );
+ }
+
/**
* Ensures that special handling of unsupported tags is cleaned up
* as handling is implemented. Otherwise there's risk of leaving special
@@ -159,16 +239,13 @@ public function test_step_in_body_fails_on_unsupported_tags( $tag_name ) {
public function data_unsupported_special_in_body_tags() {
return array(
'APPLET' => array( 'APPLET' ),
- 'AREA' => array( 'AREA' ),
'BASE' => array( 'BASE' ),
'BASEFONT' => array( 'BASEFONT' ),
'BGSOUND' => array( 'BGSOUND' ),
'BODY' => array( 'BODY' ),
- 'BR' => array( 'BR' ),
'CAPTION' => array( 'CAPTION' ),
'COL' => array( 'COL' ),
'COLGROUP' => array( 'COLGROUP' ),
- 'EMBED' => array( 'EMBED' ),
'FORM' => array( 'FORM' ),
'FRAME' => array( 'FRAME' ),
'FRAMESET' => array( 'FRAMESET' ),
@@ -176,7 +253,6 @@ public function data_unsupported_special_in_body_tags() {
'HTML' => array( 'HTML' ),
'IFRAME' => array( 'IFRAME' ),
'INPUT' => array( 'INPUT' ),
- 'KEYGEN' => array( 'KEYGEN' ),
'LINK' => array( 'LINK' ),
'LISTING' => array( 'LISTING' ),
'MARQUEE' => array( 'MARQUEE' ),
@@ -213,7 +289,6 @@ public function data_unsupported_special_in_body_tags() {
'TITLE' => array( 'TITLE' ),
'TR' => array( 'TR' ),
'TRACK' => array( 'TRACK' ),
- 'WBR' => array( 'WBR' ),
'XMP' => array( 'XMP' ),
);
}
diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php b/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php
index c16d38146acb3..7da3730910cf5 100644
--- a/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php
+++ b/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php
@@ -162,15 +162,12 @@ public function test_fails_when_encountering_unsupported_tag( $html ) {
public function data_unsupported_elements() {
$unsupported_elements = array(
'APPLET', // Deprecated.
- 'AREA',
'BASE',
'BGSOUND', // Deprecated; self-closing if self-closing flag provided, otherwise normal.
'BODY',
- 'BR',
'CAPTION',
'COL',
'COLGROUP',
- 'EMBED',
'FORM',
'FRAME',
'FRAMESET',
@@ -178,7 +175,6 @@ public function data_unsupported_elements() {
'HTML',
'IFRAME',
'INPUT',
- 'KEYGEN', // Deprecated; void.
'LINK',
'LISTING', // Deprecated, use PRE instead.
'MARQUEE', // Deprecated.
@@ -213,7 +209,6 @@ public function data_unsupported_elements() {
'TITLE',
'TR',
'TRACK',
- 'WBR',
'XMP', // Deprecated, use PRE instead.
);
diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php b/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php
index ba9640f2efdc7..c0bd9d9c75084 100644
--- a/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php
+++ b/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php
@@ -392,4 +392,29 @@ public function test_in_body_any_other_end_tag_with_unclosed_non_special_element
$this->assertSame( 'DIV', $p->get_tag(), "Expected to find DIV element, but found {$p->get_tag()} instead." );
$this->assertSame( array( 'HTML', 'BODY', 'DIV', 'DIV' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting: SPAN should be closed and DIV should be its sibling.' );
}
+
+ /**
+ * Ensures that support isn't accidentally partially added for the closing BR tag ``.
+ *
+ * This tag closer has special rules and support shouldn't be added without implementing full support.
+ *
+ * > An end tag whose tag name is "br"
+ * > Parse error. Drop the attributes from the token, and act as described in the next entry;
+ * > i.e. act as if this was a "br" start tag token with no attributes, rather than the end
+ * > tag token that it actually is.
+ *
+ * When this handling is implemented, this test should be removed. It's not incorporated
+ * into the existing unsupported tag behavior test because the opening tag is supported;
+ * only the closing tag isn't.
+ *
+ * @covers WP_HTML_Processor::step_in_body
+ *
+ * @ticket 60283
+ */
+ public function test_br_end_tag_unsupported() {
+ $p = WP_HTML_Processor::create_fragment( '' );
+
+ $this->assertFalse( $p->next_tag(), 'Found a BR tag that should not be handled.' );
+ $this->assertSame( WP_HTML_Processor::ERROR_UNSUPPORTED, $p->get_last_error() );
+ }
}