Skip to content

Interactivity API: Show the data-wp-html fallback on created elements under Trusted Types - #84924

Open
luisherranz wants to merge 5 commits into
trunkfrom
fix/interactivity-data-wp-html-created-element-fallback
Open

luisherranz wants to merge 5 commits into
trunkfrom
fix/interactivity-data-wp-html-created-element-fallback

Conversation

@luisherranz

@luisherranz luisherranz commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

What?

Follow-up to #84652 and #84889.

Elements the client creates instead of hydrating, such as data-wp-each items added after hydration or elements a client-side navigation adds, now show their data-wp-html fallback on sites enforcing Trusted Types while their value is pending or { "wpServerHtml": true }. This removes the known limitation documented in #84652.

Why?

When Preact creates an element, it writes dangerouslySetInnerHTML.__html with innerHTML. While the value was pending, the directive handed Preact the fallback as a plain string, so a site enforcing Trusted Types rejected it, or a default policy rewrote it. Created elements on those sites therefore had to have an empty fallback. The package deliberately creates no Trusted Types policy of its own, so writing the fallback as a string can't be made to work.

How?

  • toVdom() keeps a detached copy of each data-wp-html element's content, next to the existing string, without its <script> elements (getHtmlFallback()).
  • The directive always gives Preact the policy-free empty HTML, so Preact never writes HTML for it.
  • In a layout effect, before paint, at the first commit on each host element, the directive inserts a copy of the snapshot with importNode() when the element is empty and its value is pending or the server representation. Cloning and inserting nodes isn't a Trusted Types sink. Later values follow the existing value rule, and the fallback doesn't come back after a value empties the element.
  • The directive's record of the HTML it last applied now belongs to the host element, not to its component. Preact can keep the directive's component while creating a new host beneath it, for example when navigation swaps a div for a span at the same position, and the old host's record would otherwise leave the new host empty.
  • <script> elements are left out of the copy so they never run. Scripts written with innerHTML never ran either, but inserted copies of template content would.

Trade-off: the copy is taken for every data-wp-html element each time toVdom() runs (the initial page and every page the router fetches), even when the value is a token. It can't be taken lazily, because the original element may have changed by the time a new host needs it.

Testing Instructions

  1. Activate the "Gutenberg Test Interactive Blocks" plugin (it ships with the e2e test environment) and publish a post containing the test/directive-html block.
  2. Open the post with ?require_trusted_types=true appended to the URL. The response carries Content-Security-Policy: require-trusted-types-for 'script'.
  3. Click "Add fallback". The new item shows "Loading". On trunk it stays empty, and the console reports a Trusted Types violation.
  4. Click "Add script items". The items show "Loading", and window.fallbackRuns stays undefined.
  5. Automated:
    • npm run test:unit:vitest -- packages/interactivity
    • npm run test:e2e -- test/e2e/specs/interactivity/directive-html.spec.ts test/e2e/specs/interactivity/router-html.spec.ts

Testing Instructions for Keyboard

No UI changes.

Use of AI Tools

100% of the code, tests, and comments in this PR were autonomously generated by AI and reviewed by @luisherranz before the PR was opened.

@github-actions github-actions Bot added [Package] E2E Tests /packages/e2e-tests [Package] Interactivity /packages/interactivity labels Oct 16, 2026
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

Preview in WordPress Playground

🎉 Props

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: luisherranz <luisherranz@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Updated as activity occurs, without notifying anyone named here. Add the props-bot label to refresh.

📦 Bundle size

Size Change: +242 B (0%)

Total Size: 8.85 MB

📦 View Changed
Filename Size Change
build/modules/interactivity/index.min.js 16 kB +242 B (+1.54%)

e22c7ca Run

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric 84eeb58 trunk % Change
timeToFirstByte 40.15 ms +12.45% -1.25% 42.95 ms +10.01% -7.92% -6.52%
largestContentfulPaint 72 ms +5.56% -5.56% 72 ms +5.56% -8.33% 0%
lcpMinusTtfb 28.35 ms +22.57% -5.29% 28 ms +16.43% -10.18% 1.25%
wpBeforeTemplate 21.4 ms +10.93% -1.54% 23.57 ms +12.26% -10.82% -9.21%
wpTemplate 16.04 ms +2.99% -5.92% 15.95 ms +4.58% -2.57% 0.56%
wpTotal 37.15 ms +13.08% -2.07% 39.42 ms +9.89% -7.26% -5.76%
wpMemoryUsage 7.62 MB +0% -0% 7.58 MB +0% -0% 0.46%
wpDbQueries 17 +0% -0% 17 +0% -0% 0%

front-end-classic-theme

Metric 84eeb58 trunk % Change
timeToFirstByte 33.4 ms +5.84% -0.9% 33.75 ms +10.81% -1.93% -1.04%
largestContentfulPaint 76 ms +5.26% -0% 76 ms +5.26% -0% 0%
lcpMinusTtfb 43.05 ms +0.93% -1.05% 42.85 ms +1.05% -1.4% 0.47%
wpBeforeTemplate 18.65 ms +2.9% -0.54% 18.94 ms +15.95% -2.22% -1.53%
wpTemplate 12.22 ms +3.27% -1.15% 12.35 ms +3.24% -1.86% -1.05%
wpTotal 30.89 ms +6.38% -0.87% 31.12 ms +11.54% -1.67% -0.74%
wpMemoryUsage 6.28 MB +0% -0% 6.24 MB +0% -0% 0.58%
wpDbQueries 14 +0% -0% 14 +0% -0% 0%

media-processing

