<!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>[58588] trunk/src/wp-includes/html-api: HTML API: Report breadcrumbs properly when visiting virtual nodes.</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/58588">58588</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/58588","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-06-27 20:47:38 +0000 (Thu, 27 Jun 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: Report breadcrumbs properly when visiting virtual nodes.

When <a href="https://core.trac.wordpress.org/changeset/58304">[58304]</a> introduced the abililty to visit virtual nodes in the HTML document,
those being the nodes which are implied by the HTML but no explicitly present in
the raw text, a bug was introduced in the `get_breadcrumbs()` method because it
wasn't updated to be aware of the virtual nodes. Therefore it would report the
wrong breadcrumbs for virtual nodes. Since the new `get_depth()` method is based
on the same logic it was also broken for virtual nodes.

In this patch, the breadcrumbs have been updated to account for the virtual nodes
and the depth method has been updated to rely on the fixed breadcrumb logic.

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

Follow-up to <a href="https://core.trac.wordpress.org/changeset/58304">[58304]</a>.

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

<h3>Modified Paths</h3>
<ul>
<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>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<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-06-27 14:38:03 UTC (rev 58587)
+++ trunk/src/wp-includes/html-api/class-wp-html-open-elements.php      2024-06-27 20:47:38 UTC (rev 58588)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -308,11 +308,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function pop() {
</span><span class="cx" style="display: block; padding: 0 10px">                $item = array_pop( $this->stack );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
</del><span class="cx" style="display: block; padding: 0 10px">                 if ( null === $item ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        return false;
</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">+                if ( 'context-node' === $item->bookmark_name ) {
+                       $this->stack[] = $item;
+                       return false;
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->after_element_pop( $item );
</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="lines" style="display: block; padding: 0 10px; color: #888">@@ -329,6 +333,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function pop_until( $tag_name ) {
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $this->walk_up() as $item ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        if ( 'context-node' === $item->bookmark_name ) {
+                               return true;
+                       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                         $this->pop();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        if (
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -369,6 +377,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return bool Whether the node was found and removed from the stack of open elements.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function remove_node( $token ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( 'context-node' === $token->bookmark_name ) {
+                       return false;
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 foreach ( $this->walk_up() as $position_from_end => $item ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( $token->bookmark_name !== $item->bookmark_name ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                continue;
</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-06-27 14:38:03 UTC (rev 58587)
+++ trunk/src/wp-includes/html-api/class-wp-html-processor.php  2024-06-27 20:47:38 UTC (rev 58588)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -519,10 +519,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        return false;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( 0 === count( $this->element_queue ) && ! $this->step() ) {
-                       while ( $this->state->stack_of_open_elements->pop() ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( 'done' !== $this->has_seen_context_node && 0 === count( $this->element_queue ) && ! $this->step() ) {
+                       while ( 'context-node' !== $this->state->stack_of_open_elements->current_node()->bookmark_name && $this->state->stack_of_open_elements->pop() ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                 continue;
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        $this->has_seen_context_node = 'done';
+                       return $this->next_token();
</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">                $this->current_element = array_shift( $this->element_queue );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -537,7 +539,11 @@
</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 ( ! isset( $this->current_element ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        return $this->next_token();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 if ( 'done' === $this->has_seen_context_node ) {
+                               return false;
+                       } else {
+                               return $this->next_token();
+                       }
</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">                if ( isset( $this->context_node ) && WP_HTML_Stack_Event::POP === $this->current_element->operation && $this->context_node === $this->current_element->token ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -782,16 +788,48 @@
</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 class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @todo make aware of queue of elements, because stack operations have already been done by now.
-        *
</del><span class="cx" style="display: block; padding: 0 10px">          * @return string[]|null Array of tag names representing path to matched node, if matched, otherwise NULL.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function get_breadcrumbs() {
</span><span class="cx" style="display: block; padding: 0 10px">                $breadcrumbs = array();
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px">                 foreach ( $this->state->stack_of_open_elements->walk_down() as $stack_item ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $breadcrumbs[] = $stack_item->node_name;
</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">+                if ( ! $this->is_virtual() ) {
+                       return $breadcrumbs;
+               }
+
+               foreach ( $this->element_queue as $queue_item ) {
+                       if ( $this->current_element->token->bookmark_name === $queue_item->token->bookmark_name ) {
+                               break;
+                       }
+
+                       if ( 'context-node' === $queue_item->token->bookmark_name ) {
+                               break;
+                       }
+
+                       if ( 'real' === $queue_item->provenance ) {
+                               break;
+                       }
+
+                       if ( WP_HTML_Stack_Event::PUSH === $queue_item->operation ) {
+                               $breadcrumbs[] = $queue_item->token->node_name;
+                       } else {
+                               array_pop( $breadcrumbs );
+                       }
+               }
+
+               if ( null !== parent::get_token_name() && ! parent::is_tag_closer() ) {
+                       array_pop( $breadcrumbs );
+               }
+
+               // Add the virtual node we're at.
+               if ( WP_HTML_Stack_Event::PUSH === $this->current_element->operation ) {
+                       $breadcrumbs[] = $this->current_element->token->node_name;
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 return $breadcrumbs;
</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">@@ -821,7 +859,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return int Nesting-depth of current location in the document.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function get_current_depth() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                return $this->state->stack_of_open_elements->count();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         return $this->is_virtual()
+                       ? count( $this->get_breadcrumbs() )
+                       : $this->state->stack_of_open_elements->count();
</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>
</div>

</body>
</html>