Commit bca7ceac1f for wordpress.org

commit bca7ceac1f6922c2c0851ee638c73b4eedc1912b
Author: luisherranz <luisherranz@git.wordpress.org>
Date:   Mon Oct 19 12:38:51 2026 +0000

    Interactivity API: Print `data-wp-html` tokens as the `wpServerHtml` marker.

    Encode `WP_Interactivity_Dangerous_HTML` tokens as `{"wpServerHtml":true}` on both paths of `wp_json_encode()`, so the client keeps the server-rendered content of a `data-wp-html` host wherever the reference reaches the token, including `data-wp-each` items read from context. Stop listing tokens reached through state in `derivedStateClosures`, which the marker makes unnecessary.

    Follow-up to [64386].

    Props santosguillamot.
    Fixes #66246.
    Built from https://develop.svn.wordpress.org/trunk@64501


    git-svn-id: http://core.svn.wordpress.org/trunk@63651 1a063a9b-81f0-0310-95a4-ce76da25c4cd

diff --git a/wp-includes/interactivity-api/class-wp-interactivity-api.php b/wp-includes/interactivity-api/class-wp-interactivity-api.php
index 6ec333401b..ad6ef564bc 100644
--- a/wp-includes/interactivity-api/class-wp-interactivity-api.php
+++ b/wp-includes/interactivity-api/class-wp-interactivity-api.php
@@ -706,7 +706,6 @@ final class WP_Interactivity_API {
 	 * @since 6.6.0 Removed `default_namespace` and `context` arguments.
 	 * @since 6.6.0 Add support for derived state.
 	 * @since 6.9.0 Receive $entry as an argument instead of the directive value string.
-	 * @since 7.2.0 Tracks the path of a token that marks HTML as trusted, reached through state.
 	 *
 	 * @param array $entry An array containing a whole directive entry with its namespace, value, suffix, or unique ID.
 	 * @return mixed|null The result of the evaluation. Null if the reference path doesn't exist or the namespace is falsy.
@@ -732,9 +731,8 @@ final class WP_Interactivity_API {
 		$path                = $should_negate_value ? substr( $path, 1 ) : $path;

 		// Extracts the value from the store using the reference path.
-		$path_segments  = explode( '.', $path );
-		$current        = $store;
-		$called_closure = false;
+		$path_segments = explode( '.', $path );
+		$current       = $store;
 		foreach ( $path_segments as $index => $path_segment ) {
 			/*
 			 * Special case for numeric arrays and strings. Add length
@@ -783,8 +781,7 @@ final class WP_Interactivity_API {
 				 */
 				array_push( $this->namespace_stack, $ns );
 				try {
-					$called_closure = true;
-					$current        = $current();
+					$current = $current();

 					/*
 					 * Tracks derived state properties that are accessed during
@@ -818,28 +815,6 @@ final class WP_Interactivity_API {
 			}
 		}

-		/*
-		 * Tracks the tokens that mark HTML as trusted and that a reference
-		 * reaches through state, the way derived state closures are tracked, so
-		 * the client keeps the server-rendered content of a token until its
-		 * store supplies a value. A token reached through context, or through
-		 * the result of a derived state closure, is not tracked: the client has
-		 * no context store to resolve it from, and the closure is tracked
-		 * instead.
-		 *
-		 * @since 7.2.0
-		 */
-		if (
-			'state' === $path_segments[0] &&
-			! $called_closure &&
-			$current instanceof WP_Interactivity_Dangerous_HTML
-		) {
-			$this->derived_state_closures[ $ns ] ??= array();
-			if ( ! in_array( $path, $this->derived_state_closures[ $ns ], true ) ) {
-				$this->derived_state_closures[ $ns ][] = $path;
-			}
-		}
-
 		// Returns the opposite if it contains a negation operator (!).
 		return $should_negate_value ? ! $current : $current;
 	}
diff --git a/wp-includes/interactivity-api/class-wp-interactivity-dangerous-html.php b/wp-includes/interactivity-api/class-wp-interactivity-dangerous-html.php
index bcfc8334d7..17ef1b6777 100644
--- a/wp-includes/interactivity-api/class-wp-interactivity-dangerous-html.php
+++ b/wp-includes/interactivity-api/class-wp-interactivity-dangerous-html.php
@@ -14,15 +14,22 @@
  * Create tokens with {@see wp_interactivity_as_dangerous_html()}, which
  * documents the risks of marking HTML as trusted.
  *
- * The HTML is kept in a private property, so encoding a token as JSON never
- * includes it. A token restored by unserialization is inert: it holds no HTML,
- * so it never renders.
+ * The HTML is kept in a private property, so it never reaches JSON: a token
+ * encodes as the marker `{"wpServerHtml":true}`, which tells the client to
+ * keep the content the element already has. The marker holds no HTML. A token
+ * restored by unserialization is inert: it holds no HTML, so it never renders,
+ * and it encodes as the marker too.
+ *
+ * Both JSON encoding paths of `wp_json_encode()` print the marker: `json_encode()`
+ * uses {@see WP_Interactivity_Dangerous_HTML::jsonSerialize()}, and the fallback
+ * for data that is not valid UTF-8 rebuilds the object by iterating it with
+ * {@see WP_Interactivity_Dangerous_HTML::getIterator()}.
  *
  * @since 7.2.0
  *
  * @access private
  */
-final class WP_Interactivity_Dangerous_HTML {
+final class WP_Interactivity_Dangerous_HTML implements JsonSerializable, IteratorAggregate {
 	/**
 	 * The trusted HTML. It is `null` for a token restored by unserialization,
 	 * which never renders.
@@ -73,4 +80,32 @@ final class WP_Interactivity_Dangerous_HTML {
 			'7.2.0'
 		);
 	}
+
+	/**
+	 * Gets the value that JSON encoding prints in place of the token.
+	 *
+	 * It is the same for every token and holds none of its HTML.
+	 *
+	 * @since 7.2.0
+	 *
+	 * @return array<string, bool> The marker: a single `wpServerHtml` entry set to `true`,
+	 *                             which `json_encode()` prints as `{"wpServerHtml":true}`.
+	 */
+	public function jsonSerialize(): array {
+		return array( 'wpServerHtml' => true );
+	}
+
+	/**
+	 * Gets the iterator that `foreach` and the fallback of `wp_json_encode()` read.
+	 *
+	 * It yields the same single entry as {@see WP_Interactivity_Dangerous_HTML::jsonSerialize()}
+	 * and none of the token's HTML.
+	 *
+	 * @since 7.2.0
+	 *
+	 * @return Traversable<string, bool> An iterator yielding `wpServerHtml` => `true`, once.
+	 */
+	public function getIterator(): Traversable {
+		return new ArrayIterator( $this->jsonSerialize() );
+	}
 }
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 4c97486fbf..145bda233f 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
  *
  * @global string $wp_version
  */
-$wp_version = '7.2-alpha-64500';
+$wp_version = '7.2-alpha-64501';

 /**
  * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.