<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[57264] trunk: HTML API: Add support for list elements.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/57264">57264</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/57264","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>dmsnell</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2024-01-10 14:03:57 +0000 (Wed, 10 Jan 2024)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>HTML API: Add support for list elements.

Adds support for the following HTML elements to the HTML Processor:

 - LI, OL, UL.
 - DD, DL, DT.

Previously, these elements were not supported and the HTML Processor would bail when encountering them.
With this patch it will proceed to parse an HTML document when encountering those tags as long as other normal conditions don't cause it to bail (such as complicated format reconstruction).

Props audrasjb, jonsurrell, bernhard-reiter.
Fixes <a href="https://core.trac.wordpress.org/ticket/60215">#60215</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkphpcsxmldist">trunk/phpcs.xml.dist</a></li>
<li><a href="#trunksrcwpincludeshtmlapiclasswphtmlopenelementsphp">trunk/src/wp-includes/html-api/class-wp-html-open-elements.php</a></li>
<li><a href="#trunksrcwpincludeshtmlapiclasswphtmlprocessorphp">trunk/src/wp-includes/html-api/class-wp-html-processor.php</a></li>
<li><a href="#trunktestsphpunittestshtmlapiwpHtmlProcessorphp">trunk/tests/phpunit/tests/html-api/wpHtmlProcessor.php</a></li>
<li><a href="#trunktestsphpunittestshtmlapiwpHtmlProcessorBreadcrumbsphp">trunk/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php</a></li>
<li><a href="#trunktestsphpunittestshtmlapiwpHtmlProcessorSemanticRulesphp">trunk/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php</a></li>
<li><a href="#trunktestsphpunittestshtmlapiwpHtmlSupportRequiredHtmlProcessorphp">trunk/tests/phpunit/tests/html-api/wpHtmlSupportRequiredHtmlProcessor.php</a></li>
<li><a href="#trunktestsphpunittestshtmlapiwpHtmlSupportRequiredOpenElementsphp">trunk/tests/phpunit/tests/html-api/wpHtmlSupportRequiredOpenElements.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestsphpunittestshtmlapiwpHtmlProcessorSemanticRulesListElementsphp">trunk/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRulesListElements.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkphpcsxmldist"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/phpcs.xml.dist</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/phpcs.xml.dist      2024-01-10 11:55:04 UTC (rev 57263)
+++ trunk/phpcs.xml.dist        2024-01-10 14:03:57 UTC (rev 57264)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -250,6 +250,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">                <exclude-pattern>/wp-tests-config-sample\.php</exclude-pattern>
</span><span class="cx" style="display: block; padding: 0 10px">        </rule>
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        <!-- Exclude forbidding goto in the HTML Processor, which mimics algorithms that are written
+            this way in the HTML specification, and these particular algorithms are complex and
+            highly imperative. Avoiding the goto introduces a number of risks that could make it
+            more difficult to maintain the relationship to the standard, lead to subtle differences
+            in the parsing, and distance the code from its standard. -->
+       <rule ref="Generic.PHP.DiscourageGoto.Found">
+               <exclude-pattern>/wp-includes/html-api/class-wp-html-processor\.php</exclude-pattern>
+       </rule>
+
</ins><span class="cx" style="display: block; padding: 0 10px">         <!-- Exclude sample config from modernization to prevent breaking CI workflows based on WP-CLI scaffold.
</span><span class="cx" style="display: block; padding: 0 10px">                 See: https://core.trac.wordpress.org/ticket/48082#comment:16 -->
</span><span class="cx" style="display: block; padding: 0 10px">        <rule ref="Modernize.FunctionCalls.Dirname.FileConstant">
</span></span></pre></div>
<a id="trunksrcwpincludeshtmlapiclasswphtmlopenelementsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/html-api/class-wp-html-open-elements.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/html-api/class-wp-html-open-elements.php    2024-01-10 11:55:04 UTC (rev 57263)
+++ trunk/src/wp-includes/html-api/class-wp-html-open-elements.php      2024-01-10 14:03:57 UTC (rev 57264)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -129,7 +129,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( in_array( $node->node_name, $termination_list, true ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                return true;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         return false;
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -166,18 +166,22 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * Returns whether a particular element is in list item scope.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 6.4.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 6.5.0 Implemented: no longer throws on every invocation.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @see https://html.spec.whatwg.org/#has-an-element-in-list-item-scope
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @throws WP_HTML_Unsupported_Exception Always until this function is implemented.
-        *
</del><span class="cx" style="display: block; padding: 0 10px">          * @param string $tag_name Name of tag to check.
</span><span class="cx" style="display: block; padding: 0 10px">         * @return bool Whether given element is in scope.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function has_element_in_list_item_scope( $tag_name ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                throw new WP_HTML_Unsupported_Exception( 'Cannot process elements depending on list item scope.' );
-
-               return false; // The linter requires this unreachable code until the function is implemented and can return.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         return $this->has_element_in_specific_scope(
+                       $tag_name,
+                       array(
+                               // There are more elements that belong here which aren't currently supported.
+                               'OL',
+                               'UL',
+                       )
+               );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -375,10 +379,22 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * see WP_HTML_Open_Elements::walk_down().
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 6.4.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 6.5.0 Accepts $above_this_node to start traversal above a given node, if it exists.
+        *
+        * @param ?WP_HTML_Token $above_this_node Start traversing above this node, if provided and if the node exists.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public function walk_up() {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function walk_up( $above_this_node = null ) {
+               $has_found_node = null === $above_this_node;
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 for ( $i = count( $this->stack ) - 1; $i >= 0; $i-- ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        yield $this->stack[ $i ];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $node = $this->stack[ $i ];
+
+                       if ( ! $has_found_node ) {
+                               $has_found_node = $node === $above_this_node;
+                               continue;
+                       }
+
+                       yield $node;
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="trunksrcwpincludeshtmlapiclasswphtmlprocessorphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/html-api/class-wp-html-processor.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/html-api/class-wp-html-processor.php        2024-01-10 11:55:04 UTC (rev 57263)
+++ trunk/src/wp-includes/html-api/class-wp-html-processor.php  2024-01-10 14:03:57 UTC (rev 57264)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -105,7 +105,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  *  - Formatting elements: B, BIG, CODE, EM, FONT, I, SMALL, STRIKE, STRONG, TT, U.
</span><span class="cx" style="display: block; padding: 0 10px">  *  - Heading elements: H1, H2, H3, H4, H5, H6, HGROUP.
</span><span class="cx" style="display: block; padding: 0 10px">  *  - Links: A.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- *  - Lists: DL.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ *  - Lists: DD, DL, DT, LI, OL, LI.
</ins><span class="cx" style="display: block; padding: 0 10px">  *  - Media elements: AUDIO, CANVAS, FIGCAPTION, FIGURE, IMG, MAP, PICTURE, VIDEO.
</span><span class="cx" style="display: block; padding: 0 10px">  *  - Paragraph: P.
</span><span class="cx" style="display: block; padding: 0 10px">  *  - Phrasing elements: ABBR, BDI, BDO, CITE, DATA, DEL, DFN, INS, MARK, OUTPUT, Q, SAMP, SUB, SUP, TIME, VAR.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -648,10 +648,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        case '+MAIN':
</span><span class="cx" style="display: block; padding: 0 10px">                        case '+MENU':
</span><span class="cx" style="display: block; padding: 0 10px">                        case '+NAV':
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        case '+OL':
</ins><span class="cx" style="display: block; padding: 0 10px">                         case '+P':
</span><span class="cx" style="display: block; padding: 0 10px">                        case '+SEARCH':
</span><span class="cx" style="display: block; padding: 0 10px">                        case '+SECTION':
</span><span class="cx" style="display: block; padding: 0 10px">                        case '+SUMMARY':
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        case '+UL':
</ins><span class="cx" style="display: block; padding: 0 10px">                                 if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                        $this->close_a_p_element();
</span><span class="cx" style="display: block; padding: 0 10px">                                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -685,9 +687,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        case '-MAIN':
</span><span class="cx" style="display: block; padding: 0 10px">                        case '-MENU':
</span><span class="cx" style="display: block; padding: 0 10px">                        case '-NAV':
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        case '-OL':
</ins><span class="cx" style="display: block; padding: 0 10px">                         case '-SEARCH':
</span><span class="cx" style="display: block; padding: 0 10px">                        case '-SECTION':
</span><span class="cx" style="display: block; padding: 0 10px">                        case '-SUMMARY':
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        case '-UL':
</ins><span class="cx" style="display: block; padding: 0 10px">                                 if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $tag_name ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                        // @todo Report parse error.
</span><span class="cx" style="display: block; padding: 0 10px">                                        // Ignore the token.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -756,6 +760,109 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                return true;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        /*
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         * > A start tag whose tag name is "li"
+                        * > A start tag whose tag name is one of: "dd", "dt"
+                        */
+                       case '+DD':
+                       case '+DT':
+                       case '+LI':
+                               $this->state->frameset_ok = false;
+                               $node                     = $this->state->stack_of_open_elements->current_node();
+                               $is_li                    = 'LI' === $tag_name;
+
+                               in_body_list_loop:
+                               /*
+                                * The logic for LI and DT/DD is the same except for one point: LI elements _only_
+                                * close other LI elements, but a DT or DD element closes _any_ open DT or DD element.
+                                */
+                               if ( $is_li ? 'LI' === $node->node_name : ( 'DD' === $node->node_name || 'DT' === $node->node_name ) ) {
+                                       $node_name = $is_li ? 'LI' : $node->node_name;
+                                       $this->generate_implied_end_tags( $node_name );
+                                       if ( $node_name !== $this->state->stack_of_open_elements->current_node()->node_name ) {
+                                               // @todo Indicate a parse error once it's possible. This error does not impact the logic here.
+                                       }
+
+                                       $this->state->stack_of_open_elements->pop_until( $node_name );
+                                       goto in_body_list_done;
+                               }
+
+                               if (
+                                       'ADDRESS' !== $node->node_name &&
+                                       'DIV' !== $node->node_name &&
+                                       'P' !== $node->node_name &&
+                                       $this->is_special( $node->node_name )
+                               ) {
+                                       /*
+                                        * > If node is in the special category, but is not an address, div,
+                                        * > or p element, then jump to the step labeled done below.
+                                        */
+                                       goto in_body_list_done;
+                               } else {
+                                       /*
+                                        * > Otherwise, set node to the previous entry in the stack of open elements
+                                        * > and return to the step labeled loop.
+                                        */
+                                       foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $item ) {
+                                               $node = $item;
+                                               break;
+                                       }
+                                       goto in_body_list_loop;
+                               }
+
+                               in_body_list_done:
+                               if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
+                                       $this->close_a_p_element();
+                               }
+
+                               $this->insert_html_element( $this->state->current_token );
+                               return true;
+
+                       /*
+                        * > An end tag whose tag name is "li"
+                        * > An end tag whose tag name is one of: "dd", "dt"
+                        */
+                       case '-DD':
+                       case '-DT':
+                       case '-LI':
+                               if (
+                                       /*
+                                        * An end tag whose tag name is "li":
+                                        * If the stack of open elements does not have an li element in list item scope,
+                                        * then this is a parse error; ignore the token.
+                                        */
+                                       (
+                                               'LI' === $tag_name &&
+                                               ! $this->state->stack_of_open_elements->has_element_in_list_item_scope( 'LI' )
+                                       ) ||
+                                       /*
+                                        * An end tag whose tag name is one of: "dd", "dt":
+                                        * If the stack of open elements does not have an element in scope that is an
+                                        * HTML element with the same tag name as that of the token, then this is a
+                                        * parse error; ignore the token.
+                                        */
+                                       (
+                                               'LI' !== $tag_name &&
+                                               ! $this->state->stack_of_open_elements->has_element_in_scope( $tag_name )
+                                       )
+                               ) {
+                                       /*
+                                        * This is a parse error, ignore the token.
+                                        *
+                                        * @todo Indicate a parse error once it's possible.
+                                        */
+                                       return $this->step();
+                               }
+
+                               $this->generate_implied_end_tags( $tag_name );
+
+                               if ( $tag_name !== $this->state->stack_of_open_elements->current_node()->node_name ) {
+                                       // @todo Indicate a parse error once it's possible. This error does not impact the logic here.
+                               }
+
+                               $this->state->stack_of_open_elements->pop_until( $tag_name );
+                               return true;
+
+                       /*
</ins><span class="cx" style="display: block; padding: 0 10px">                          * > An end tag whose tag name is "p"
</span><span class="cx" style="display: block; padding: 0 10px">                         */
</span><span class="cx" style="display: block; padding: 0 10px">                        case '-P':
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1223,6 +1330,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        private function generate_implied_end_tags( $except_for_this_element = null ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $elements_with_implied_end_tags = array(
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'DD',
+                       'DT',
+                       'LI',
</ins><span class="cx" style="display: block; padding: 0 10px">                         'P',
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1248,6 +1358,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        private function generate_implied_end_tags_thoroughly() {
</span><span class="cx" style="display: block; padding: 0 10px">                $elements_with_implied_end_tags = array(
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'DD',
+                       'DT',
+                       'LI',
</ins><span class="cx" style="display: block; padding: 0 10px">                         'P',
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="trunktestsphpunittestshtmlapiwpHtmlProcessorphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/html-api/wpHtmlProcessor.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/html-api/wpHtmlProcessor.php    2024-01-10 11:55:04 UTC (rev 57263)
+++ trunk/tests/phpunit/tests/html-api/wpHtmlProcessor.php      2024-01-10 14:03:57 UTC (rev 57264)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -168,8 +168,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'CAPTION'   => array( 'CAPTION' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'COL'       => array( 'COL' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'COLGROUP'  => array( 'COLGROUP' ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'DD'        => array( 'DD' ),
-                       'DT'        => array( 'DT' ),
</del><span class="cx" style="display: block; padding: 0 10px">                         'EMBED'     => array( 'EMBED' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'FORM'      => array( 'FORM' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'FRAME'     => array( 'FRAME' ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -180,7 +178,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'IFRAME'    => array( 'IFRAME' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'INPUT'     => array( 'INPUT' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'KEYGEN'    => array( 'KEYGEN' ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'LI'        => array( 'LI' ),
</del><span class="cx" style="display: block; padding: 0 10px">                         'LINK'      => array( 'LINK' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'LISTING'   => array( 'LISTING' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'MARQUEE'   => array( 'MARQUEE' ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -191,7 +188,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'NOFRAMES'  => array( 'NOFRAMES' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'NOSCRIPT'  => array( 'NOSCRIPT' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'OBJECT'    => array( 'OBJECT' ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'OL'        => array( 'OL' ),
</del><span class="cx" style="display: block; padding: 0 10px">                         'OPTGROUP'  => array( 'OPTGROUP' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'OPTION'    => array( 'OPTION' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'PARAM'     => array( 'PARAM' ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -218,7 +214,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'TITLE'     => array( 'TITLE' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'TR'        => array( 'TR' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'TRACK'     => array( 'TRACK' ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'UL'        => array( 'UL' ),
</del><span class="cx" style="display: block; padding: 0 10px">                         'WBR'       => array( 'WBR' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'XMP'       => array( 'XMP' ),
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span></span></pre></div>
<a id="trunktestsphpunittestshtmlapiwpHtmlProcessorBreadcrumbsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php 2024-01-10 11:55:04 UTC (rev 57263)
+++ trunk/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php   2024-01-10 14:03:57 UTC (rev 57264)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -38,7 +38,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $supported_elements = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'A',
</span><span class="cx" style="display: block; padding: 0 10px">                        'ABBR',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'ACRONYM', // Neutralized
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'ACRONYM', // Neutralized.
</ins><span class="cx" style="display: block; padding: 0 10px">                         'ADDRESS',
</span><span class="cx" style="display: block; padding: 0 10px">                        'ARTICLE',
</span><span class="cx" style="display: block; padding: 0 10px">                        'ASIDE',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -47,13 +47,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'BDI',
</span><span class="cx" style="display: block; padding: 0 10px">                        'BDO',
</span><span class="cx" style="display: block; padding: 0 10px">                        'BIG',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'BLINK', // Deprecated
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'BLINK', // Deprecated.
</ins><span class="cx" style="display: block; padding: 0 10px">                         'BUTTON',
</span><span class="cx" style="display: block; padding: 0 10px">                        'CANVAS',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'CENTER', // Neutralized
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'CENTER', // Neutralized.
</ins><span class="cx" style="display: block; padding: 0 10px">                         'CITE',
</span><span class="cx" style="display: block; padding: 0 10px">                        'CODE',
</span><span class="cx" style="display: block; padding: 0 10px">                        'DATA',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'DD',
</ins><span class="cx" style="display: block; padding: 0 10px">                         'DATALIST',
</span><span class="cx" style="display: block; padding: 0 10px">                        'DFN',
</span><span class="cx" style="display: block; padding: 0 10px">                        'DEL',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -62,6 +63,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'DIR',
</span><span class="cx" style="display: block; padding: 0 10px">                        'DIV',
</span><span class="cx" style="display: block; padding: 0 10px">                        'DL',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'DT',
</ins><span class="cx" style="display: block; padding: 0 10px">                         'EM',
</span><span class="cx" style="display: block; padding: 0 10px">                        'FIELDSET',
</span><span class="cx" style="display: block; padding: 0 10px">                        'FIGCAPTION',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -79,6 +81,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'I',
</span><span class="cx" style="display: block; padding: 0 10px">                        'IMG',
</span><span class="cx" style="display: block; padding: 0 10px">                        'INS',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'LI',
</ins><span class="cx" style="display: block; padding: 0 10px">                         'ISINDEX', // Deprecated
</span><span class="cx" style="display: block; padding: 0 10px">                        'KBD',
</span><span class="cx" style="display: block; padding: 0 10px">                        'LABEL',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -91,6 +94,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'MULTICOL', // Deprecated
</span><span class="cx" style="display: block; padding: 0 10px">                        'NAV',
</span><span class="cx" style="display: block; padding: 0 10px">                        'NEXTID', // Deprecated
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'OL',
</ins><span class="cx" style="display: block; padding: 0 10px">                         'OUTPUT',
</span><span class="cx" style="display: block; padding: 0 10px">                        'P',
</span><span class="cx" style="display: block; padding: 0 10px">                        'PICTURE',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -112,6 +116,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'TIME',
</span><span class="cx" style="display: block; padding: 0 10px">                        'TT',
</span><span class="cx" style="display: block; padding: 0 10px">                        'U',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'UL',
</ins><span class="cx" style="display: block; padding: 0 10px">                         'VAR',
</span><span class="cx" style="display: block; padding: 0 10px">                        'VIDEO',
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -156,7 +161,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function data_unsupported_elements() {
</span><span class="cx" style="display: block; padding: 0 10px">                $unsupported_elements = array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'APPLET', // Deprecated
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'APPLET', // Deprecated.
</ins><span class="cx" style="display: block; padding: 0 10px">                         'AREA',
</span><span class="cx" style="display: block; padding: 0 10px">                        'BASE',
</span><span class="cx" style="display: block; padding: 0 10px">                        'BGSOUND', // Deprecated; self-closing if self-closing flag provided, otherwise normal.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -165,8 +170,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'CAPTION',
</span><span class="cx" style="display: block; padding: 0 10px">                        'COL',
</span><span class="cx" style="display: block; padding: 0 10px">                        'COLGROUP',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'DD',
-                       'DT',
</del><span class="cx" style="display: block; padding: 0 10px">                         'EMBED',
</span><span class="cx" style="display: block; padding: 0 10px">                        'FORM',
</span><span class="cx" style="display: block; padding: 0 10px">                        'FRAME',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -176,27 +179,25 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'HTML',
</span><span class="cx" style="display: block; padding: 0 10px">                        'IFRAME',
</span><span class="cx" style="display: block; padding: 0 10px">                        'INPUT',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'KEYGEN', // Deprecated; void
-                       'LI',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'KEYGEN', // Deprecated; void.
</ins><span class="cx" style="display: block; padding: 0 10px">                         'LINK',
</span><span class="cx" style="display: block; padding: 0 10px">                        'LISTING', // Deprecated, use PRE instead.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'MARQUEE', // Deprecated
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'MARQUEE', // Deprecated.
</ins><span class="cx" style="display: block; padding: 0 10px">                         'MATH',
</span><span class="cx" style="display: block; padding: 0 10px">                        'META',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'NOBR', // Neutralized
-                       'NOEMBED', // Neutralized
-                       'NOFRAMES', // Neutralized
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'NOBR', // Neutralized.
+                       'NOEMBED', // Neutralized.
+                       'NOFRAMES', // Neutralized.
</ins><span class="cx" style="display: block; padding: 0 10px">                         'NOSCRIPT',
</span><span class="cx" style="display: block; padding: 0 10px">                        'OBJECT',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'OL',
</del><span class="cx" style="display: block; padding: 0 10px">                         'OPTGROUP',
</span><span class="cx" style="display: block; padding: 0 10px">                        'OPTION',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'PLAINTEXT', // Neutralized
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'PLAINTEXT', // Neutralized.
</ins><span class="cx" style="display: block; padding: 0 10px">                         'PRE',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'RB', // Neutralized
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'RB', // Neutralized.
</ins><span class="cx" style="display: block; padding: 0 10px">                         'RP',
</span><span class="cx" style="display: block; padding: 0 10px">                        'RT',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'RTC', // Neutralized
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'RTC', // Neutralized.
</ins><span class="cx" style="display: block; padding: 0 10px">                         'SCRIPT',
</span><span class="cx" style="display: block; padding: 0 10px">                        'SELECT',
</span><span class="cx" style="display: block; padding: 0 10px">                        'SOURCE',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -213,7 +214,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'TITLE',
</span><span class="cx" style="display: block; padding: 0 10px">                        'TR',
</span><span class="cx" style="display: block; padding: 0 10px">                        'TRACK',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'UL',
</del><span class="cx" style="display: block; padding: 0 10px">                         'WBR',
</span><span class="cx" style="display: block; padding: 0 10px">                        'XMP', // Deprecated, use PRE instead.
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -348,6 +348,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'MAIN inside MAIN inside SPAN'          => array( '<span><main><main target>', array( 'HTML', 'BODY', 'SPAN', 'MAIN', 'MAIN' ), 1 ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'MAIN next to unclosed P'               => array( '<p><main target>', array( 'HTML', 'BODY', 'MAIN' ), 1 ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'LI after unclosed LI'                  => array( '<li>one<li>two<li target>three', array( 'HTML', 'BODY', 'LI' ), 3 ),
+                       'LI in UL in LI'                        => array( '<ul><li>one<ul><li target>two', array( 'HTML', 'BODY', 'UL', 'LI', 'UL', 'LI' ), 1 ),
+                       'DD and DT mutually close, LI self-closes (dt 2)' => array( '<dd><dd><dt><dt target><dd><li><li>', array( 'HTML', 'BODY', 'DT' ), 2 ),
+                       'DD and DT mutually close, LI self-closes (dd 3)' => array( '<dd><dd><dt><dt><dd target><li><li>', array( 'HTML', 'BODY', 'DD' ), 3 ),
+                       'DD and DT mutually close, LI self-closes (li 1)' => array( '<dd><dd><dt><dt><dd><li target><li>', array( 'HTML', 'BODY', 'DD', 'LI' ), 1 ),
+                       'DD and DT mutually close, LI self-closes (li 2)' => array( '<dd><dd><dt><dt><dd><li><li target>', array( 'HTML', 'BODY', 'DD', 'LI' ), 2 ),
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // H1 - H6 close out _any_ H1 - H6 when encountering _any_ of H1 - H6, making this section surprising.
</span><span class="cx" style="display: block; padding: 0 10px">                        'EM inside H3 after unclosed P'         => array( '<p><h3><em target>Important Message</em></h3>', array( 'HTML', 'BODY', 'H3', 'EM' ), 1 ),
</span></span></pre></div>
<a id="trunktestsphpunittestshtmlapiwpHtmlProcessorSemanticRulesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php       2024-01-10 11:55:04 UTC (rev 57263)
+++ trunk/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php 2024-01-10 14:03:57 UTC (rev 57264)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -225,6 +225,109 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Verifies that H1 through H6 elements close an open P element.
+        *
+        * @ticket 60215
+        *
+        * @dataProvider data_heading_elements
+        *
+        * @param string $tag_name Name of H1 - H6 element under test.
+        */
+       public function test_in_body_heading_element_closes_open_p_tag( $tag_name ) {
+               $processor = WP_HTML_Processor::create_fragment(
+                       "<p>Open<{$tag_name}>Closed P</{$tag_name}><img></p>"
+               );
+
+               $processor->next_tag( $tag_name );
+               $this->assertSame(
+                       array( 'HTML', 'BODY', $tag_name ),
+                       $processor->get_breadcrumbs(),
+                       "Expected {$tag_name} to be a direct child of the BODY, having closed the open P element."
+               );
+
+               $processor->next_tag( 'IMG' );
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'IMG' ),
+                       $processor->get_breadcrumbs(),
+                       'Expected IMG to be a direct child of BODY, having closed the open P element.'
+               );
+       }
+
+       /**
+        * Data provider.
+        *
+        * @return array[].
+        */
+       public function data_heading_elements() {
+               return array(
+                       'H1' => array( 'H1' ),
+                       'H2' => array( 'H2' ),
+                       'H3' => array( 'H3' ),
+                       'H4' => array( 'H4' ),
+                       'H5' => array( 'H5' ),
+                       'H6' => array( 'H5' ),
+               );
+       }
+
+       /**
+        * Verifies that H1 through H6 elements close an open H1 through H6 element.
+        *
+        * @ticket 60215
+        *
+        * @dataProvider data_heading_combinations
+        *
+        * @param string $first_heading  H1 - H6 element appearing (unclosed) before the second.
+        * @param string $second_heading H1 - H6 element appearing after the first.
+        */
+       public function test_in_body_heading_element_closes_other_heading_elements( $first_heading, $second_heading ) {
+               $processor = WP_HTML_Processor::create_fragment(
+                       "<div><{$first_heading} first> then <{$second_heading} second> and end </{$second_heading}><img></{$first_heading}></div>"
+               );
+
+               while ( $processor->next_tag() && null === $processor->get_attribute( 'second' ) ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'second' ),
+                       "Failed to find expected {$second_heading} tag."
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'DIV', $second_heading ),
+                       $processor->get_breadcrumbs(),
+                       "Expected {$second_heading} to be a direct child of the DIV, having closed the open {$first_heading} element."
+               );
+
+               $processor->next_tag( 'IMG' );
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'DIV', 'IMG' ),
+                       $processor->get_breadcrumbs(),
+                       "Expected IMG to be a direct child of DIV, having closed the open {$first_heading} element."
+               );
+       }
+
+       /**
+        * Data provider.
+        *
+        * @return array[]
+        */
+       public function data_heading_combinations() {
+               $headings = array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' );
+
+               $combinations = array();
+
+               // Create all unique pairs of H1 - H6 elements.
+               foreach ( $headings as $first_tag ) {
+                       foreach ( $headings as $second_tag ) {
+                               $combinations[ "{$first_tag} then {$second_tag}" ] = array( $first_tag, $second_tag );
+                       }
+               }
+
+               return $combinations;
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Verifies that when "in body" and encountering "any other end tag"
</span><span class="cx" style="display: block; padding: 0 10px">         * that the HTML processor ignores the end tag if there's a special
</span><span class="cx" style="display: block; padding: 0 10px">         * element on the stack of open elements before the matching opening.
</span></span></pre></div>
<a id="trunktestsphpunittestshtmlapiwpHtmlProcessorSemanticRulesListElementsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRulesListElements.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRulesListElements.php                           (rev 0)
+++ trunk/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRulesListElements.php     2024-01-10 14:03:57 UTC (rev 57264)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,431 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Unit tests covering WP_HTML_Processor compliance with HTML5 semantic parsing rules
+ * for the list elements, including DD, DL, DT, LI, OL, and UL.
+ *
+ * @package WordPress
+ * @subpackage HTML-API
+ *
+ * @since 6.5.0
+ *
+ * @group html-api
+ *
+ * @coversDefaultClass WP_HTML_Processor
+ */
+class Tests_HtmlApi_WpHtmlProcessorSemanticRulesListElements extends WP_UnitTestCase {
+       /*******************************************************************
+        * RULES FOR "IN BODY" MODE
+        *******************************************************************/
+
+       /**
+        * Ensures that an opening LI element implicitly closes an open LI element.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_li_closes_open_li() {
+               $processor = WP_HTML_Processor::create_fragment( '<li><li><li target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'LI' ),
+                       $processor->get_breadcrumbs(),
+                       "LI should have closed open LI, but didn't."
+               );
+       }
+
+       /**
+        * Ensures that an opening LI element implicitly closes other open elements with optional closing tags.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_li_generates_implied_end_tags_inside_open_li() {
+               $processor = WP_HTML_Processor::create_fragment( '<li><li><div><li target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'LI' ),
+                       $processor->get_breadcrumbs(),
+                       "LI should have closed open LI, but didn't."
+               );
+       }
+
+       /**
+        * Ensures that when closing tags with optional tag closers, an opening LI tag doesn't close beyond a special boundary.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_li_generates_implied_end_tags_inside_open_li_but_stopping_at_special_tags() {
+               $processor = WP_HTML_Processor::create_fragment( '<li><li><blockquote><li target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'LI', 'BLOCKQUOTE', 'LI' ),
+                       $processor->get_breadcrumbs(),
+                       'LI should have left the BLOCKQOUTE open, but closed it.'
+               );
+       }
+
+       /**
+        * Ensures that an opening LI closes an open P in button scope.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_li_in_li_closes_p_in_button_scope() {
+               $processor = WP_HTML_Processor::create_fragment( '<li><li><p><button><p><li target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'LI', 'P', 'BUTTON', 'LI' ),
+                       $processor->get_breadcrumbs(),
+                       'LI should have left the outer P open, but closed it.'
+               );
+       }
+
+       /**
+        * Ensures that an opening DD closes an open DD element.
+        *
+        * Note that a DD closes an open DD and also an open DT.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_dd_closes_open_dd() {
+               $processor = WP_HTML_Processor::create_fragment( '<dd><dd><dd target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'DD' ),
+                       $processor->get_breadcrumbs(),
+                       "DD should have closed open DD, but didn't."
+               );
+       }
+
+       /**
+        * Ensures that an opening DD closes an open DT element.
+        *
+        * Note that a DD closes an open DD and also an open DT.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_dd_closes_open_dt() {
+               $processor = WP_HTML_Processor::create_fragment( '<dt><dt><dd target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'DD' ),
+                       $processor->get_breadcrumbs(),
+                       "DD should have closed open DD, but didn't."
+               );
+       }
+
+       /**
+        * Ensures that an opening DD implicitly closes open elements with optional closing tags.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_dd_generates_implied_end_tags_inside_open_dd() {
+               $processor = WP_HTML_Processor::create_fragment( '<dd><dd><div><dd target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'DD' ),
+                       $processor->get_breadcrumbs(),
+                       "DD should have closed open DD, but didn't."
+               );
+       }
+
+       /**
+        * Ensures that an opening DD implicitly closes open elements with optional closing tags,
+        * but doesn't close beyond a special boundary.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_dd_generates_implied_end_tags_inside_open_dd_but_stopping_at_special_tags() {
+               $processor = WP_HTML_Processor::create_fragment( '<dd><dd><blockquote><dd target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'DD', 'BLOCKQUOTE', 'DD' ),
+                       $processor->get_breadcrumbs(),
+                       'DD should have left the BLOCKQOUTE open, but closed it.'
+               );
+       }
+
+       /**
+        * Ensures that an opening DD inside a DD closes a P in button scope.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_dd_in_dd_closes_p_in_button_scope() {
+               $processor = WP_HTML_Processor::create_fragment( '<dd><dd><p><button><p><dd target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'DD', 'P', 'BUTTON', 'DD' ),
+                       $processor->get_breadcrumbs(),
+                       'DD should have left the outer P open, but closed it.'
+               );
+       }
+
+       /**
+        * Ensures that an opening DT closes an open DT element.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_dt_closes_open_dt() {
+               $processor = WP_HTML_Processor::create_fragment( '<dt><dt><dt target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'DT' ),
+                       $processor->get_breadcrumbs(),
+                       "DT should have closed open DT, but didn't."
+               );
+       }
+
+       /**
+        * Ensures that an opening DT closes an open DD.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_dt_closes_open_dd() {
+               $processor = WP_HTML_Processor::create_fragment( '<dd><dd><dt target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'DT' ),
+                       $processor->get_breadcrumbs(),
+                       "DT should have closed open DT, but didn't."
+               );
+       }
+
+       /**
+        * Ensures that an opening DT implicitly closes open elements with optional closing tags.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_dt_generates_implied_end_tags_inside_open_dt() {
+               $processor = WP_HTML_Processor::create_fragment( '<dt><dt><div><dt target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'DT' ),
+                       $processor->get_breadcrumbs(),
+                       "DT should have closed open DT, but didn't."
+               );
+       }
+
+       /**
+        * Ensures that an opening DT implicitly closes open elements with optional closing tags,
+        * but doesn't close beyond a special boundary.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_dt_generates_implied_end_tags_inside_open_dt_but_stopping_at_special_tags() {
+               $processor = WP_HTML_Processor::create_fragment( '<dt><dt><blockquote><dt target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'DT', 'BLOCKQUOTE', 'DT' ),
+                       $processor->get_breadcrumbs(),
+                       'DT should have left the BLOCKQOUTE open, but closed it.'
+               );
+       }
+
+       /**
+        * Ensures that an opening DT inside a DT closes a P in button scope.
+        *
+        * @ticket 60215
+        */
+       public function test_in_body_dt_in_dt_closes_p_in_button_scope() {
+               $processor = WP_HTML_Processor::create_fragment( '<dt><dt><p><button><p><dt target>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'DT', 'P', 'BUTTON', 'DT' ),
+                       $processor->get_breadcrumbs(),
+                       'DT should have left the outer P open, but closed it.'
+               );
+       }
+
+       /**
+        * Ensures that an unexpected LI doesn't close more elements than it should, that it doesn't
+        * close open LI elements that are beyond a special element (in this case, the UL).
+        *
+        * @ticket 60215
+        */
+       public function test_unexpected_li_close_tag_is_properly_contained() {
+               $processor = WP_HTML_Processor::create_fragment( '<ul><li><ul></li><li target>a</li></ul></li></ul>' );
+
+               while (
+                       null === $processor->get_attribute( 'target' ) &&
+                       $processor->next_tag()
+               ) {
+                       continue;
+               }
+
+               $this->assertTrue(
+                       $processor->get_attribute( 'target' ),
+                       'Failed to find target node.'
+               );
+
+               $this->assertSame(
+                       array( 'HTML', 'BODY', 'UL', 'LI', 'UL', 'LI' ),
+                       $processor->get_breadcrumbs(),
+                       'Unexpected LI close tag should have left its containing UL open, but closed it.'
+               );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRulesListElements.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestshtmlapiwpHtmlSupportRequiredHtmlProcessorphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/html-api/wpHtmlSupportRequiredHtmlProcessor.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/html-api/wpHtmlSupportRequiredHtmlProcessor.php 2024-01-10 11:55:04 UTC (rev 57263)
+++ trunk/tests/phpunit/tests/html-api/wpHtmlSupportRequiredHtmlProcessor.php   2024-01-10 14:03:57 UTC (rev 57264)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -58,9 +58,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @covers WP_HTML_Processor::generate_implied_end_tags
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_generate_implied_end_tags_needs_support() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->ensure_support_is_added_everywhere( 'DD' );
-               $this->ensure_support_is_added_everywhere( 'DT' );
-               $this->ensure_support_is_added_everywhere( 'LI' );
</del><span class="cx" style="display: block; padding: 0 10px">                 $this->ensure_support_is_added_everywhere( 'OPTGROUP' );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->ensure_support_is_added_everywhere( 'OPTION' );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->ensure_support_is_added_everywhere( 'RB' );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -82,9 +79,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_generate_implied_end_tags_thoroughly_needs_support() {
</span><span class="cx" style="display: block; padding: 0 10px">                $this->ensure_support_is_added_everywhere( 'CAPTION' );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->ensure_support_is_added_everywhere( 'COLGROUP' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->ensure_support_is_added_everywhere( 'DD' );
-               $this->ensure_support_is_added_everywhere( 'DT' );
-               $this->ensure_support_is_added_everywhere( 'LI' );
</del><span class="cx" style="display: block; padding: 0 10px">                 $this->ensure_support_is_added_everywhere( 'OPTGROUP' );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->ensure_support_is_added_everywhere( 'OPTION' );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->ensure_support_is_added_everywhere( 'RB' );
</span></span></pre></div>
<a id="trunktestsphpunittestshtmlapiwpHtmlSupportRequiredOpenElementsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/html-api/wpHtmlSupportRequiredOpenElements.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/html-api/wpHtmlSupportRequiredOpenElements.php  2024-01-10 11:55:04 UTC (rev 57263)
+++ trunk/tests/phpunit/tests/html-api/wpHtmlSupportRequiredOpenElements.php    2024-01-10 14:03:57 UTC (rev 57264)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -120,13 +120,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                 * FOREIGNOBJECT, DESC, TITLE.
</span><span class="cx" style="display: block; padding: 0 10px">                 */
</span><span class="cx" style="display: block; padding: 0 10px">                $this->ensure_support_is_added_everywhere( 'SVG' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-               // These elements are specific to list item scope.
-               $this->ensure_support_is_added_everywhere( 'OL' );
-               $this->ensure_support_is_added_everywhere( 'UL' );
-
-               // This element is the only element that depends on list item scope.
-               $this->ensure_support_is_added_everywhere( 'LI' );
</del><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span></span></pre>
</div>
</div>

</body>
</html>