Repository navigation
Option to allow querySelector to query shadow roots #1422
Description
What problem are you trying to solve?
This is a counterpart of #1287. #1287 is about monitoring future changes, whereas this is about observing the current state. Both are necessary for these use cases (polyfilling future HTML syntax), since as script cannot know at what point it is dropped in a page, and what already exists.
What solutions exist today?
Current workarounds are even worse than those described in #1287, since monkey-patching attachShadow is no help here, so the only solution is to walk the DOM and check its shadowRoot. And without something like w3c/csswg-drafts#11007 you need to literally walk every single element.
Here is a complete userland implementation:
function querySelectorAll (selector, root = document, options = {}) {
let shadowRoots = options.shadowRoots?.roots ?? (Array.isArray(options.shadowRoots) ? options.shadowRoots : []);
shadowRoots = [...shadowRoots];
let openRoots = options.shadowRoots?.open || options.shadowRoots === true;
if (openRoots) {
shadowRoots.push(...getShadowRootsUnder(root));
}
let ret = [...root.querySelectorAll(selector)];
for (const shadowRoot of shadowRoots) {
ret.push(...shadowRoot.querySelectorAll(selector));
}
return ret;
}
function getShadowRootsUnder (root = document, recursive = true) {
let ret = root.shadowRoot ? [root.shadowRoot] : [];
ret.push(...[...root.querySelectorAll("*")]
.flatMap(element => {
if (!element.shadowRoot) return [];
let ret = [element.shadowRoot];
if (recursive) {
ret.push(...getShadowRootsUnder(element.shadowRoot));
}
return ret;
}));
return ret;
}As you can see, it is nontrivial, not to mention very inefficient.
How would you solve it?
Ideally, we want to be able to:
- Query all open shadow roots, recursively
- Pass specific shadow roots by reference (which allows querying certain closed roots)
We could add an options bag after the first argument, with a shadowRoots option with type boolean | Iterable<ShadowRoot> | { open: boolean, roots: ShadowRoot[] }
If making it that easy to query open shadow roots is not desirable, an MVP could be just an Iterable<ShadowRoot> which at least makes it easier and paves the way for later expansion. Though I don't see the point: if authors need to query all open roots, they just have to implement it in userland with the same or worse performance. Even if currently UAs don't store a data structure for quickly finding all open roots, once there is API surface that allows authors to do this, they could lazily instantiate such a structure and use it on all subsequent calls, updating it only when a new shadow root is attached, whereas authors need to redo the entire walking every time.
We should probably decide on what the API shape should look like for all DOM methods we want to enable such functionality on (e.g. #1189 ) rather than re-inventing the wheel on every single one.
Anything else?
The following issues are also relevant:
- This has been proposed before, but specifically for extensions: Extension only API to query across shadow trees #1321
- This is about finding open shadow roots or their hosts: A faster way to find open shadow roots (or shadow hosts) #665
- This is about adding similar functionality to
TreeWalker: Should TreeWalker be expanded to support shadow roots in support of the selection API? #1189 - These are about adding a CSS selector to find shadow hosts:
Activity
jonathanKingston commentedon Jan 12, 2026
This seems really well thought out and certainly solves an annoying problem.
The one caveat here that the snippet doesn't solve is selectors that cross the boundaries; I think that's very intentional. My guess is the CSS selectors would assist with this optionality in future:
Or:
In short: I think this is a non issue to solve as part of this proposal.
jonathanKingston commentedon Jan 27, 2026
@jakearchibald you were discussing this lately with $$$ in the Firefox console, I'd be interested in prototyping it if you think there's support in landing it.
jakearchibald commentedon Jan 27, 2026
I don't know if there's support for landing it, but in terms of API design it might be worth looking at things like https://developer.mozilla.org/en-US/docs/Web/API/Selection/getComposedRanges which take an array of shadow roots. There isn't a way to include all open shadow roots there - I wonder if that was a deliberate choice and if the reasons are documented.
jonathanKingston commentedon Jan 30, 2026
Thanks for the reference, I wasn't aware of that! I think the use-case is pretty different in regards to passing shadow roots, it seems that it returns a range of any of the shadows entirely when contained within the shadow root.
https://www.mail-archive.com/blink-dev@chromium.org/msg13171.html discusses that:
So I think the read is that because this provides ranges, it needs to propagate up to known open ones. I don't think this is a performance concern.
jakearchibald commentedon Jan 30, 2026
The utility of the API is different, but the pattern of "these are the shadow roots you're allowed to see inside" is the same.
This pattern is also used in
el.getHTML(), and resolved to add togetElementsFromPoint.Basically, if you're going to propose a different way to give an API visibility into shadow roots, you'll need to justify why it's different to established patterns.
annevk commentedon Jan 30, 2026
Essentially the constrain we try to place upon such APIs is that they work equally well for closed and open shadow roots, without regressing on encapsulation.
jonathanKingston commentedon Jan 30, 2026
Well I don't think in this case this can work well for closed shadow routes (but that's the status quo of
querySelectoranyway). So the next best thing is allow the design to be specific that it is open shadow routes only, right?What's the best way to try and drum up implementer interest here? (I did read the docs a bit but couldn't find anything quickly, sorry for the noise)
annevk commentedon Jan 30, 2026
I think the same shape as the other APIs would make this work for closed shadow trees? I doubt you’ll get sufficient implementer interest if you don’t solve for that.
jakearchibald commentedon Jan 30, 2026
@jonathanKingston
Why not? Closed shadow roots can contain elements too.
Imagine we had an API to get all open shadow roots, then you could have:
jonathanKingston commentedon Jan 30, 2026
Ah, sorry, yeah, I understand. I thought you were suggesting it should query into shadow dom if I didn't have a reference.
That makes sense to me, and I think what Lea has suggested, even though the user land script would not work in that fashion, there's nothing to suggest that the real API wouldn't be able to.