How to get element in user-agent shadow root with JavaScript?
Asked Answered
B

4

30

I need get elements from Shadow DOM and change it. How i can do it?

<div>
     <input type="range" min="100 $" max="3000 $">
</div>
Badinage answered 1/8, 2016 at 14:45 Comment(5)
I try .getElementByIdBadinage
I think element.shadowRoot represents the youngest shadow root that is hosted on the element !Emmanuelemmeline
I need to get an element that is inside the Shadow DOMBadinage
Please provide us with what you have tried so far. A single line of code is not much to go on. Questions asking us to write code for you do not typically belong here.Monadelphous
Inside input I have element with id="track". And I need get this element. How I can find it. I try .getElementById(track)Badinage
E
11

Here is an example:

var container = document.querySelector('#example');
//Create shadow root !
var root = container.createShadowRoot();
root.innerHTML = '<div>Root<div class="test">Element in shadow</div></div>';

//Access the element inside the shadow !
//"container.shadowRoot" represents the youngest shadow root that is hosted on the element !
console.log(container.shadowRoot.querySelector(".test").innerHTML);

Demo:

var container = document.querySelector('#example');
//Create shadow root !
var root = container.createShadowRoot();
root.innerHTML = '<div>Root<div class="test">Element in shadow</div></div>';

//Access the element inside the shadow !
console.log(container.shadowRoot.querySelector(".test").innerHTML);
<div id="example">Element</div>

I hope this will help you.

