From 7903a3551986cceec7269bc149797c84ae6cac1f Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Sun, 9 Mar 2025 22:56:24 +0100 Subject: [PATCH] fix: add already connected validation --- .../docs/98-reference/.generated/client-errors.md | 6 ++++++ packages/svelte/messages/client-errors/errors.md | 4 ++++ .../internal/client/dom/blocks/svelte-element.js | 11 ++++++++++- packages/svelte/src/internal/client/errors.js | 15 +++++++++++++++ .../svelte-element-element-connected/_config.js | 13 +++++++++++++ .../svelte-element-element-connected/main.svelte | 9 +++++++++ 6 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/svelte-element-element-connected/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/svelte-element-element-connected/main.svelte diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md index 2c2e0707ea..687c427094 100644 --- a/documentation/docs/98-reference/.generated/client-errors.md +++ b/documentation/docs/98-reference/.generated/client-errors.md @@ -133,3 +133,9 @@ Reading state that was created inside the same derived is forbidden. Consider us ``` Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state` ``` + +### svelte_element_already_connected + +``` +You can't use an HTML element as the `this` attribute of `svelte:element` if it's already connected to a document +``` diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md index ce1f222c63..051c4fabae 100644 --- a/packages/svelte/messages/client-errors/errors.md +++ b/packages/svelte/messages/client-errors/errors.md @@ -87,3 +87,7 @@ See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-long ## state_unsafe_mutation > Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state` + +## svelte_element_already_connected + +> You can't use an HTML element as the `this` attribute of `svelte:element` if it's already connected to a document diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js index b4fe477972..81dfe324a5 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -23,6 +23,7 @@ import { DEV } from 'esm-env'; import { EFFECT_TRANSPARENT } from '../../constants.js'; import { assign_nodes } from '../template.js'; import { is_raw_text_element } from '../../../../utils.js'; +import * as e from '../../errors.js'; /** * @param {Comment | Element} node @@ -70,11 +71,19 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio block(() => { const next_tag = get_tag() || null; - var ns = get_namespace ? get_namespace() : is_svg || next_tag === 'svg' ? NAMESPACE_SVG : null; + var ns = get_namespace + ? get_namespace() + : is_svg || (typeof next_tag === 'string' ? next_tag === 'svg' : next_tag?.tagName === 'svg') + ? NAMESPACE_SVG + : null; // Assumption: Noone changes the namespace but not the tag (what would that even mean?) if (next_tag === tag) return; + if (typeof next_tag !== 'string' && next_tag?.isConnected) { + e.svelte_element_already_connected(); + } + // See explanation of `each_item_block` above var previous_each_item = current_each_item; set_current_each_item(each_item_block); diff --git a/packages/svelte/src/internal/client/errors.js b/packages/svelte/src/internal/client/errors.js index 682816e1d6..6e4098f1a2 100644 --- a/packages/svelte/src/internal/client/errors.js +++ b/packages/svelte/src/internal/client/errors.js @@ -335,4 +335,19 @@ export function state_unsafe_mutation() { } else { throw new Error(`https://svelte.dev/e/state_unsafe_mutation`); } +} + +/** + * You can't use an HTML element as the `this` attribute of `svelte:element` if it's already connected to a document + * @returns {never} + */ +export function svelte_element_already_connected() { + if (DEV) { + const error = new Error(`svelte_element_already_connected\nYou can't use an HTML element as the \`this\` attribute of \`svelte:element\` if it's already connected to a document\nhttps://svelte.dev/e/svelte_element_already_connected`); + + error.name = 'Svelte error'; + throw error; + } else { + throw new Error(`https://svelte.dev/e/svelte_element_already_connected`); + } } \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-element-element-connected/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-element-element-connected/_config.js new file mode 100644 index 0000000000..8a94e24076 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-element-element-connected/_config.js @@ -0,0 +1,13 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target }) { + const btn = target.querySelector('button'); + assert.throws(() => { + flushSync(() => { + btn?.click(); + }); + }, 'svelte_element_already_connected'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-element-element-connected/main.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-element-element-connected/main.svelte new file mode 100644 index 0000000000..558a65a711 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-element-element-connected/main.svelte @@ -0,0 +1,9 @@ + + +
+ + + \ No newline at end of file