最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How to execute javascript in shadow domweb components? - Stack Overflow

programmeradmin4浏览0评论

I'm trying to create a custom element with most of the javascript encapsulated/referenced in the template/html itself. How can I make that javascript from the template/element to be executed in the shadow dom? Below is an example to better understand the issue. How can I make the script from template.innerHTML (<script>alert("hello"); console.log("hello from tpl");</script>) to execute?

Currently I get no alert or logs into the console. I'm testing this with 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 />

I'm trying to create a custom element with most of the javascript encapsulated/referenced in the template/html itself. How can I make that javascript from the template/element to be executed in the shadow dom? Below is an example to better understand the issue. How can I make the script from template.innerHTML (<script>alert("hello"); console.log("hello from tpl");</script>) to execute?

Currently I get no alert or logs into the console. I'm testing this with 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 />

Share Improve this question edited Mar 25, 2020 at 11:55 sergeykish 1811 silver badge10 bronze badges asked Dec 10, 2018 at 2:12 mikemike 6391 gold badge9 silver badges25 bronze badges 2
  • Actually your example works. – Supersharp Commented Dec 10, 2018 at 16:20
  • 2 Possible duplicate of Script inside shadow dom not working – Supersharp Commented Dec 10, 2018 at 16:43
Add a ment  | 

2 Answers 2

Reset to default 3

The reason this fails is because importNode does not evaluate scripts that were imported from another document, which is essentially what's happening when you use innerHTML to set the template content. The string you provide is parsed into a DocumentFragment which is considered a separate document. If the template element was selected from the main document, the scripts would be evaluated as expected :

<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>

One way to force your scripts to evaluate would be to import them using a contextual fragment :

<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>

But, this breaks encapsulation as the scripts inside your shadowRoot will actually be evaluated in the global scope with no access to your closed shadow dom. The method that I came up with to deal with this issue is to loop over each script in the shadow dom and evaluate it with the shadowRoot as it's scope. You can't just pass the host object itself as the scope because you'll lose access to the closed shadowRoot. Instead, you can access the ShadowRoot.host property which would be available as this.host inside the embedded scripts.

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>

Do not use this technique with an open shadowRoot as you will leave your ponent vulnerable to script injection attacks. The browser prevents arbitrary code execution for a reason: to keep you and your users safe. Do not inject untrusted content into your shadow dom with this enabled, only use this to evaluate your own scripts or trusted libraries, and ideally avoid this trick if at all possible. There are almost always better ways to execute scripts that interact with your shadow dom, like scoping all your logic into your custom element definition.

Side note: Element.setHTML is a much safer method for importing untrusted content which is ing soon as part of the HTML Sanitizer API.

A few points:

  1. Browsers no longer allow you to add script via innerHTML
  2. There is no sand-boxing of script within the DOM a web ponent like there is in an iFrame.

You can create script blocks using var el = document.createElement('script'); and then adding them as child elements.

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>

发布评论

评论列表(0)

  1. 暂无评论