<!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>[58656] trunk/src/wp-includes/html-api: HTML API: Implement the _reset insertion mode appropriately_ algorithm.</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/58656">58656</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/58656","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-07-03 17:05:46 +0000 (Wed, 03 Jul 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: Implement the _reset insertion mode appropriately_ algorithm.

In order to add support for the SELECT and TABLE tags in the HTML Processor, it
needs to implement the HTML algorithm named "reset the insertion mode
appropriately".

This patch implements that algorithm to unblock the additional tag support. The
algorithm resets the parsing mode after specific state changes in complicated
situations where alternative rules are in effect (such as rules governing how
the parser handles tags found within a TABLE element).

Developed in https://github.com/WordPress/wordpress-develop/pull/6020
Discussed in https://core.trac.wordpress.org/ticket/61549

Props dmsnell, jonsurrell.
Fixes <a href="https://core.trac.wordpress.org/ticket/61549">#61549</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludeshtmlapiclasswphtmlprocessorstatephp">trunk/src/wp-includes/html-api/class-wp-html-processor-state.php</a></li>
<li><a href="#trunksrcwpincludeshtmlapiclasswphtmlprocessorphp">trunk/src/wp-includes/html-api/class-wp-html-processor.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludeshtmlapiclasswphtmlprocessorstatephp"></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-state.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-state.php  2024-07-03 16:32:11 UTC (rev 58655)
+++ trunk/src/wp-includes/html-api/class-wp-html-processor-state.php    2024-07-03 17:05:46 UTC (rev 58656)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -216,6 +216,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">        const INSERTION_MODE_IN_TEMPLATE = 'insertion-mode-in-template';
</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">+         * The stack of template insertion modes.
+        *
+        * @since 6.7.0
+        *
+        * @see https://html.spec.whatwg.org/#the-insertion-mode:stack-of-template-insertion-modes
+        *
+        * @var array<string>
+        */
+       public $stack_of_template_insertion_modes = array();
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Tracks open elements while scanning HTML.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * This property is initialized in the constructor and never null.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -273,6 +284,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public $context_node = null;
</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">+         * HEAD element pointer.
+        *
+        * @since 6.7.0
+        *
+        * @see https://html.spec.whatwg.org/multipage/parsing.html#head-element-pointer
+        *
+        * @var WP_HTML_Token|null
+        */
+       public $head_element = null;
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * The frameset-ok flag indicates if a `FRAMESET` element is allowed in the current state.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * > The frameset-ok flag is set to "ok" when the parser is created. It is set to "not ok" after certain tokens are seen.
</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-07-03 16:32:11 UTC (rev 58655)
+++ trunk/src/wp-includes/html-api/class-wp-html-processor.php  2024-07-03 17:05:46 UTC (rev 58656)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2130,6 +2130,189 @@
</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">+         * Runs the reset the insertion mode appropriately algorithm.
+        *
+        * @since 6.7.0
+        *
+        * @see https://html.spec.whatwg.org/multipage/parsing.html#reset-the-insertion-mode-appropriately
+        */
+       public function reset_insertion_mode(): void {
+               // Set the first node.
+               $first_node = null;
+               foreach ( $this->state->stack_of_open_elements->walk_down() as $first_node ) {
+                       break;
+               }
+
+               /*
+                * > 1. Let _last_ be false.
+                */
+               $last = false;
+               foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) {
+                       /*
+                        * > 2. Let _node_ be the last node in the stack of open elements.
+                        * > 3. _Loop_: If _node_ is the first node in the stack of open elements, then set _last_
+                        * >            to true, and, if the parser was created as part of the HTML fragment parsing
+                        * >            algorithm (fragment case), set node to the context element passed to
+                        * >            that algorithm.
+                        * > …
+                        */
+                       if ( $node === $first_node ) {
+                               $last = true;
+                               if ( isset( $this->context_node ) ) {
+                                       $node = $this->context_node;
+                               }
+                       }
+
+                       switch ( $node->node_name ) {
+                               /*
+                                * > 4. If node is a `select` element, run these substeps:
+                                * >   1. If _last_ is true, jump to the step below labeled done.
+                                * >   2. Let _ancestor_ be _node_.
+                                * >   3. _Loop_: If _ancestor_ is the first node in the stack of open elements,
+                                * >      jump to the step below labeled done.
+                                * >   4. Let ancestor be the node before ancestor in the stack of open elements.
+                                * >   …
+                                * >   7. Jump back to the step labeled _loop_.
+                                * >   8. _Done_: Switch the insertion mode to "in select" and return.
+                                */
+                               case 'SELECT':
+                                       if ( ! $last ) {
+                                               foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $ancestor ) {
+                                                       switch ( $ancestor->node_name ) {
+                                                               /*
+                                                                * > 5. If _ancestor_ is a `template` node, jump to the step below
+                                                                * >    labeled _done_.
+                                                                */
+                                                               case 'TEMPLATE':
+                                                                       break 2;
+
+                                                               /*
+                                                                * > 6. If _ancestor_ is a `table` node, switch the insertion mode to
+                                                                * >    "in select in table" and return.
+                                                                */
+                                                               case 'TABLE':
+                                                                       $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT_IN_TABLE;
+                                                                       return;
+                                                       }
+                                               }
+                                       }
+                                       $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT;
+                                       return;
+
+                               /*
+                                * > 5. If _node_ is a `td` or `th` element and _last_ is false, then switch the
+                                * >    insertion mode to "in cell" and return.
+                                */
+                               case 'TD':
+                               case 'TH':
+                                       if ( ! $last ) {
+                                               $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_CELL;
+                                               return;
+                                       }
+                                       break;
+
+                                       /*
+                                       * > 6. If _node_ is a `tr` element, then switch the insertion mode to "in row"
+                                       * >    and return.
+                                       */
+                               case 'TR':
+                                       $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW;
+                                       return;
+
+                               /*
+                                * > 7. If _node_ is a `tbody`, `thead`, or `tfoot` element, then switch the
+                                * >    insertion mode to "in table body" and return.
+                                */
+                               case 'TBODY':
+                               case 'THEAD':
+                               case 'TFOOT':
+                                       $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY;
+                                       return;
+
+                               /*
+                                * > 8. If _node_ is a `caption` element, then switch the insertion mode to
+                                * >    "in caption" and return.
+                                */
+                               case 'CAPTION':
+                                       $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION;
+                                       return;
+
+                               /*
+                                * > 9. If _node_ is a `colgroup` element, then switch the insertion mode to
+                                * >    "in column group" and return.
+                                */
+                               case 'COLGROUP':
+                                       $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP;
+                                       return;
+
+                               /*
+                                * > 10. If _node_ is a `table` element, then switch the insertion mode to
+                                * >     "in table" and return.
+                                */
+                               case 'TABLE':
+                                       $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE;
+                                       return;
+
+                               /*
+                                * > 11. If _node_ is a `template` element, then switch the insertion mode to the
+                                * >     current template insertion mode and return.
+                                */
+                               case 'TEMPLATE':
+                                       $this->state->insertion_mode = end( $this->state->stack_of_template_insertion_modes );
+                                       return;
+
+                               /*
+                                * > 12. If _node_ is a `head` element and _last_ is false, then switch the
+                                * >     insertion mode to "in head" and return.
+                                */
+                               case 'HEAD':
+                                       if ( ! $last ) {
+                                               $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD;
+                                               return;
+                                       }
+                                       break;
+
+                               /*
+                                * > 13. If _node_ is a `body` element, then switch the insertion mode to "in body"
+                                * >     and return.
+                                */
+                               case 'BODY':
+                                       $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
+                                       return;
+
+                               /*
+                                * > 14. If _node_ is a `frameset` element, then switch the insertion mode to
+                                * >     "in frameset" and return. (fragment case)
+                                */
+                               case 'FRAMESET':
+                                       $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_FRAMESET;
+                                       return;
+
+                               /*
+                                * > 15. If _node_ is an `html` element, run these substeps:
+                                * >     1. If the head element pointer is null, switch the insertion mode to
+                                * >        "before head" and return. (fragment case)
+                                * >     2. Otherwise, the head element pointer is not null, switch the insertion
+                                * >        mode to "after head" and return.
+                                */
+                               case 'HTML':
+                                       $this->state->insertion_mode = isset( $this->state->head_element )
+                                               ? WP_HTML_Processor_State::INSERTION_MODE_AFTER_HEAD
+                                               : WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD;
+                                       return;
+                       }
+               }
+
+               /*
+                * > 16. If _last_ is true, then switch the insertion mode to "in body"
+                * >     and return. (fragment case)
+                *
+                * This is only reachable if `$last` is true, as per the fragment parsing case.
+                */
+               $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Runs the adoption agency algorithm.
</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></span></pre>
</div>
</div>

</body>
</html>