I have a project where I'm using the shadow DOM natively (not through a polyfill). I'd like to detect if a given element
is contained within a shadow DOM or a light DOM.
I've looked through all of the properties on the elements, but there don't seem to be any which vary based on the type of DOM an element is in.
How can I determine if an element is part of a shadow DOM or a light DOM?
Here is an example of what is considered "shadow DOM" and "light DOM" for the purpose of this question.
(light root) • Document (light) • HTML (light) | • BODY (light) | • DIV (shadow root) | • ShadowRoot (shadow) | • DIV (shadow) | • IFRAME (light root) | • Document (light) | • HTML (light) | | • BODY (light) | | • DIV (shadow root) | | • ShadowRoot (shadow) | | • DIV (none) | • [Unattached DIV of second Document] (none) • [Unattached DIV of first Document]
<!doctype html>
<title>
isInShadow() test document - can not run in Stack Exchange's sandbox
</title>
<iframe src="about:blank"></iframe>
<script>
function isInShadow(element) {
// TODO
}
function test() {
// (light root) • Document
// (light) • HTML
var html = document.documentElement;
console.assert(isInShadow(html) === false);
// (light) | • BODY
var body = document.body;
console.assert(isInShadow(body) === false);
// (light) | • DIV
var div = document.createElement('div');
body.appendChild(div);
console.assert(isInShadow(div) === false);
// (shadow root) | • ShadowRoot
var divShadow = div.createShadowRoot();
var shadowDiv = document.createElement('div');
divShadow.appendChild(shadowDiv);
// (shadow) | • DIV
console.assert(isInShadow(shadowDiv) === true);
// (shadow) | • IFRAME
var iframe = document.querySelector('iframe');
shadowDiv.appendChild(iframe);
console.assert(isInShadow(iframe) === true);
// (light root) | • Document
var iframeDocument = iframe.contentWindow.document;
// (light) | • HTML
var iframeHtml = iframeDocument.documentElement;
console.assert(isInShadow(iframeHtml) === false);
// (light) | | • BODY
var iframeBody = iframeDocument.body;
//
console.assert(isInShadow(iframeHtml) === false);
// (light) | | • DIV
var iframeDiv = iframeDocument.createElement('div');
iframeBody.appendChild(iframeDiv);
console.assert(isInShadow(iframeDiv) === false);
// (shadow root) | | • ShadowRoot
var iframeDivShadow = iframeDiv.createShadowRoot();
// (shadow) | | • DIV
var iframeDivShadowDiv = iframeDocument.createElement('div');
iframeDivShadow.appendChild(iframeDivShadowDiv);
console.assert(isInShadow(iframeDivShadowDiv) === true);
// (none) | • [Unattached DIV of second Document]
var iframeUnattached = iframeDocument.createElement('div');
console.assert(Boolean(isInShadow(iframeUnattached)) === false);
// (none) • [Unattached DIV of first Document]
var rootUnattached = document.createElement('div');
console.assert(Boolean(isInShadow(rootUnattached)) === false);
}
onload = function main() {
console.group('Testing');
try {
test();
console.log('Testing complete.');
} finally {
console.groupEnd();
}
}
</script>
You can check if an element has a shadow parent like this:
This uses
instanceof
over.toString()
.We can use
Element
's.matches()
method to determine if an element is attached to a shadow DOM.If and only if the element is in a shadow DOM, we will be able to match it by using the selector
:host
to identify elements that have a Shadow DOM,::shadow
to look in those shadow DOMs, and*
and to match any descendant.Lets understand Light Dom:
The Light DOM is the user supplied DOM of an element that hosts a shadow root. For more info read at polymer-project.
https://www.polymer-project.org/platform/shadow-dom.html#shadow-dom-subtrees
This means: Light DOM is always relative to the next ancestor which hosts a shadow root.
An Element can be a part of the light dom of a custom element while it can be a part of the shadow root of another custom element at same time.
Example:
According to your question:
If you want to know if an element is in a shadow root, you just need to grab the element out of the document.
The only Light DOM which is not preceeded by a shadow Root is part of the document, because Light DOM is relative as shown above.
It doesnt work backwards: if its part of the document its not in a Light DOM at all. You need to check if one of the ancestors is hosting a Shadow Root like suggested from Leo.
You can use this approach with other elements to. Just replace the "document" with e.g. "my-custom-element" and test if
div#LDofMCE
is in Light DOM relative to "my-custom-element".Because of the lack of information about why you need this information i cant get closer...
EDIT:
It doesnt work backwards should be understand as follows:
Is this element in a Shadow Root?: document.contains() or the isInShadow(node) method from Leo deliver the answer.
"backwards" Question: Is this element in a Light DOM (In case you start searching relative to document)?: domcument.contains() does not deliver the answer because to be in a Light Dom - one of the elements ancestors needs to be a shadow host.
Come to the Point
If you call a ShadowRoot's
toString()
method, it will return"[object ShadowRoot]"
. According to this fact, here's my approach:EDIT
Jeremy Banks suggests an approach in another style of looping. This approach is a little different from mine: it also checks the passed node itself, which I didn't do.