19

Is it possible to see if a Shadow DOM element exists? I'm not too concerned with manipulating it, or even really targeting it per-say. I understand the reasoning of the encapsulation. But I'd like to be able to style other elements in the regular DOM, based on whether or not the Shadow DOM element is present.

Sort of like:

if ( $('#element-id #shadow-root').length ) {
    // true
}

Or if not for the shadow-root, at least a specific element within, like the id of a div. So if that div exists, then clearly that Shadow DOM element is on the page.

I know it wouldn't be that simple... From some research I've done, there are things like >>> and /deep/ but their support seems to be low/none/deprecated. Buy maybe there's another way, however inelegant it may be?

Chase
  • 2,051
  • 2
  • 21
  • 26
  • 2
    Are you wanting to detect if an element is hosting a Shadow DOM element? Or would that be helpful? – KevBot Feb 13 '16 at 02:25
  • 1
    From what I understand, given an element in the shadow dom, via a js var representing a shadow-dom node, you can determine if that node is a shadow dom element or not, and can traverse down its tree, or up its tree up to the non-shadow element it may be a child of ( http://stackoverflow.com/questions/27453617/how-can-i-tell-if-an-element-is-in-a-shadow-dom) but without having that node already, since the js queryselector api cannot target shadow dom on its own, I dont think you can do what you are asking – chiliNUT Feb 13 '16 at 02:26
  • @KevBot I think that is what op is asking, lol if not I would like to know, do you know how to do that? – chiliNUT Feb 13 '16 at 02:27
  • As an example, find a username text input in Chrome, where your login has been saved. (ie. when you access the login page, the username input is already filled in) and inspect it. This is done in the Shadow Dom. ` #shadow-dom
    Your username
    ` So I'd want a way of saying, does input > #shadow-dom exist? or if there's no way to target #shadow-dom, maybe say does input > div exist? Because there wouldn't be a div inside the input, unless the Shadow DOM put it there...
    – Chase Feb 13 '16 at 02:34

3 Answers3

13

If you want to check whether or not a specific element is hosting an open Shadow DOM element, you can do the following:

var el = document.querySelector('#some-element');
if (!!el.shadowRoot) {
    // Then it is hosting an OPEN Shadow DOM element
}

You can also get the Shadow DOM element, and then operate on it like a normal node:

var shadowEl = el.shadowRoot;
// And for example:
console.log(shadowEl.innerHTML);

Here is an example that works in the latest version of Chrome:

const div = document.querySelector('div');
const p = document.querySelector('p');

const shadowRoot = p.attachShadow({mode: 'open'})
shadowRoot.textContent = 'A Shadow DOM Paragraph. I overrode the content specified!';

console.log('Paragraph has Shadow DOM:', !!p.shadowRoot); // true
console.log('Div has Shadow DOM:', !!div.shadowRoot); // false
<div>A Normal Div</div>
<p>A Normal Paragraph</p>
KevBot
  • 17,900
  • 5
  • 50
  • 68
  • Thanks for the reply,this looks good! Your paragraph example definitely seems to work. Now that I'm exploring this more, I think your example of actually getting the element is what I really need. Any idea why this doesn't appear to work though? http://codepen.io/chasebank/pen/WrPEzX/ I did notice that when you create the Shadow Root, it shows as`#shadow-root (open)` whereas the browser's is `#shadow-root (user-agent)`. I'm hoping that's not an issue. Also FYI, CodePen has a console now. Button for it is in the bottom left. Sort of a newish feature... – Chase Feb 13 '16 at 06:28
  • @Chase, it looks like there is no shadow dom element in the input. Try this [codepen](http://codepen.io/anon/pen/jWdYNm?editors=1111). I added some checking to see if shadow dom element was present before trying to get the innerHTML of it. – KevBot Feb 13 '16 at 06:37
  • Interesting. When I inspect it with show user agent shodow DOM enabled, I see `#shadow-root (user-agent)
    ` within the input tag. Do you know how that differs from what your code is checking for?
    – Chase Feb 13 '16 at 06:43
  • 1
    Ah, I see what you're saying. Ok, generally, when people are working with the Shadow DOM, they are writing their own implementations of things, like web components. What you are seeing when you see the `user agent` shadow element, you are looking at the browsers build of that element. Think of an input element as a few elements put together that are encapsulated so that you can put it on the page with just ``. Under the scenes are some other fancy things going on too. But basically, the `user-agent` stuff is the browsers Shadow DOM which is left inaccessible for security concerns. – KevBot Feb 13 '16 at 07:05
  • Ah ha! I was afraid you were going to say that. Thanks explaining the difference. – Chase Feb 13 '16 at 21:24
  • 'close' mode can not be detected by shadowRoot as its value is always 'null' – Nonoroazoro Aug 27 '20 at 09:48
  • How would you do this in Java selenium? I am having a hard time – devin Oct 22 '22 at 18:46
7

You can access the shadowRoot of an element with the property shadowRoot, so you could traverse all the nodes and check if the property is null or not.

You can select all nodes in a document with document.getElementsByTagName('*').

So all in all, we would have something like this:

var allNodes = document.getElementsByTagName('*');
for (var i = 0; i < allNodes.length; i++) {
  if(allNodes[i].shadowRoot) {
    // Do some CSS styling
  }
}

With the additions of ES6, we could do something simpler like this:

document.getElementsByTagName('*')
    .filter(element => element.shadowRoot)
    .forEach(element => {
        // Do some CSS styling
    });
Marko Kajzer
  • 106
  • 1
  • 5
1

The other answers by KevBot and Marko Kajzer only work for ShadowRoot created with mode: 'open'. Here's a way to detect if an element has a ShadowRoot, even if the root is closed. Make sure this runs before other code (before any calls to attachShadow) or it will fail to catch any elements that already have a ShadowRoot by the time this code is set up:

const shadowHosts = new WeakSet()

const original = Element.prototype.attachShadow

Element.prototype.attachShadow = function attachShadow(...args) {
    const result = original.apply(this, args)

    shadowHosts.add(this)

    return result
}

export function hasShadow(el) {
    return shadowHosts.has(el)
}

then use hasShadow on any element

if (hasShadow(someElement)) {...}
trusktr
  • 44,284
  • 53
  • 191
  • 263