[whatwg/dom] Option to allow `querySelector` to query shadow roots (Issue #1422)

LeaVerou created an issue (whatwg/dom#1422)

### 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 https://github.com/w3c/csswg-drafts/issues/11007 you need to literally walk every single element.

Here is a complete userland implementation:

```js
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:
1. Query all open shadow roots, recursively
2. 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: https://github.com/whatwg/dom/issues/1321
- This is about finding open shadow roots or their hosts: https://github.com/whatwg/dom/issues/665
- This is about adding similar functionality to `TreeWalker`: https://github.com/whatwg/dom/issues/1189
- These are about adding a CSS selector to find shadow hosts:
  - https://github.com/w3c/csswg-drafts/issues/2908
  - https://github.com/w3c/csswg-drafts/issues/11007

-- 
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/dom/issues/1422
You are receiving this because you are subscribed to this thread.

Message ID: <whatwg/dom/issues/1422@github.com>

Received on Friday, 14 November 2025 05:38:36 UTC