Skip to content

bind:textContent causes script execution errors with code containing <script> tags #17195

@johannesmutter

Description

@johannesmutter

Describe the bug

When using bind:textContent on a contenteditable element with a string value containing <script> tags and import statements, the browser attempts to execute the code, resulting in the error:

Uncaught SyntaxError: Cannot use import statement outside a module

Reproduction

<script>
	let code_example = $state(`<script>
  import { Component } from 'library';
  console.log('This should not execute');
<\/script>`);
	
	let editor_element = $state();
</script>

<div
	bind:this={editor_element}
	contenteditable="true"
	bind:textContent={code_example}
></div>

Expected Behavior

The <script> tag should be rendered as plain text in the contenteditable div, and no JavaScript should be executed.

Actual Behavior

The browser attempts to parse and execute the script content, throwing:

Uncaught SyntaxError: Cannot use import statement outside a module (at (index):XXXX)

Analysis

According to MDN documentation, setting textContent should be safe:

"Setting textContent on a node removes all of the node's children and replaces them with a single text node with the given string value."

Text nodes should not be parsed as HTML or executed as JavaScript. However, Svelte's bind:textContent appears to be doing something that triggers script evaluation.

Compiled Output

The compiled code shows:

ho("textContent", E, e)

This binding mechanism seems to be causing the issue.

Workaround

Replace bind:textContent with direct assignment in $effect:

<script>
	let code_example = $state(`<script>
  import { Component } from 'library';
<\/script>`);
	
	let editor_element = $state();
	
	$effect(() => {
		if (editor_element) {
			editor_element.textContent = code_example;
		}
	});
</script>

<div
	bind:this={editor_element}
	contenteditable="true"
	oninput={(e) => {
		code_example = e.currentTarget.textContent || '';
	}}
	class="code-editor"
></div>

This works correctly and doesn't cause script execution.

Context

This was discovered while building a Svelte syntax highlighter component that displays code examples. The component uses contenteditable with bind:textContent to allow editing while applying CSS highlight API for syntax highlighting:

Logs

System Info

System:
    OS: macOS 15.5
  Binaries:
    Node: 22.17.1 - .../.nvm/versions/node/v22.17.1/bin/node
    Yarn: 1.22.22 - .../usr/local/bin/yarn
    npm: 11.6.2 - .../.nvm/versions/node/v22.17.1/bin/npm
    bun: 0.8.0 - .../.bun/bin/bun
    Watchman: 2024.10.21.00 - /usr/local/bin/watchman
  Browsers:
    Chrome: 142.0.7444.176
    Safari: 26.1
  npmPackages:
    svelte: ^5.41.0 => 5.43.6

Severity

annoyance

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions