From 5673d6042400ad9e61dc7cb5a03b00c396d82a28 Mon Sep 17 00:00:00 2001 From: Jay Harris Date: Tue, 1 Jul 2025 15:12:23 +1200 Subject: [PATCH 1/3] [trustedTypes]: Add TrustedTypes support to Svelte --- .changeset/witty-lies-happen.md | 5 +++++ packages/svelte/package.json | 1 + .../src/internal/client/dom/reconciler.js | 21 ++++++++++++++++++- pnpm-lock.yaml | 8 +++++++ 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 .changeset/witty-lies-happen.md diff --git a/.changeset/witty-lies-happen.md b/.changeset/witty-lies-happen.md new file mode 100644 index 000000000000..01f5bed7b9c6 --- /dev/null +++ b/.changeset/witty-lies-happen.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +Add TrustedTypes support diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 2d88d2a051c4..18421174ed58 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -166,6 +166,7 @@ "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", diff --git a/packages/svelte/src/internal/client/dom/reconciler.js b/packages/svelte/src/internal/client/dom/reconciler.js index 8d3b5c04958f..2cd965e9fcb2 100644 --- a/packages/svelte/src/internal/client/dom/reconciler.js +++ b/packages/svelte/src/internal/client/dom/reconciler.js @@ -1,6 +1,25 @@ +/** @import { TrustedTypePolicy } from 'trusted-types' */ + +/** @type {Pick | undefined} */ +let policy; + +if (globalThis?.window?.trustedTypes) { + policy = globalThis.window.trustedTypes.createPolicy('svelte-trusted-html', { + /** @param {string} html */ + createHTML: (html) => { + return html; + } + }); +} + +/** @param {string} html */ +function create_trusted_html(html) { + return /** @type {string} */(policy?.createHTML(html) ?? html); +} + /** @param {string} html */ export function create_fragment_from_html(html) { var elem = document.createElement('template'); - elem.innerHTML = html.replaceAll('', ''); // XHTML compliance + elem.innerHTML = create_trusted_html(html.replaceAll('', '')); // XHTML compliance return elem.content; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7fb655b2d69..222f93fa1e34 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: '@types/estree': specifier: ^1.0.5 version: 1.0.6 + '@types/trusted-types': + specifier: ^2.0.7 + version: 2.0.7 acorn: specifier: ^8.12.1 version: 8.14.0 @@ -797,6 +800,9 @@ packages: '@types/semver@7.5.6': resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@typescript-eslint/eslint-plugin@8.26.0': resolution: {integrity: sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3059,6 +3065,8 @@ snapshots: '@types/semver@7.5.6': {} + '@types/trusted-types@2.0.7': {} + '@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.12.1 From b3ba3c72f68e78ac94a92047898d6bacbd87290f Mon Sep 17 00:00:00 2001 From: 7nik Date: Tue, 1 Jul 2025 13:48:43 +0300 Subject: [PATCH 2/3] fix treeshaking --- .../svelte/src/internal/client/dom/reconciler.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/reconciler.js b/packages/svelte/src/internal/client/dom/reconciler.js index 2cd965e9fcb2..de9826cc5526 100644 --- a/packages/svelte/src/internal/client/dom/reconciler.js +++ b/packages/svelte/src/internal/client/dom/reconciler.js @@ -1,20 +1,18 @@ /** @import { TrustedTypePolicy } from 'trusted-types' */ -/** @type {Pick | undefined} */ -let policy; - -if (globalThis?.window?.trustedTypes) { - policy = globalThis.window.trustedTypes.createPolicy('svelte-trusted-html', { +const policy = /* @__PURE__ */ globalThis?.window?.trustedTypes?.createPolicy( + 'svelte-trusted-html', + { /** @param {string} html */ createHTML: (html) => { return html; } - }); -} + } +); /** @param {string} html */ function create_trusted_html(html) { - return /** @type {string} */(policy?.createHTML(html) ?? html); + return /** @type {string} */ (policy?.createHTML(html) ?? html); } /** @param {string} html */ From 0be07cac3dc135cd13c8d589211a70e8148a9ece Mon Sep 17 00:00:00 2001 From: Jay Harris Date: Wed, 2 Jul 2025 10:21:25 +1200 Subject: [PATCH 3/3] [trustedTypes]: Mark output from @html as untrusted --- packages/svelte/src/internal/client/dom/blocks/html.js | 2 +- packages/svelte/src/internal/client/dom/reconciler.js | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js index d7190abc6668..e4896248d95e 100644 --- a/packages/svelte/src/internal/client/dom/blocks/html.js +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -97,7 +97,7 @@ export function html(node, get_value, svg = false, mathml = false, skip_warning // Don't use create_fragment_with_script_from_html here because that would mean script tags are executed. // @html is basically `.innerHTML = ...` and that doesn't execute scripts either due to security reasons. /** @type {DocumentFragment | Element} */ - var node = create_fragment_from_html(html); + var node = create_fragment_from_html(html, /*untrusted=*/ true); if (svg || mathml) { node = /** @type {Element} */ (get_first_child(node)); diff --git a/packages/svelte/src/internal/client/dom/reconciler.js b/packages/svelte/src/internal/client/dom/reconciler.js index de9826cc5526..1e2df13201bf 100644 --- a/packages/svelte/src/internal/client/dom/reconciler.js +++ b/packages/svelte/src/internal/client/dom/reconciler.js @@ -15,9 +15,13 @@ function create_trusted_html(html) { return /** @type {string} */ (policy?.createHTML(html) ?? html); } -/** @param {string} html */ -export function create_fragment_from_html(html) { +/** + * @param {string} html + * @param {boolean} untrusted + */ +export function create_fragment_from_html(html, untrusted = false) { var elem = document.createElement('template'); - elem.innerHTML = create_trusted_html(html.replaceAll('', '')); // XHTML compliance + html = html.replaceAll('', ''); // XHTML compliance + elem.innerHTML = untrusted ? html : create_trusted_html(html); return elem.content; }