Emmanuelemmeline answered 1/8, 2016 at 15:0 Comment(7)
Thank you! But element <input type="range" min="100 $" max="3000 $"> has ShadowDOM with <div id="track">, and I need get this element.Badinage
The shadow root created on DIV ? because it can be created on the Input !Emmanuelemmeline
This doesn't seem to work in recent Firefox: "message": "TypeError: container.createShadowRoot is not a function"Linette
Downvoted as this described creating a shadow root with the Javascript API, it does not describe access the browser-implemented shadow root. The response indicating you cannot access the shadow root generated by the browser (e.g. #shadow-root in Chrome) is more correct. However, thank you for explaining creating one as well.Concertante
This does not answer how to access user-agent element's shadow-root (already created by browser). It shows how to access custom element's shadow-root (created by developer).Goncourt
createShadowRoot is non-standard and deprecated. Neither Firefox nor Safari support it. Don't use it. Instead use element.attachShadow({mode: 'open'});.Prosecutor
This is not answering the OP's question since that was specifically about the input control and the browser-native shadow content that shows up in dev tools.Carrissa
F
35

You cannot access a Shadow DOM created by the browser to display a control, that is called a #shadow-root (user-agent) in the Dev Tools. <input> is one example.

You can only access open custom Shadow DOM (the ones that you create yourself), with the { mode: 'open' } option.

element.attachShadow( { mode: 'open' } )

Update

It's true for most UX standard HTML elements: <input>, <video>, <textarea>, <select>, <audio>, etc.

3rd party edit 2022

The following might help to illustrate the question. Give there is only 1 <input type=range> in the html document this code shows if its children can be accessed.

 // returns 1 as expected since only one input element is in the document
document.querySelectorAll("input").length; 

// get a reference to <input type=range>
var rangeInput = document.querySelector("input");     

// Is it a shadowRoot?
// if null then either 
//   - it is not a shadowRoot OR 
//   - its elements can not be accessed (mode == closed)
console.log(rangeInput.shadowRoot);  // returns null

The code above shows that the internals of an <input type=range> can not be accessed.

input_type_range_shadowRoot_access

Farthest answered 3/8, 2016 at 6:43 Comment(2)
I know that this post was 2 years ago, but do you have the source of this? Or are there any updates about this? I am trying to access the shadow dom that created by the iOS safari. Thanks.Foolish
Source of what? user-agent Shadow DOM are browser vendors native implementation so they are not documented and will never be accessible. Only open Shadow DOM are, as per the specs.Farthest
C
23

To answer a generalized version of the OP's question:

Query shadow elements FROM ANYWHERE on the page?

It feels like the shadow root API is still lacking. It seems to make querySelectorAll useless, in that querySelectorAll will not actually get all matching elements anymore, since it ignores all descendants in shadowRoots. Maybe there is an API that fixes that, but since I have not found any, I wrote my own:

This function recursively iterates all shadowRoots and gets you all matching elements on the page, not just those of a single shadowRoot.

/**
 * Finds all elements in the entire page matching `selector`, even if they are in shadowRoots.
 * Just like `querySelectorAll`, but automatically expand on all child `shadowRoot` elements.
 * @see https://mcmap.net/q/1622029/-how-to-get-element-in-user-agent-shadow-root-with-javascript
 */
function querySelectorAllShadows(selector, el = document.body) {
  // recurse on childShadows
  const childShadows = Array.from(el.querySelectorAll('*')).
    map(el => el.shadowRoot).filter(Boolean);

  // console.log('[querySelectorAllShadows]', selector, el, `(${childShadows.length} shadowRoots)`);

  const childResults = childShadows.map(child => querySelectorAllShadows(selector, child));
  
  // fuse all results into singular, flat array
  const result = Array.from(el.querySelectorAll(selector));
  return result.concat(childResults).flat();
}
// examples:
querySelectorAllShadows('td'); // all `td`s in body
querySelectorAllShadows('.btn') // all `.btn`s in body
querySelectorAllShadows('a', document.querySelector('#right-nav')); // all `a`s in right menu
Curkell answered 31/3, 2022 at 12:32 Comment(3)
That's not really an omission, its by design, and its the main mechanism by which shadowDOM 'encapsulates' styles and namespace. querySelect/+all is a method of the capital D Document interface of which lowercase d document is the most famous member, but Shadowroots are also documents (or document fragments, really). Documents within documents within documents... its documents all the way down manMoira
Shadow roots have many purposes. Being able to search/interact with elements of the entire Page, rather than an individual Document, is still necessary at times.Curkell
For sure, and in those instances I do end up using a little more verbose syntax as a ‘workaround’ (I.e, one must query the shadow root host element first before querying it’s shadow descendants). But at least in my experience the “encapsulation by default” is convenient more often than it’s not, and it’s why I use shadowroots often.Moira
E
11

Here is an example:

var container = document.querySelector('#example');
//Create shadow root !
var root = container.createShadowRoot();
root.innerHTML = '<div>Root<div class="test">Element in shadow</div></div>';

//Access the element inside the shadow !
//"container.shadowRoot" represents the youngest shadow root that is hosted on the element !
console.log(container.shadowRoot.querySelector(".test").innerHTML);

Demo:

var container = document.querySelector('#example');
//Create shadow root !
var root = container.createShadowRoot();
root.innerHTML = '<div>Root<div class="test">Element in shadow</div></div>';

//Access the element inside the shadow !
console.log(container.shadowRoot.querySelector(".test").innerHTML);
<div id="example">Element</div>

I hope this will help you.

Emmanuelemmeline answered 1/8, 2016 at 15:0 Comment(7)
Thank you! But element <input type="range" min="100 $" max="3000 $"> has ShadowDOM with <div id="track">, and I need get this element.Badinage
The shadow root created on DIV ? because it can be created on the Input !Emmanuelemmeline
This doesn't seem to work in recent Firefox: "message": "TypeError: container.createShadowRoot is not a function"Linette
Downvoted as this described creating a shadow root with the Javascript API, it does not describe access the browser-implemented shadow root. The response indicating you cannot access the shadow root generated by the browser (e.g. #shadow-root in Chrome) is more correct. However, thank you for explaining creating one as well.Concertante
This does not answer how to access user-agent element's shadow-root (already created by browser). It shows how to access custom element's shadow-root (created by developer).Goncourt
createShadowRoot is non-standard and deprecated. Neither Firefox nor Safari support it. Don't use it. Instead use element.attachShadow({mode: 'open'});.Prosecutor
This is not answering the OP's question since that was specifically about the input control and the browser-native shadow content that shows up in dev tools.Carrissa
S
1

If your control is inside Shadow-root (user-agent). You can perform enter text, get value and click by using the Parent of Shadow-root (user-agent).

Example:

(Selenium/C# code) - same should work for others as well

//You can use any identifier like id, XPath, tagname, etc
Var element = Driver.FindElement(By.Tagname("input"));
//Text will return null, so use below code
var text = element.GetAttribute("value");
or
element.Click();
or
element.Sendkeys("Entered text");
Streit answered 7/3 at 9:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.