From 5a1c756a4e7fa1d66f1b197906f8b80295d48b11 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Tue, 9 Apr 2024 15:19:02 +0200
Subject: [PATCH] fix: execute sole static script tag (#11095)
- take into account that template could consist of a single script tag, for which querySelectorAll('script') would yield false negatives
- add test to ensure that we don't execute script tags inside `@html` tags next to static script tags
fixes #11082
---
.changeset/cuddly-points-tickle.md | 5 +++++
.../src/internal/client/dom/template.js | 20 +++++++++++++++++--
.../tests/runtime-browser/driver-ssr.js | 2 +-
.../samples/head-script/_config.js | 4 ++--
.../samples/head-script/main.svelte | 0
.../samples/html-tag-script-2/_config.js | 17 ++++++++++++++++
.../samples/html-tag-script-2/main.svelte | 7 +++++++
.../samples/html-tag-script/_config.js | 10 ++++++++--
.../samples/sole-script-tag/_config.js | 11 ++++++++++
.../samples/sole-script-tag/main.svelte | 4 ++++
packages/svelte/tests/runtime-browser/test.ts | 4 ++--
11 files changed, 75 insertions(+), 9 deletions(-)
create mode 100644 .changeset/cuddly-points-tickle.md
rename packages/svelte/tests/{runtime-legacy => runtime-browser}/samples/head-script/_config.js (69%)
rename packages/svelte/tests/{runtime-legacy => runtime-browser}/samples/head-script/main.svelte (100%)
create mode 100644 packages/svelte/tests/runtime-browser/samples/html-tag-script-2/_config.js
create mode 100644 packages/svelte/tests/runtime-browser/samples/html-tag-script-2/main.svelte
create mode 100644 packages/svelte/tests/runtime-browser/samples/sole-script-tag/_config.js
create mode 100644 packages/svelte/tests/runtime-browser/samples/sole-script-tag/main.svelte
diff --git a/.changeset/cuddly-points-tickle.md b/.changeset/cuddly-points-tickle.md
new file mode 100644
index 0000000000..4a75553b42
--- /dev/null
+++ b/.changeset/cuddly-points-tickle.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: execute sole static script tag
diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js
index 827508b739..8e1f51c8eb 100644
--- a/packages/svelte/src/internal/client/dom/template.js
+++ b/packages/svelte/src/internal/client/dom/template.js
@@ -3,6 +3,7 @@ import { clone_node, empty } from './operations.js';
import { create_fragment_from_html } from './reconciler.js';
import { current_effect } from '../runtime.js';
import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants.js';
+import { effect } from '../reactivity/effects.js';
/**
* @param {string} content
@@ -120,14 +121,29 @@ export function svg_template_with_script(content, flags) {
* @param {Element | DocumentFragment} node
*/
function run_scripts(node) {
- for (const script of node.querySelectorAll('script')) {
+ // scripts were SSR'd, in which case they will run
+ if (hydrating) return;
+
+ const scripts =
+ /** @type {HTMLElement} */ (node).tagName === 'SCRIPT'
+ ? [/** @type {HTMLScriptElement} */ (node)]
+ : node.querySelectorAll('script');
+ for (const script of scripts) {
var clone = document.createElement('script');
for (var attribute of script.attributes) {
clone.setAttribute(attribute.name, attribute.value);
}
clone.textContent = script.textContent;
- script.replaceWith(clone);
+ // If node === script tag, replaceWith will do nothing because there's no parent yet,
+ // waiting until that's the case using an effect solves this.
+ // Don't do it in other circumstances or we could accidentally execute scripts
+ // in an adjacent @html tag that was instantiated in the meantime.
+ if (script === node) {
+ effect(() => script.replaceWith(clone));
+ } else {
+ script.replaceWith(clone);
+ }
}
}
diff --git a/packages/svelte/tests/runtime-browser/driver-ssr.js b/packages/svelte/tests/runtime-browser/driver-ssr.js
index 71a8877a9e..f5f15b6493 100644
--- a/packages/svelte/tests/runtime-browser/driver-ssr.js
+++ b/packages/svelte/tests/runtime-browser/driver-ssr.js
@@ -6,5 +6,5 @@ import config from '__CONFIG__';
import { render } from 'svelte/server';
export default function () {
- return render(SvelteComponent, { props: config.props || {} }).html;
+ return render(SvelteComponent, { props: config.props || {} });
}
diff --git a/packages/svelte/tests/runtime-legacy/samples/head-script/_config.js b/packages/svelte/tests/runtime-browser/samples/head-script/_config.js
similarity index 69%
rename from packages/svelte/tests/runtime-legacy/samples/head-script/_config.js
rename to packages/svelte/tests/runtime-browser/samples/head-script/_config.js
index 31acab66a3..b40d4e22e2 100644
--- a/packages/svelte/tests/runtime-legacy/samples/head-script/_config.js
+++ b/packages/svelte/tests/runtime-browser/samples/head-script/_config.js
@@ -1,7 +1,7 @@
-import { test } from '../../test';
+import { test } from '../../assert';
export default test({
- test({ assert, component, window }) {
+ test({ assert, window }) {
document.dispatchEvent(new Event('DOMContentLoaded'));
assert.equal(window.document.querySelector('button')?.textContent, 'Hello world');
}
diff --git a/packages/svelte/tests/runtime-legacy/samples/head-script/main.svelte b/packages/svelte/tests/runtime-browser/samples/head-script/main.svelte
similarity index 100%
rename from packages/svelte/tests/runtime-legacy/samples/head-script/main.svelte
rename to packages/svelte/tests/runtime-browser/samples/head-script/main.svelte
diff --git a/packages/svelte/tests/runtime-browser/samples/html-tag-script-2/_config.js b/packages/svelte/tests/runtime-browser/samples/html-tag-script-2/_config.js
new file mode 100644
index 0000000000..c8fbb89b7c
--- /dev/null
+++ b/packages/svelte/tests/runtime-browser/samples/html-tag-script-2/_config.js
@@ -0,0 +1,17 @@
+import { test } from '../../assert';
+
+export default test({
+ // Test that @html does not execute scripts when instantiated in the client.
+ // Needs to be in this test suite because JSDOM does not quite get this right.
+ mode: ['client'],
+ test({ window, assert }) {
+ // In here to give effects etc time to execute
+ assert.htmlEqual(
+ window.document.body.innerHTML,
+ `