繁体   English   中英

如何在影子 dom/web 组件中执行 javascript?

[英]How to execute javascript in shadow dom/web components?

我正在尝试创建一个自定义元素,其中大部分 javascript 封装/引用在模板/html 本身中。 如何从要在影子 dom 中执行的模板/元素中制作 javascript? 下面是一个例子,可以更好地理解这个问题。 如何从template.innerHTML ( <script>alert("hello"); console.log("hello from tpl");</script> ) 执行脚本?

目前我没有收到任何警报或登录控制台。 我正在用 Chrome 进行测试。

 class ViewMedia extends HTMLElement { constructor() { super(); const shadow = this.attachShadow({mode: 'closed'}); var template = document.createElement( 'template' ); template.innerHTML = '<script>alert("hello"); console.log("hello from tpl")'; shadow.appendChild( document.importNode( template.content, true ) ); } } customElements.define('x-view-media', ViewMedia);
 <x-view-media />

几点:

  1. 浏览器不再允许您通过innerHTML添加脚本
  2. DOM 中没有像 iFrame 中那样的 Web 组件的脚本沙盒。

您可以使用var el = document.createElement('script');创建脚本块然后将它们添加为子元素。

 class ViewMedia extends HTMLElement { constructor() { super(); const shadow = this.attachShadow({mode: 'closed'}); const s = document.createElement('script'); s.textContent = 'alert("hello");'; shadow.appendChild(s); } } customElements.define('x-view-media', ViewMedia);
 <x-view-media></x-view-media>

失败的原因是importNode不会评估从另一个文档导入的脚本,这本质上是使用innerHTML设置模板内容时发生的情况。 您提供的字符串将被解析为DocumentFragment ,它被视为一个单独的文档。 如果模板元素是从主文档中选择的,则脚本将按预期进行评估:

 <template id="temp"> <script> console.log('templated script'); </script> </template> <div id="host"></div> <script> let temp = document.querySelector('#temp'); let host = document.querySelector('#host'); let shadow = host.attachShadow({ mode:'closed' }); shadow.append(document.importNode(temp.content, true)); </script>

强制脚本评估的一种方法是使用上下文片段导入它们:

 <div id="host"></div> <script> let host = document.querySelector('#host'); let shadow = host.attachShadow({ mode:'closed' }); let content = `<script> console.log(this); <\/script>`; let fragment = document.createRange().createContextualFragment(content); shadow.append(document.importNode(fragment, true)); </script>

但是,这会破坏封装,因为 shadowRoot 中的脚本实际上将在全局范围内进行评估,而无法访问封闭的 shadow dom。 我想出的解决这个问题的方法是循环遍历 shadow dom 中的每个脚本,并使用 shadowRoot 对其范围进行评估。 您不能只将主机对象本身作为范围传递,因为您将无法访问已关闭的 shadowRoot。 相反,您可以访问ShadowRoot.host属性,该属性可在嵌入式脚本中作为this.host使用。

 class TestElement extends HTMLElement { #shadowRoot = null; constructor() { super(); this.#shadowRoot = this.attachShadow({ mode:'closed' }); this.#shadowRoot.innerHTML = this.template } get template() { return ` <style>.passed{color:green}</style> <div id="test"> TEST A </div> <slot></slot> <script> let a = this.querySelector('#test'); let b = this.host.firstElementChild; a && a.classList.add('passed'); b && (b.style.color = 'green'); <\/script> `; } get #scripts() { return this.#shadowRoot.querySelectorAll('script'); } #scopedEval = (script) => Function(script).bind(this.#shadowRoot)(); #processScripts() { this.#scripts.forEach( s => this.#scopedEval(s.innerHTML) ); } connectedCallback() { this.#processScripts(); } } customElements.define('test-element', TestElement);
 <test-element> <p> TEST B </p> </test-element>

不要将此技术与打开的 shadowRoot 一起使用,因为您将使您的组件容易受到脚本注入攻击。 浏览器阻止任意代码执行的原因是:保证您和您的用户的安全。 不要在启用此功能的情况下将不受信任的内容注入到您的 shadow dom 中,仅使用它来评估您自己的脚本或受信任的库,并且尽可能避免使用此技巧。 几乎总是有更好的方法来执行与影子 dom 交互的脚本,例如将所有逻辑范围限定到自定义元素定义中。

旁注: Element.setHTML是一种更安全的导入不受信任内容的方法,该方法即将作为HTML Sanitizer API的一部分出现。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM