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/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 8d3b5c04958f..1e2df13201bf 100644 --- a/packages/svelte/src/internal/client/dom/reconciler.js +++ b/packages/svelte/src/internal/client/dom/reconciler.js @@ -1,6 +1,27 @@ +/** @import { TrustedTypePolicy } from 'trusted-types' */ + +const policy = /* @__PURE__ */ globalThis?.window?.trustedTypes?.createPolicy( + 'svelte-trusted-html', + { + /** @param {string} html */ + createHTML: (html) => { + return html; + } + } +); + /** @param {string} html */ -export function create_fragment_from_html(html) { +function create_trusted_html(html) { + return /** @type {string} */ (policy?.createHTML(html) ?? html); +} + +/** + * @param {string} html + * @param {boolean} untrusted + */ +export function create_fragment_from_html(html, untrusted = false) { var elem = document.createElement('template'); - elem.innerHTML = html.replaceAll('', ''); // XHTML compliance + html = html.replaceAll('', ''); // XHTML compliance + elem.innerHTML = untrusted ? html : create_trusted_html(html); 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