<!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>[60919] trunk: HTML API: Escape all submitted HTML character references.</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/60919">60919</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/60919","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>2025-10-09 23:36:10 +0000 (Thu, 09 Oct 2025)</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: Escape all submitted HTML character references.

The HTML API has relied on `esc_attr()` and `esc_html()` when setting string attribute values or the contents of modifiable text. This leads to unexpected behavior when those functions attempt to prevent double-escaping of existing character references, and it can make certain contents impossible to represent.

After this change, the HTML API will reliably escape all submitted plaintext such that it appears in the browser the way it was submitted to the HTML API, with all character references escaped. This does not change the behavior of how URL attributes are escaped.

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

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

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludeshtmlapiclasswphtmlprocessorphp">trunk/src/wp-includes/html-api/class-wp-html-processor.php</a></li>
<li><a href="#trunksrcwpincludeshtmlapiclasswphtmltagprocessorphp">trunk/src/wp-includes/html-api/class-wp-html-tag-processor.php</a></li>
<li><a href="#trunktestsphpunittestsblocksupportswpRenderBackgroundSupportphp">trunk/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php</a></li>
<li><a href="#trunktestsphpunittestshtmlapiwpHtmlTagProcessorphp">trunk/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<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        2025-10-09 22:03:48 UTC (rev 60918)
+++ trunk/src/wp-includes/html-api/class-wp-html-processor.php  2025-10-09 23:36:10 UTC (rev 60919)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5290,13 +5290,30 @@
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Updates or creates a new attribute on the currently matched tag with the passed value.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * For boolean attributes special handling is provided:
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * This function handles all necessary HTML encoding. Provide normal, unescaped string values.
+        * The HTML API will encode the strings appropriately so that the browser will interpret them
+        * as the intended value.
+        *
+        * Example:
+        *
+        *     // Renders “Eggs & Milk” in a browser, encoded as `<abbr title="Eggs &amp; Milk">`.
+        *     $processor->set_attribute( 'title', 'Eggs & Milk' );
+        *
+        *     // Renders “Eggs &amp; Milk” in a browser, encoded as `<abbr title="Eggs &amp;amp; Milk">`.
+        *     $processor->set_attribute( 'title', 'Eggs &amp; Milk' );
+        *
+        *     // Renders `true` as `<abbr title>`.
+        *     $processor->set_attribute( 'title', true );
+        *
+        *     // Renders without the attribute for `false` as `<abbr>`.
+        *     $processor->set_attribute( 'title', false );
+        *
+        * Special handling is provided for boolean attribute values:
</ins><span class="cx" style="display: block; padding: 0 10px">          *  - When `true` is passed as the value, then only the attribute name is added to the tag.
</span><span class="cx" style="display: block; padding: 0 10px">         *  - When `false` is passed, the attribute gets removed if it existed before.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * For string attributes, the value is escaped using the `esc_attr` function.
-        *
</del><span class="cx" style="display: block; padding: 0 10px">          * @since 6.6.0 Subclassed for the HTML Processor.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 6.9.0 Escapes all character references instead of trying to avoid double-escaping.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string      $name  The attribute name to target.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string|bool $value The new attribute value.
</span></span></pre></div>
<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    2025-10-09 22:03:48 UTC (rev 60918)
+++ trunk/src/wp-includes/html-api/class-wp-html-tag-processor.php      2025-10-09 23:36:10 UTC (rev 60919)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3746,10 +3746,22 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *         $processor->set_modifiable_text( str_replace( ':)', '🙂', $chunk ) );
</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">+         * This function handles all necessary HTML encoding. Provide normal, unescaped string values.
+        * The HTML API will encode the strings appropriately so that the browser will interpret them
+        * as the intended value.
+        *
+        * Example:
+        *
+        *     // Renders as “Eggs & Milk” in a browser, encoded as `<p>Eggs &amp; Milk</p>`.
+        *     $processor->set_modifiable_text( 'Eggs & Milk' );
+        *
+        *     // Renders as “Eggs &amp; Milk” in a browser, encoded as `<p>Eggs &amp;amp; Milk</p>`.
+        *     $processor->set_modifiable_text( 'Eggs &amp; Milk' );
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @since 6.7.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 6.9.0 Escapes all character references instead of trying to avoid double-escaping.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string $plaintext_content New text content to represent in the matched token.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         *
</del><span class="cx" style="display: block; padding: 0 10px">          * @return bool Whether the text was able to update.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function set_modifiable_text( string $plaintext_content ): bool {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3757,7 +3769,16 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->lexical_updates['modifiable text'] = new WP_HTML_Text_Replacement(
</span><span class="cx" style="display: block; padding: 0 10px">                                $this->text_starts_at,
</span><span class="cx" style="display: block; padding: 0 10px">                                $this->text_length,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                htmlspecialchars( $plaintext_content, ENT_QUOTES | ENT_HTML5 )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         strtr(
+                                       $plaintext_content,
+                                       array(
+                                               '<' => '&lt;',
+                                               '>' => '&gt;',
+                                               '&' => '&amp;',
+                                               '"' => '&quot;',
+                                               "'" => '&apos;',
+                                       )
+                               )
</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">                        return true;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3871,14 +3892,31 @@
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Updates or creates a new attribute on the currently matched tag with the passed value.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * For boolean attributes special handling is provided:
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * This function handles all necessary HTML encoding. Provide normal, unescaped string values.
+        * The HTML API will encode the strings appropriately so that the browser will interpret them
+        * as the intended value.
+        *
+        * Example:
+        *
+        *     // Renders “Eggs & Milk” in a browser, encoded as `<abbr title="Eggs &amp; Milk">`.
+        *     $processor->set_attribute( 'title', 'Eggs & Milk' );
+        *
+        *     // Renders “Eggs &amp; Milk” in a browser, encoded as `<abbr title="Eggs &amp;amp; Milk">`.
+        *     $processor->set_attribute( 'title', 'Eggs &amp; Milk' );
+        *
+        *     // Renders `true` as `<abbr title>`.
+        *     $processor->set_attribute( 'title', true );
+        *
+        *     // Renders without the attribute for `false` as `<abbr>`.
+        *     $processor->set_attribute( 'title', false );
+        *
+        * Special handling is provided for boolean attribute values:
</ins><span class="cx" style="display: block; padding: 0 10px">          *  - When `true` is passed as the value, then only the attribute name is added to the tag.
</span><span class="cx" style="display: block; padding: 0 10px">         *  - When `false` is passed, the attribute gets removed if it existed before.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * For string attributes, the value is escaped using the `esc_attr` function.
-        *
</del><span class="cx" style="display: block; padding: 0 10px">          * @since 6.2.0
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 6.2.1 Fix: Only create a single update for multiple calls with case-variant attribute names.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 6.9.0 Escapes all character references instead of trying to avoid double-escaping.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string      $name  The attribute name to target.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string|bool $value The new attribute value.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3950,12 +3988,23 @@
</span><span class="cx" style="display: block; padding: 0 10px">                } else {
</span><span class="cx" style="display: block; padding: 0 10px">                        $comparable_name = strtolower( $name );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        /*
-                        * Escape URL attributes.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 /**
+                        * Escape attribute values appropriately.
</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/#attributes-3
</span><span class="cx" style="display: block; padding: 0 10px">                         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $escaped_new_value = in_array( $comparable_name, wp_kses_uri_attributes(), true ) ? esc_url( $value ) : esc_attr( $value );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $escaped_new_value = in_array( $comparable_name, wp_kses_uri_attributes(), true )
+                               ? esc_url( $value )
+                               : strtr(
+                                       $value,
+                                       array(
+                                               '<' => '&lt;',
+                                               '>' => '&gt;',
+                                               '&' => '&amp;',
+                                               '"' => '&quot;',
+                                               "'" => '&apos;',
+                                       )
+                               );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // If the escaping functions wiped out the update, reject it and indicate it was rejected.
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( '' === $escaped_new_value && '' !== $value ) {
</span></span></pre></div>
<a id="trunktestsphpunittestsblocksupportswpRenderBackgroundSupportphp"></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/block-supports/wpRenderBackgroundSupport.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php    2025-10-09 22:03:48 UTC (rev 60918)
+++ trunk/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php      2025-10-09 23:36:10 UTC (rev 60919)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -138,7 +138,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                                'url' => 'https://example.com/image.jpg',
</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">-                                'expected_wrapper'    => '<div class="has-background" style="background-image:url(&#039;https://example.com/image.jpg&#039;);background-size:cover;">Content</div>',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'expected_wrapper'    => '<div class="has-background" style="background-image:url(&apos;https://example.com/image.jpg&apos;);background-size:cover;">Content</div>',
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'wrapper'             => '<div>Content</div>',
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'background image style with contain, position, attachment, and repeat is applied' => array(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -155,7 +155,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        'backgroundSize'       => 'contain',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'backgroundAttachment' => 'fixed',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'expected_wrapper'    => '<div class="has-background" style="background-image:url(&#039;https://example.com/image.jpg&#039;);background-position:50% 50%;background-repeat:no-repeat;background-size:contain;background-attachment:fixed;">Content</div>',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'expected_wrapper'    => '<div class="has-background" style="background-image:url(&apos;https://example.com/image.jpg&apos;);background-position:50% 50%;background-repeat:no-repeat;background-size:contain;background-attachment:fixed;">Content</div>',
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'wrapper'             => '<div>Content</div>',
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'background image style is appended if a style attribute already exists' => array(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -169,7 +169,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                                'url' => 'https://example.com/image.jpg',
</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">-                                'expected_wrapper'    => '<div class="wp-block-test has-background" style="color: red;background-image:url(&#039;https://example.com/image.jpg&#039;);background-size:cover;">Content</div>',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'expected_wrapper'    => '<div class="wp-block-test has-background" style="color: red;background-image:url(&apos;https://example.com/image.jpg&apos;);background-size:cover;">Content</div>',
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'wrapper'             => '<div class="wp-block-test" style="color: red">Content</div>',
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'background image style is appended if a style attribute containing multiple styles already exists' => array(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -183,7 +183,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                                'url' => 'https://example.com/image.jpg',
</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">-                                'expected_wrapper'    => '<div class="wp-block-test has-background" style="color: red;font-size: 15px;background-image:url(&#039;https://example.com/image.jpg&#039;);background-size:cover;">Content</div>',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'expected_wrapper'    => '<div class="wp-block-test has-background" style="color: red;font-size: 15px;background-image:url(&apos;https://example.com/image.jpg&apos;);background-size:cover;">Content</div>',
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'wrapper'             => '<div class="wp-block-test" style="color: red;font-size: 15px;">Content</div>',
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'background image style is appended if a boolean style attribute already exists' => array(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -198,7 +198,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                                'source' => 'file',
</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">-                                'expected_wrapper'    => '<div class="has-background" classname="wp-block-test" style="background-image:url(&#039;https://example.com/image.jpg&#039;);background-size:cover;">Content</div>',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'expected_wrapper'    => '<div class="has-background" classname="wp-block-test" style="background-image:url(&apos;https://example.com/image.jpg&apos;);background-size:cover;">Content</div>',
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'wrapper'             => '<div classname="wp-block-test" style>Content</div>',
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'background image style is not applied if the block does not support background image' => array(
</span></span></pre></div>
<a id="trunktestsphpunittestshtmlapiwpHtmlTagProcessorphp"></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/wpHtmlTagProcessor.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php 2025-10-09 22:03:48 UTC (rev 60918)
+++ trunk/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php   2025-10-09 23:36:10 UTC (rev 60919)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -841,7 +841,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string $attribute_value A value with potential XSS exploit.
</span><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 test_set_attribute_prevents_xss( $attribute_value ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function test_set_attribute_prevents_xss( $attribute_value, $escaped_attribute_value = null ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 $processor = new WP_HTML_Tag_Processor( '<div></div>' );
</span><span class="cx" style="display: block; padding: 0 10px">                $processor->next_tag();
</span><span class="cx" style="display: block; padding: 0 10px">                $processor->set_attribute( 'test', $attribute_value );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -861,7 +861,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                preg_match( '~^<div test=(.*)></div>$~', $processor->get_updated_html(), $match );
</span><span class="cx" style="display: block; padding: 0 10px">                list( , $actual_value ) = $match;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertSame( '"' . esc_attr( $attribute_value ) . '"', $actual_value, 'Entities were not properly escaped in the attribute value' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertSame( '"' . $escaped_attribute_value . '"', $actual_value, 'Entities were not properly escaped in the attribute value' );
</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">@@ -871,15 +871,18 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public static function data_set_attribute_prevents_xss() {
</span><span class="cx" style="display: block; padding: 0 10px">                return array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        array( '"' ),
-                       array( '&quot;' ),
-                       array( '&' ),
-                       array( '&amp;' ),
-                       array( '&euro;' ),
-                       array( "'" ),
-                       array( '<>' ),
-                       array( '&quot";' ),
-                       array( '" onclick="alert(\'1\');"><span onclick=""></span><script>alert("1")</script>' ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 array( '"', '&quot;' ),
+                       array( '&quot;', '&amp;quot;' ),
+                       array( '&', '&amp;' ),
+                       array( '&amp;', '&amp;amp;' ),
+                       array( '&euro;', '&amp;euro;' ),
+                       array( "'", '&apos;' ),
+                       array( '<>', '&lt;&gt;' ),
+                       array( '&quot";', '&amp;quot&quot;;' ),
+                       array(
+                               '" onclick="alert(\'1\');"><span onclick=""></span><script>alert("1")</script>',
+                               '&quot; onclick=&quot;alert(&apos;1&apos;);&quot;&gt;&lt;span onclick=&quot;&quot;&gt;&lt;/span&gt;&lt;script&gt;alert(&quot;1&quot;)&lt;/script&gt;',
+                       ),
</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">@@ -906,6 +909,21 @@
</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">+         * Ensure that attribute values that appear to contain HTML character references are correctly
+        * encoded and preserve the original value.
+        *
+        * @ticket 64054
+        */
+       public function test_set_attribute_encodes_html_character_references() {
+               $original  = 'HTML character references: &lt; &gt; &amp;';
+               $processor = new WP_HTML_Tag_Processor( '<span>' );
+               $processor->next_tag();
+               $processor->set_attribute( 'data-attr', $original );
+               $this->assertSame( $original, $processor->get_attribute( 'data-attr' ) );
+               $this->assertEqualHTML( '<span data-attr="HTML character references: &amp;lt; &amp;gt; &amp;amp;">', $processor->get_updated_html() );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * @ticket 56299
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @covers WP_HTML_Tag_Processor::get_attribute
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2786,9 +2804,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $processor->next_tag();
</span><span class="cx" style="display: block; padding: 0 10px">                $processor->add_class( 'secondTag' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertSame(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertEqualHTML(
</ins><span class="cx" style="display: block; padding: 0 10px">                         $expected,
</span><span class="cx" style="display: block; padding: 0 10px">                        $processor->get_updated_html(),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        '<body>',
</ins><span class="cx" style="display: block; padding: 0 10px">                         'Did not properly update attributes and classnames given malformed input'
</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">@@ -2806,11 +2825,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'HTML tag opening inside attribute value'      => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'input'    => '<pre id="<code" class="wp-block-code <code is poetry&gt;"><code>This &lt;is> a &lt;strong is="true">thing.</code></pre><span>test</span>',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'expected' => '<pre foo="bar" id="<code" class="wp-block-code &lt;code is poetry&gt; firstTag"><code class="secondTag">This &lt;is> a &lt;strong is="true">thing.</code></pre><span>test</span>',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'expected' => '<pre foo="bar" id="<code" class="wp-block-code &lt;code is poetry&amp;gt; firstTag"><code class="secondTag">This &lt;is> a &lt;strong is="true">thing.</code></pre><span>test</span>',
</ins><span class="cx" style="display: block; padding: 0 10px">                         ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'HTML tag brackets in attribute values and data markup' => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'input'    => '<pre id="<code-&gt;-block-&gt;" class="wp-block-code <code is poetry&gt;"><code>This &lt;is> a &lt;strong is="true">thing.</code></pre><span>test</span>',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'expected' => '<pre foo="bar" id="<code-&gt;-block-&gt;" class="wp-block-code &lt;code is poetry&gt; firstTag"><code class="secondTag">This &lt;is> a &lt;strong is="true">thing.</code></pre><span>test</span>',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'expected' => '<pre foo="bar" id="<code-&gt;-block-&gt;" class="wp-block-code &lt;code is poetry&amp;gt; firstTag"><code class="secondTag">This &lt;is> a &lt;strong is="true">thing.</code></pre><span>test</span>',
</ins><span class="cx" style="display: block; padding: 0 10px">                         ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'Single and double quotes in attribute value'  => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'input'    => '<p title="Demonstrating how to use single quote (\') and double quote (&quot;)"><span>test</span>',
</span></span></pre>
</div>
</div>

</body>
</html>