<!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>[58866] trunk: HTML API: Ensure that `get_modifiable_text()` reads enqueued updates.</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/58866">58866</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/58866","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-08-08 04:24:03 +0000 (Thu, 08 Aug 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: Ensure that `get_modifiable_text()` reads enqueued updates.
When `set_modifiable_text()` was added to the Tag Processor, it was considered that the same information could be queried after setting its value and before proceeding to the next token, but unfortunately overlooked that if the starting modifiable text length was zero, then the read in `get_modifiable_text()` would ignore enqueued updates.
In this patch, `get_modifiable_text()` will read any enqueued values before reading from the input HTML document to ensure consistency.
Follow-up to <a href="https://core.trac.wordpress.org/changeset/58829">[58829]</a>.
Props dmsnell, jonsurrell, ramonopoly.
Fixes <a href="https://core.trac.wordpress.org/ticket/61617">#61617</a>.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludeshtmlapiclasswphtmltagprocessorphp">trunk/src/wp-includes/html-api/class-wp-html-tag-processor.php</a></li>
<li><a href="#trunktestsphpunittestshtmlapiwpHtmlTagProcessorModifiableTextphp">trunk/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludeshtmlapiclasswphtmltagprocessorphp"></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-tag-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-tag-processor.php 2024-08-08 02:25:32 UTC (rev 58865)
+++ trunk/src/wp-includes/html-api/class-wp-html-tag-processor.php 2024-08-08 04:24:03 UTC (rev 58866)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -614,7 +614,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 6.5.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">- * @var string
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @var int
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> private $text_length;
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2894,11 +2894,13 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @return string
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function get_modifiable_text(): string {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( null === $this->text_starts_at || 0 === $this->text_length ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $has_enqueued_update = isset( $this->lexical_updates['modifiable text'] );
+
+ if ( ! $has_enqueued_update && ( null === $this->text_starts_at || 0 === $this->text_length ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> return '';
</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">- $text = isset( $this->lexical_updates['modifiable text'] )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $text = $has_enqueued_update
</ins><span class="cx" style="display: block; padding: 0 10px"> ? $this->lexical_updates['modifiable text']->text
</span><span class="cx" style="display: block; padding: 0 10px"> : substr( $this->html, $this->text_starts_at, $this->text_length );
</span><span class="cx" style="display: block; padding: 0 10px">
</span></span></pre></div>
<a id="trunktestsphpunittestshtmlapiwpHtmlTagProcessorModifiableTextphp"></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/wpHtmlTagProcessorModifiableText.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php 2024-08-08 02:25:32 UTC (rev 58865)
+++ trunk/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php 2024-08-08 04:24:03 UTC (rev 58866)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -40,6 +40,83 @@
</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">+ * Ensures that `get_modifiable_text()` reads enqueued updates when read
+ * from after writing; guarantees consistency through writes.
+ *
+ * @ticket 61617
+ */
+ public function test_get_modifiable_text_is_consistent_after_writes() {
+ $before = 'just some text';
+ $after = 'different text';
+ $processor = new WP_HTML_Tag_Processor( $before );
+ $processor->next_token();
+
+ $this->assertSame(
+ '#text',
+ $processor->get_token_name(),
+ "Should have found text node but found '{$processor->get_token_name()}' instead: check test setup."
+ );
+
+ $this->assertSame(
+ $before,
+ $processor->get_modifiable_text(),
+ 'Should have found initial test text: check test setup.'
+ );
+
+ $processor->set_modifiable_text( $after );
+ $this->assertSame(
+ $after,
+ $processor->get_modifiable_text(),
+ 'Should have found enqueued updated text.'
+ );
+
+ $processor->get_updated_html();
+ $this->assertSame(
+ $after,
+ $processor->get_modifiable_text(),
+ 'Should have found updated text.'
+ );
+ }
+
+ /**
+ * Ensures that `get_modifiable_text()` reads enqueued updates when read from after
+ * writing when starting from an empty text; guarantees consistency through writes.
+ *
+ * @ticket 61617
+ */
+ public function test_get_modifiable_text_is_consistent_after_writes_to_empty_text() {
+ $after = 'different text';
+ $processor = new WP_HTML_Tag_Processor( '<script></script>' );
+ $processor->next_token();
+
+ $this->assertSame(
+ 'SCRIPT',
+ $processor->get_token_name(),
+ "Should have found text node but found '{$processor->get_token_name()}' instead: check test setup."
+ );
+
+ $this->assertSame(
+ '',
+ $processor->get_modifiable_text(),
+ 'Should have found initial test text: check test setup.'
+ );
+
+ $processor->set_modifiable_text( $after );
+ $this->assertSame(
+ $after,
+ $processor->get_modifiable_text(),
+ 'Should have found enqueued updated text.'
+ );
+
+ $processor->get_updated_html();
+ $this->assertSame(
+ $after,
+ $processor->get_modifiable_text(),
+ 'Should have found updated text.'
+ );
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Ensures that updates to modifiable text that are shorter than the
</span><span class="cx" style="display: block; padding: 0 10px"> * original text do not cause the parser to lose its orientation.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span></span></pre>
</div>
</div>
</body>
</html>