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
pull/11093/head
Simon H 1 year ago committed by GitHub
parent 3c2f4d2d55
commit 5a1c756a4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: execute sole static script tag

@ -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);
}
}
}

@ -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 || {} });
}

@ -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');
}

@ -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,
`<main>
<div><script></script></div><script>document.body.innerHTML = 'this should not be executed'</script>
<script></script><script>document.body.innerHTML = 'this neither'</script>
</main>`
);
}
});

@ -0,0 +1,7 @@
<div>
<script></script>
</div>
{@html `<script>document.body.innerHTML = 'this should not be executed'</script>`}
{#if true}
<script></script>{@html `<script>document.body.innerHTML = 'this neither'</script>`}
{/if}

@ -3,6 +3,12 @@ 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.
html: `<div></div><script>document.body.innerHTML = 'this should not be executed'</script>`,
mode: ['client']
mode: ['client'],
test({ window, assert }) {
// In here to give effects etc time to execute
assert.htmlEqual(
window.document.body.innerHTML,
`<main><div></div><script>document.body.innerHTML = 'this should not be executed'</script></main>`
);
}
});

@ -0,0 +1,11 @@
import { test } from '../../assert';
export default test({
// Test that template with sole script tag does execute 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, 'this should be executed');
}
});

@ -0,0 +1,4 @@
<div></div>
{#if true}
<script>document.body.innerHTML = 'this should be executed'</script>
{/if}

@ -194,10 +194,10 @@ async function run_test(
});
if (build_result_ssr) {
const html = await page.evaluate(
const result: any = await page.evaluate(
build_result_ssr.outputFiles[0].text + '; test_ssr.default()'
);
await page.setContent('<main>' + html + '</main>');
await page.setContent('<head>' + result.head + '</head><main>' + result.html + '</main>');
} else {
await page.setContent('<main></main>');
}

Loading…
Cancel
Save