How to remove a shadow root from an HTML element adorned with a Shadow DOM from a template?

wjohnson picture wjohnson · Nov 20, 2013 · Viewed 23.8k times · Source

I'm exploring imports, templates, shadow DOM and custom elements in Chrome Canary (33.0.1712.3). In a grid layout I have a particular content element (region of the display) that will display different web components or cloned light DOM fragments imported from files.

However, I'm unable to redisplay ordinary HTML DOM once a shadow DOM has been added because I don't know how to remove the shadow root. Once created, the shadow root remains and interferes with the rendering of ordinary DOM. (I've looked at various W3C specs such as intro to web components, shadow DOM, templates, Bidelman's articles on HTML5 Rocks, etc.) I've isolated the problem in a simple example below:

Click "show plain old div"; click "show shadowed template"; click "show plain old div". Inspect in devtools after each click. After the third click, there is no output below the buttons and in devtools I am seeing:

<div id="content">
  #document-fragment
  <div id="plaindiv">Plain old div</div>
</div>

What do I need to add to removeShadow() to remove the shadow root and fully reset the content element to its initial state?

removing_shadows.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>

  <template id="shadowedTemplateComponent">
    <style>
      div { background: lightgray; }
      #t { color: red; }
    </style>

    <div id="t">template</div>

    <script>console.log("Activated the shadowed template component.");</script>
  </template>

  <template id="plainDiv">
    <div id="plaindiv">Plain old div</div>
  </template>
</head>

<body>
<div>
  <input type="button" value="show plain old div" onclick="showPlainOldDiv()"/>
  <input type="button" value="show shadowed template" onclick="showShadowTemplate()"/>
  <div id="content"></div>
</div>

<script>
  function removeChildren(elt) {
    console.log('removing children: %s', elt);
    while (elt.firstChild) {
      elt.removeChild(elt.firstChild);
    }
  }
  function removeShadow(elt) {
    if (elt.shadowRoot) {
      console.log('removing shadow: %s', elt);
      removeChildren(elt.shadowRoot); // Leaves the shadow root property.
      // elt.shadowRoot = null; doesn't work
      // delete elt.shadowRoot; doesn't work
      // What goes here to delete the shadow root (#document-fragment in devtools)?
    }
  }

  function showPlainOldDiv() {
    console.log('adding a plain old div');
    var host = document.querySelector('#content');
    removeChildren(host);
    removeShadow(host);
    var template = document.querySelector('#plainDiv');
    host.appendChild(template.content.cloneNode(true));
  }

  function showShadowTemplate() {
    console.log('adding shadowed template component');
    var host = document.querySelector('#content');
    removeChildren(host);
    removeShadow(host);
    var template = document.querySelector('#shadowedTemplateComponent');
    var root = host.shadowRoot || host.webkitCreateShadowRoot();
    root.appendChild(template.content.cloneNode(true));
  }
</script>
</body>
</html>

Answer

GullerYA picture GullerYA · Apr 14, 2017

The spec of Shadow DOM moved from v0 to v1.

One of the changes is that in v1 there is no way to create shadow root on itself and the host element may contain only one shadow root.

So it seems like the answer of replacing the shadow root with a new blank shadow root is not valid anymore.

Solution paths:

  • if the host element self (div in your example) has no special value beside holding that Shadow DOM, one can just replace the host element as a whole
  • if one still likes to preserve the host, clearing the Shadow DOM with something like e.shadowRoot.innerHTML = '' might be sufficient