innerHTML 的新替代方案

3,564 阅读3分钟

浏览器支持说明:所有浏览器都支持 setHTMLUnsafesetHTML 仍在标准化中,仅在 Firefox 中可用。 getHTML 自版本 125 起在 Chrome 和 Edge 中受支持。

浏览器最近实施了一种新的 setHTMLUnsafe 方法。这里的 Unsafe 意味着,就像 innerHTML 一样,它不执行输入清理。这种命名方式与以前的浏览器 API 并不一致:我们有 innerHTML,但没有 innerHTMLUnsafe;有 eval(),但没有 evalUnsafe(),等等。但与老方法不同的是,它既有安全版本(setHTML),也有不安全版本(setHTMLUnsafe)--这就是命名的由来。

Sanitizer API 规范如下:

“safe”方法不会生成任何执行脚本的标记。也就是说,它们应该不会受到 XSS 的影响。

假设我们有一个带有文本 <input> 的 HTML 表单和一些根据用户提供的值更改 DOM 的 JavaScript 代码:

form.addEventListener('submit', function(event) {
    event.preventDefault();
    const markup = `<h2>${input.value}</h2>`
    div.innerHTML = markup;
});

如果用户在输入中输入 <img src=doesnotexist onerror="alert('Potential XSS Attack')"> ,该 JavaScript 代码将在浏览器中运行。 .setHTMLUnsafe() 也有同样的问题。

在这个简单的示例中,代码仅在用户自己的浏览器中运行,但如果此类用户输入存储在数据库中并用于向其他人显示动态内容,则任意且潜在恶意的 JavaScript 可能会在其他用户的浏览器中运行。

使用 setHTML 时,插入 DOM 的内容只有 <img src="doesnotexist"> 。图像仍被注入页面,但 JavaScript 已被剥离。

Sanitizer API 仍在进行中,但它有助于将 setHTMLUnsafe 的命名放在上下文中。

setHTMLUnsafe

如果我们(希望)能得到 setHTML,而且我们已经有了 innerHTML,那为什么还需要 setHTMLUnsafe 呢?答案就是声明式 shadow DOM。

HTML <template> 元素可以通过两种不同的方式使用:

  • 保存未渲染但稍后可以通过 JavaScript 使用的 HTML 片段。
  • 立即生成 Shadow DOM。如果 <template> 包含 shadowrootmode 属性,则该元素在 DOM 中将在 Shadow 根内替换为其内容。

innerHTML 可以很好地处理第一个用例,但无法处理第二个用例。

const main = document.querySelector('main');
main.innerHTML = `
    <h2>I am in the Light DOM</h2>
    <div>
    <template shadowrootmode="open">
        <style>
        h2 { color: blue; }
        </style>
        <h2>Shadow DOM</h2>
    </template>
    </div>`

innerHTML 确实将 <template> 注入到页面中,但它仍然是一个 <template> 元素 - 它不会变成shadow DOM,并且它的内容不会被渲染,无论 shadowrootmode 属性如何。

setHTML 将有目的地删除 template 及其内容:

const main = document.querySelector('main');
main.setHTML(`
     <h2>I am in the Light DOM</h2>
    <div>
    <template shadowrootmode="open">
        <style>
        h2 { color: blue; }
        </style>
        <h2>Shadow DOM</h2>
    </template>
    </div>`);

在上面的示例中, main 的内容现在是 h2 和空的 div。该 template 被视为“unsafe node”。

这就是浏览器添加 setHTMLUnsafe 的原因,作为向页面动态添加声明性 shadow DOM 的一种方式。

main.setHTMLUnsafe(`
    <h2>I am in the Light DOM</h2>
    <div>
    <template shadowrootmode="open">
        <style>
        h2 { color: blue; }
        </style>
        <h2>Shadow DOM</h2>
    </template>
    </div>
`);

当使用 setHTMLUnsafe 时, <template> 的内容将在 Shadow DOM 内部渲染。

getHTML

setHTMLsetHTMLUnsafe 本身并不能完全替代 innerHTMLsetHTMLsetHTMLUnsafe 的补充函数是 getHTML(没有不安全版本)。

const main = document.querySelector('main');
const html = main.getHTML();

默认情况下 getHTML 不会从 shadow DOM 中返回任何标记,但它是可配置的。

const main = document.querySelector('main');
const html = main.getHTML({serializableShadowRoots: true} );

serializableShadowRoots 设置为 true 将序列化所有选择序列化的 shadow DOM 树。

template 元素可以使用 shadowrootserializable 属性选择加入:

<template shadowrootmode="open" shadowrootserializable>

类似地,在 JavaScript 中, attachShadow 方法有一个布尔值 serializable 选项。

this.attachShadow({ mode: "open", serializable: true });

还可以通过传递 shadow 根数组,只序列化某些指定的 shadow DOM 树:

const markup= main.getHTML({
    shadowRoots: [document.querySelector('.example').shadowRoot]
});

数组中的所有 shadow root 都将被序列化,即使它们没有标记为可序列化。


原文:fullystacked.net/innerhtml-a…