ShadowRoot: setHTMLUnsafe() method
Baseline2024Newly available
Since April 2024, this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.
ThesetHTMLUnsafe()
method of theShadowRoot
interface can be used to parse a string of HTML into aDocumentFragment
, optionally filtering out unwanted elements and attributes, and then use it to replace the existing tree in the Shadow DOM.
Unlike withShadowRoot.setHTML()
, XSS-unsafe HTML entities are not guaranteed to be removed.
Syntax
setHTMLUnsafe(input)setHTMLUnsafe(input, options)
Parameters
input
A string or
TrustedHTML
instance defining HTML to be parsed.options
OptionalAn options object with the following optional parameters:
sanitizer
OptionalA
Sanitizer
orSanitizerConfig
object which defines what elements of the input will be allowed or removed.Note that generally a"Sanitizer
is expected than the to be more efficient than aSanitizerConfig
if the configuration is to reused.If not specified, no sanitizer is used.
Return value
None (undefined
).
Exceptions
TypeError
This is thrown if:
html
is passed a string whenTrusted Types areenforced by a CSP and no default policy is defined.options.sanitizer
is passed a:- value that is not a
Sanitizer
,SanitizerConfig
, or string. - non-normalized
SanitizerConfig
(one that includes both "allowed" and "removed" configuration settings). - string that does not have the value
"default"
.
- value that is not a
Description
ThesetHTMLUnsafe()
method can be used to parse a string of HTML, optionally filtering out unwanted elements and attributes, and use it to replace the existing Shadow DOM.
The suffix "Unsafe" in the method name indicates that while the method does allow the input string to be filtered of unwanted HTML entities, it does not enforce the sanitization or removal of potentially unsafe XSS-relevant input, such as<script>
elements, and script or event handler content attributes.If no sanitizer configuration is specified in theoptions.sanitizer
parameter,setHTMLUnsafe()
is used without any sanitization.
The input HTML may includedeclarative shadow roots.If the string of HTML defines more than onedeclarative shadow root in a particular shadow host then only the firstShadowRoot
is created — subsequent declarations are parsed as<template>
elements within that shadow root.
setHTMLUnsafe()
should be instead ofShadowRoot.setHTML()
when parsing potentially unsafe strings of HTML that for whatever reason need to contain XSS-unsafe elements or attributes.If strings to be injected don't need to contain unsafe HTML entities, then you should useShadowRoot.setHTML()
.
Note that since this method does not necessarily sanitize input strings of XSS-unsafe entities, input strings should also be validated using theTrusted Types API.If the method is used with both a trusted types and a sanitizer, the input string will be passed through the trusted transformation function before it is sanitized.
Examples
Basic usage
This example shows some of the ways you can usesetHTMLUnsafe()
to sanitize and inject a string of HTML.
First we will create theShadowRoot
we want to target.This could be created programmatically usingElement.attachShadow()
but for this example we'll create the root declaratively.
<div> <template shadowrootmode="open"> <span>A span element in the shadow DOM</span> </template></div>
We can get a handle to the shadow root from the#host
element like this:
const shadow = document.querySelector("#host").shadowRoot;
The code below shows how we can callsetHTMLUnsafe()
with a string and different sanitizers in order to filter and inject the HTML into the shadow root.
// Define unsanitized string of HTMLconst unsanitizedString = "abc <script>alert(1)<" + "/script> def";// setHTMLUnsafe() with no sanitizer (no filtering)shadow.setHTMLUnsafe(unsanitizedString);// Define custom Sanitizer and use in setHTMLUnsafe()// This allows only elements: <div>, <p>, <span>, <script> (<script> is unsafe)const sanitizer1 = new Sanitizer({ elements: ["div", "p", "span", "script"] });shadow.setHTMLUnsafe(unsanitizedString, { sanitizer: sanitizer1 });// Define custom SanitizerConfig within setHTMLUnsafe()// This removes only the scriptshadow.setHTMLUnsafe(unsanitizedString, { sanitizer: { removeElements: ["script"] },});
setHTMLUnsafe()
live example
This example provides a "live" demonstration of the method when called with different sanitizers.The code defines buttons that you can click to sanitize and inject a string of HTML using a default and a custom sanitizer, respectively.The original string and sanitized HTML are logged so you can inspect the results in each case.
HTML
The HTML defines two<button>
elements for injecting the HTML with no sanitizer and with a custom sanitizer (respectively), another button to reset the example, and a<div>
that contains the declarative shadow root.
<button type="button">None</button><button type="button">allowScript</button><button type="button">Reload</button><div> <template shadowrootmode="open"> <span>I am in the shadow DOM </span> </template></div>
<pre></pre>
#log { height: 250px; overflow: scroll; padding: 0.5rem; border: 1px solid black; margin: 5px;}
JavaScript
const logElement = document.querySelector("#log");function log(text) { logElement.textContent += text;}
if ("Sanitizer" in window) {
First we define the handler for the reload button.
const reload = document.querySelector("#reload");reload.addEventListener("click", () => document.location.reload());
Then we define the input string to inject into the shadow root, which will be the same for all cases.This contains the<script>
element and theonclick
handler, both of which are considered XSS-unsafe.We also get variableshadow
, which is our handle to the shadow root.
// Define unsafe string of HTMLconst unsanitizedString = ` <div> <p>Paragraph to inject into shadow DOM. <button>Click me</button></p> <script src="path/to/a/module.js" type="module"><script> </div>`;const shadow = document.querySelector("#host").shadowRoot;
Next we define the click handler for the button that sets the shadow root usingsetHTMLUnsafe()
without passing a sanitizer.As there is no sanitizer, we expect the injected HTML to match the input string.
const buttonNoSanitizer = document.querySelector("#buttonNoSanitizer");buttonNoSanitizer.addEventListener("click", () => { // Set the content of the element with no sanitizer shadow.setHTMLUnsafe(unsanitizedString); // Log HTML before sanitization and after being injected logElement.textContent = "No sanitizer\n\n"; log(`\nunsanitized: ${unsanitizedString}`); log(`\nsanitized: ${shadow.innerHTML}`);});
The next click handler sets the target HTML using a custom sanitizer that allows only<div>
,<p>
, and<script>
elements.
const allowScriptButton = document.querySelector("#buttonAllowScript");allowScriptButton.addEventListener("click", () => { // Set the content of the element using a custom sanitizer const sanitizer1 = new Sanitizer({ elements: ["div", "p", "script"], }); shadow.setHTMLUnsafe(unsanitizedString, { sanitizer: sanitizer1 }); // Log HTML before sanitization and after being injected logElement.textContent = "Sanitizer: {elements: ['div', 'p', 'script']}\n"; log(`\nunsanitized: ${unsanitizedString}`); log(`\nsanitized: ${shadow.innerHTML}`);});
} else { log("The HTML Sanitizer API is NOT supported in this browser."); // Provide fallback or alternative behavior}
Results
Click the "None" and "allowScript" buttons to see the effects of no sanitizer and a custom sanitizer, respectively.
When you click the "None" button, you should see that the input and output match, as no sanitizer is applied.When you click the "allowScript" button the<script>
element is still present, but the<button>
element is removed.With this approach you can create safe HTML, but you aren't forced to.
Specifications
Specification |
---|
HTML # dom-shadowroot-sethtmlunsafe |