Skip to content

Commit 5a1c756

Browse files
authored
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
1 parent 3c2f4d2 commit 5a1c756

File tree

11 files changed

+75
-9
lines changed

11 files changed

+75
-9
lines changed

.changeset/cuddly-points-tickle.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: execute sole static script tag

packages/svelte/src/internal/client/dom/template.js

+18-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { clone_node, empty } from './operations.js';
33
import { create_fragment_from_html } from './reconciler.js';
44
import { current_effect } from '../runtime.js';
55
import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants.js';
6+
import { effect } from '../reactivity/effects.js';
67

78
/**
89
* @param {string} content
@@ -120,14 +121,29 @@ export function svg_template_with_script(content, flags) {
120121
* @param {Element | DocumentFragment} node
121122
*/
122123
function run_scripts(node) {
123-
for (const script of node.querySelectorAll('script')) {
124+
// scripts were SSR'd, in which case they will run
125+
if (hydrating) return;
126+
127+
const scripts =
128+
/** @type {HTMLElement} */ (node).tagName === 'SCRIPT'
129+
? [/** @type {HTMLScriptElement} */ (node)]
130+
: node.querySelectorAll('script');
131+
for (const script of scripts) {
124132
var clone = document.createElement('script');
125133
for (var attribute of script.attributes) {
126134
clone.setAttribute(attribute.name, attribute.value);
127135
}
128136

129137
clone.textContent = script.textContent;
130-
script.replaceWith(clone);
138+
// If node === script tag, replaceWith will do nothing because there's no parent yet,
139+
// waiting until that's the case using an effect solves this.
140+
// Don't do it in other circumstances or we could accidentally execute scripts
141+
// in an adjacent @html tag that was instantiated in the meantime.
142+
if (script === node) {
143+
effect(() => script.replaceWith(clone));
144+
} else {
145+
script.replaceWith(clone);
146+
}
131147
}
132148
}
133149

packages/svelte/tests/runtime-browser/driver-ssr.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ import config from '__CONFIG__';
66
import { render } from 'svelte/server';
77

88
export default function () {
9-
return render(SvelteComponent, { props: config.props || {} }).html;
9+
return render(SvelteComponent, { props: config.props || {} });
1010
}

packages/svelte/tests/runtime-legacy/samples/head-script/_config.js renamed to packages/svelte/tests/runtime-browser/samples/head-script/_config.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { test } from '../../test';
1+
import { test } from '../../assert';
22

33
export default test({
4-
test({ assert, component, window }) {
4+
test({ assert, window }) {
55
document.dispatchEvent(new Event('DOMContentLoaded'));
66
assert.equal(window.document.querySelector('button')?.textContent, 'Hello world');
77
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { test } from '../../assert';
2+
3+
export default test({
4+
// Test that @html does not execute scripts when instantiated in the client.
5+
// Needs to be in this test suite because JSDOM does not quite get this right.
6+
mode: ['client'],
7+
test({ window, assert }) {
8+
// In here to give effects etc time to execute
9+
assert.htmlEqual(
10+
window.document.body.innerHTML,
11+
`<main>
12+
<div><script></script></div><script>document.body.innerHTML = 'this should not be executed'</script>
13+
<script></script><script>document.body.innerHTML = 'this neither'</script>
14+
</main>`
15+
);
16+
}
17+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<div>
2+
<script></script>
3+
</div>
4+
{@html `<script>document.body.innerHTML = 'this should not be executed'</script>`}
5+
{#if true}
6+
<script></script>{@html `<script>document.body.innerHTML = 'this neither'</script>`}
7+
{/if}

packages/svelte/tests/runtime-browser/samples/html-tag-script/_config.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import { test } from '../../assert';
33
export default test({
44
// Test that @html does not execute scripts when instantiated in the client.
55
// Needs to be in this test suite because JSDOM does not quite get this right.
6-
html: `<div></div><script>document.body.innerHTML = 'this should not be executed'</script>`,
7-
mode: ['client']
6+
mode: ['client'],
7+
test({ window, assert }) {
8+
// In here to give effects etc time to execute
9+
assert.htmlEqual(
10+
window.document.body.innerHTML,
11+
`<main><div></div><script>document.body.innerHTML = 'this should not be executed'</script></main>`
12+
);
13+
}
814
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { test } from '../../assert';
2+
3+
export default test({
4+
// Test that template with sole script tag does execute when instantiated in the client.
5+
// Needs to be in this test suite because JSDOM does not quite get this right.
6+
mode: ['client'],
7+
test({ window, assert }) {
8+
// In here to give effects etc time to execute
9+
assert.htmlEqual(window.document.body.innerHTML, 'this should be executed');
10+
}
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<div></div>
2+
{#if true}
3+
<script>document.body.innerHTML = 'this should be executed'</script>
4+
{/if}

packages/svelte/tests/runtime-browser/test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,10 @@ async function run_test(
194194
});
195195

196196
if (build_result_ssr) {
197-
const html = await page.evaluate(
197+
const result: any = await page.evaluate(
198198
build_result_ssr.outputFiles[0].text + '; test_ssr.default()'
199199
);
200-
await page.setContent('<main>' + html + '</main>');
200+
await page.setContent('<head>' + result.head + '</head><main>' + result.html + '</main>');
201201
} else {
202202
await page.setContent('<main></main>');
203203
}

0 commit comments

Comments
 (0)