Metric 84eeb58 trunk % Change
mediaProcessingJpeg 318.31 ms +1.6% -0.77% 320.28 ms +0.56% -0.52% -0.62%
mediaProcessingAvif 4783.56 ms +0.17% -0.1% 4794.13 ms +0.13% -0.11% -0.22%
mediaProcessingJpegToAvif 3340.2 ms +0.29% -0.08% 3377.02 ms +0.29% -0.58% -1.09%

media-upload

Metric 84eeb58 trunk % Change
jpegUploadProcessing 1404.8 ms +0.52% -0.29% 1402.53 ms +0.69% -0.26% 0.16%
pngUploadProcessing 174.98 ms +4.71% -1.89% 172.63 ms +8.37% -1.86% 1.36%
largeJpegUploadProcessing 1395.52 ms +0.98% -0.49% 1403.43 ms +0.29% -0.88% -0.56%
multipleImageUploadProcessing 1450.11 ms +1.03% -0.63% 1452.54 ms +1.15% -0.67% -0.17%

post-editor

Metric 84eeb58 trunk % Change
serverResponse 333.73 ms +4.95% -12.57% 327.9 ms +5.84% -7.03% 1.78%
firstPaint 390.69 ms +1.96% -42.25% 365.04 ms +16.69% -41.23% 7.03%
domContentLoaded 882.2 ms +3.04% -0.47% 954.83 ms +2.14% -2.79% -7.61%
loaded 883.29 ms +3.08% -0.43% 956.16 ms +2.12% -2.81% -7.62%
firstContentfulPaint 397.28 ms +1.31% -2.22% 432.24 ms +3.2% -5.08% -8.09%
firstBlock 2647.28 ms +0.23% -0.55% 2788.11 ms +0.74% -1.78% -5.05%
type 15.79 ms +5.51% -4.75% 16.78 ms +8.1% -6.26% -5.9%
typeWithoutInspector 15.04 ms +4.26% -3.59% 16.28 ms +2.95% -2.89% -7.62%
typeWithTopToolbar 20.66 ms +2.23% -2.95% 21.68 ms +4.8% -6.04% -4.7%
typeContainer 7.63 ms +7.34% -2.1% 7.78 ms +14.01% -8.74% -1.93%
focus 103.34 ms +2.16% -11.77% 99.9 ms +3.77% -6.19% 3.44%
firstFocus 185.98 ms +0% -0% 198.69 ms +0% -0% -6.4%
selectAll 443.6 ms +9.72% -0.97% 478.16 ms +0.67% -3.26% -7.23%
listViewOpen 63.76 ms +1.52% -6.21% 63.78 ms +6.76% -2.73% -0.03%
inserterOpen 33.56 ms +5.39% -3.72% 34.27 ms +7.21% -9.4% -2.07%
inserterHover 10.33 ms +7.65% -7.26% 9.6 ms +7.5% -4.06% 7.6%
inserterSearch 6.71 ms +19.52% -4.92% 7.42 ms +12.4% -6.33% -9.57%
loadPatterns 509.77 ms +4.67% -3.29% 520.68 ms +4.79% -3.51% -2.1%
wpTotal 325.09 ms +4.71% -12.85% 319.03 ms +6.02% -7.24% 1.9%
wpMemoryUsage 13.51 MB +0% -0% 13.47 MB +0% -0% 0.28%
wpDbQueries 54 +0% -1.85% 54 +0% -1.85% 0%

site-editor

Metric 84eeb58 trunk % Change
serverResponse 302.68 ms +3% -6.54% 297.78 ms +2.77% -1.37% 1.65%
firstPaint 213.71 ms +20.23% -9.44% 193.65 ms +88.85% -9.47% 10.36%
domContentLoaded 920.06 ms +2.14% -2.03% 928.36 ms +1.33% -3.11% -0.89%
loaded 920.76 ms +2.18% -2.01% 929.26 ms +1.31% -3.11% -0.91%
firstContentfulPaint 389.19 ms +3.75% -0.79% 391.26 ms +1.21% -6.53% -0.53%
firstBlock 3430.33 ms +0.61% -1.39% 3406.16 ms +0.68% -1.48% 0.71%
type 14.41 ms +6.04% -2.22% 14.77 ms +1.22% -3.05% -2.44%
navigate 88.71 ms +4.53% -8.75% 95.26 ms +16.06% -12.05% -6.88%
loadPatterns 1195.16 ms +8.18% -12.27% 1172.66 ms +10.91% -11.57% 1.92%
loadPages 1028.21 ms +1.2% -3.12% 1025 ms +2.59% -1.13% 0.31%
wpTotal 294.26 ms +3.16% -6.74% 289.62 ms +2.87% -1.53% 1.6%
wpMemoryUsage 12.56 MB +0% -0% 12.53 MB +0% -0% 0.3%
wpDbQueries 43 +2.33% -0% 43.5 +1.15% -1.15% -1.15%

e22c7ca Run

@luisherranz luisherranz added [Type] Bug An existing feature does not function as intended and removed [Package] E2E Tests /packages/e2e-tests labels Oct 16, 2026
@luisherranz luisherranz self-assigned this Oct 16, 2026
@github-actions github-actions Bot added the [Package] E2E Tests /packages/e2e-tests label Oct 16, 2026
@luisherranz luisherranz mentioned this pull request Oct 16, 2026
5 of 10 tasks
Base automatically changed from add/interactivity-data-wp-html-server-representation to trunk October 16, 2026 12:27
Comment thread docs/reference-guides/interactivity-api/directives-and-store.md Outdated
Comment thread docs/reference-guides/interactivity-api/directives-and-store.md Outdated
@luisherranz
luisherranz force-pushed the fix/interactivity-data-wp-html-created-element-fallback branch from 682a4e9 to e22c7ca Compare October 16, 2026 13:22

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] E2E Tests /packages/e2e-tests [Package] Interactivity /packages/interactivity [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant