Repository navigation
Interactivity API: Render data-wp-html on the server as the client hydrates it. - #15737
SantosGuillamot wants to merge 10 commits into
Conversation
…ctives processor. `WP_Interactivity_API_Directives_Processor::skip_to_balanced_tag_closer()` moves to the matching closer tag of the current opener tag and optionally replaces the content between them. It neither applies the pending updates nor seeks back to the opener tag, so a processor can replace the content of any number of elements. See #66246.
…hydrates it. The server now follows the client's rules for `data-wp-html`, so hydration keeps what the server rendered: - A host whose reference resolves to `null` or to nothing is emptied without a notice. That covers missing paths, throwing derived-state closures, references with a `__proto__`, `constructor` or `prototype` path segment or namespace, empty values, and values that name no property. The reference is read raw and never decoded as JSON. - A host whose reference resolves to any other value that is not a token is emptied with a notice. A `!` applies after the reserved-name check, as in the client. - The content of an element that carries a `data-wp-html` entry without a suffix is never processed, so the directives in it do not run and the derived-state closures referenced only there are not listed. An element with only unique-ID entries keeps its content as written. - `data-wp-html` is ignored, with a notice, when the element also carries `data-wp-text`, `data-wp-each` or `data-wp-router-region` in any form, on any element. Directives the client drops for a reserved name part or namespace do not count, and a dropped `data-wp-html` leaves the element as if it had none. - A `script`, `style`, `textarea`, `title` or other raw-text host is emptied for a value that is not a token, and keeps its text with a notice for a token. The content of each host is consumed in one forward pass, without seeking back to its opener tag, so a call renders every host however many it holds, and processing time grows with the size of the page alone. The rewinding `WP_Interactivity_API_Directives_Processor::set_raw_content_between_balanced_tags()` is removed. See #66246.
… one pass. The `data-wp-html` processor now parses an element's directive attributes once, for its own entries and for the directives that conflict with it, instead of once per directive prefix. See #66246.
…!` to nothing. The client removes one leading `!` and reads the rest of the path as given, so `!!state.x` names a property `!state` that does not exist and `state.x` is never read. The server now does the same instead of negating twice, so no derived-state closure on that path runs or is listed. See #66246.
… case's path. See #66246.
…scapes its element. See #66246.
…t replacement. `WP_Interactivity_API_Directives_Processor::skip_to_balanced_tag_closer()` always replaces the content now. An element whose `data-wp-html` entries all have a unique ID is skipped with `next_balanced_tag_closer_tag()`, which keeps its content. See #66246.
…t island names. The client takes an unprefixed reference's namespace from the nearest `data-wp-interactive` alone. When that directive has no value, an empty one, a JSON object without a string `namespace`, or a reserved word, no namespace is in effect, even if an enclosing directive names one. Core's fit-text typography support adds such a valueless `data-wp-interactive` to blocks that can sit inside a region. `data_wp_interactive_processor()` now also records the namespace each directive names by that reading, and `data-wp-html` resolves in it, so a host there is emptied as the client empties it. Every other directive keeps resolving in the inherited namespace. See #66246.
…is processing. A call made while another call is processing, as from a derived-state closure or a state value that renders markup, used to reset the shared stacks and placeholders and leave them empty when it returned. The other call then ended in a fatal `TypeError`, or shipped a placeholder comment in place of its `data-wp-html` content. Each call now saves the processing state it finds (the namespace and context stacks, the current element and the `data-wp-html` placeholders) and restores it when it returns, however it ends. Its placeholder marker is longer than that of any call it runs inside, so the placeholders of the two calls never coincide. See #66246.
…der a suffixed `data-wp-interactive`. Only the attribute named exactly `data-wp-interactive` makes an island in the client. An element that carries only `data-wp-interactive--<suffix>` or `data-wp-interactive---<id>` is no island, so a `data-wp-html` host inside it resolves its reference in the enclosing namespace. The server recorded such an element as an island that names no namespace, and emptied the host. See #66246.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. Props Bot commandsYou can trigger Props Bot actions by commenting on this PR:
|
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
64386 added server-side processing of the
data-wp-htmldirective before the client's behavior was settled in Gutenberg. In several cases the server and the client disagree, so hydration changes what the server rendered: the fallback disappears, the trusted HTML disappears, or content the server processed is rendered again by the client.This makes the server follow the client's rules, so the HTML a
data-wp-htmlelement is served with is what the client keeps after hydration.Behavior changes against trunk
wp_interactivity_as_dangerous_html()renders HTML, as before.nulland references that resolve to nothing empty the element without a notice: a missing path, a derived-state closure that throws, a__proto__,constructororprototypepath segment or namespace, an empty or missing attribute value.data-wp-html='"state.html"'no longer renders the token: the value is never JSON-decoded. A!applies after the reserved-name check, and!!state.xresolves to nothing, as in the client.data-wp-htmlis ignored, with a notice, when the element also carriesdata-wp-text,data-wp-eachordata-wp-router-regionin any form (with a suffix or a unique ID, anddata-wp-eachon any element).data-wp-htmlelement are never processed, and derived-state closures referenced only there are not listed in the client data.data-wp-interactiveonly. When that attribute names no namespace (no value, an empty value, invalid JSON, a reserved word), the element is emptied as the client empties it. Core produces this markup: the fit-text typography support adds a valuelessdata-wp-interactive. A suffixeddata-wp-interactive--xordata-wp-interactive---xdoes not start an island, as in the client.textarea,title,scriptor other raw-text element is emptied for a value that is not a token, and keeps its text, with a notice, for a token. Void elements are left as written.data-wp-htmlelement is replaced in one forward pass instead of seeking back per element, so a call with more than 1,000data-wp-htmlelements is processed instead of being returned unchanged.wp_interactivity_process_directives()call made while another is running, for example from a derived-state closure, saves and restores the state of the outer call. Previously the outer call either ended in a fatal error or printed a raw<!--wp-interactivity-dangerous-html:N-->placeholder.Not in scope
svgandmathelements are still not processed, as for every other directive.<svg />or<math />.Testing
npm run test:php -- --group interactivity-apiTrac ticket: https://core.trac.wordpress.org/ticket/66246
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Fable and Opus
Used for: Code, tests, and comments in this PR were autonomously generated by AI and reviewed by @SantosGuillamot before the PR was opened.
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.
🤖 Generated with Claude Code