WebKit Bugzilla
New
Browse
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
RESOLVED INVALID
233380
CSS :scope pseudo selector doesn't work in shadowRoot.querySelectorAll
https://bugs.webkit.org/show_bug.cgi?id=233380
Summary
CSS :scope pseudo selector doesn't work in shadowRoot.querySelectorAll
Andrei Anischevici
Reported
2021-11-19 13:18:22 PST
CSS :scope pseudo selector doesn't work in querySelectorAll() when it is called on the shadowRoot of a custom element. Due to this bug, it's impossible to select direct descendants of the shadowRoot via CSS selector. Sample code to reproduce the issue: ------------------------------- const content = ` <div> <input type="radio" id="btn1" name="btn1" value="Button 1"> <label for="btn1">Button 1</label><br> <div> <input type="radio" id="btn2" name="btn2" value="Button 2"> <label for="btn2">Button 2</label><br> </div> </div> `; customElements.define( "my-button", class extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: "open" }); shadowRoot.innerHTML = content; } } ); const container = document.createElement("DIV"); container.setAttribute("id", "container"); container.innerHTML = ` <my-button>FirstButton</my-button> <my-button>SecondButton</my-button> `; document.body.appendChild(container); document.body.querySelectorAll(':scope > DIV'); // works document.getElementById('container').children[0].shadowRoot.querySelectorAll(':scope > DIV') // fails, [] is returned instead of [div] ------------------------------- See also related Firefox issue -
https://bugzilla.mozilla.org/show_bug.cgi?id=1689893
Note: this is working properly in Chrome.
Attachments
Add attachment
proposed patch, testcase, etc.
Ryosuke Niwa
Comment 1
2021-11-19 20:46:44 PST
As mentioned in Mozilla bug, this seems working as intended. If this behavior is desirable, perhaps you need to open a spec issue for CSS WG.
Andrei Anischevici
Comment 2
2021-11-20 09:15:22 PST
(In reply to Ryosuke Niwa from
comment #1
)
> As mentioned in Mozilla bug, this seems working as intended. If this > behavior is desirable, perhaps you need to open a spec issue for CSS WG.
As I also mentioned in the Mozilla bug, this is not working as intended and the comment with which that bug was initially resolved does not apply, as we're not crossing the shadow boundary with a single selector, but instead doing the selection from the shadow root node, which is expected to be working, similarly to how it's working for the document root node. That ticket has since been reopened and being looked into. As I also noted, this is working properly in Chrome and it is indeed a bug in Mozilla and WebKit, as there's no way of selecting direct descendants of the shadow root, other than parsing the selector and manually matching each shadow root child.
Ryosuke Niwa
Comment 3
2021-11-20 09:26:17 PST
(In reply to Andrei Anischevici from
comment #2
)
> (In reply to Ryosuke Niwa from
comment #1
) > > As mentioned in Mozilla bug, this seems working as intended. If this > > behavior is desirable, perhaps you need to open a spec issue for CSS WG. > > As I also mentioned in the Mozilla bug, this is not working as intended and > the comment with which that bug was initially resolved does not apply, as > we're not crossing the shadow boundary with a single selector, but instead > doing the selection from the shadow root node, which is expected to be > working, similarly to how it's working for the document root node. That > ticket has since been reopened and being looked into.
Well, :scope is spec'ed to behave like this. node.querySelector is defined here:
https://dom.spec.whatwg.org/#dom-parentnode-queryselector
which invokes the algorithm "to scope-match a selectors string" with "this", which is shadow root in this case:
https://dom.spec.whatwg.org/#scope-match-a-selectors-string
This algorithm in turn does this: "Return the result of match a selector against a tree with s (the parsed selector) and node’s root (shadow root's root is itself) using scoping root node (shadow root)" Now take a look at the definition of a scoping root:
https://drafts.csswg.org/selectors-4/#scoping-root
"The root of the scoping subtree is called the scoping root, and may be either a true element (the scoping element) or a virtual one" And now the definition of ":scope":
https://drafts.csswg.org/selectors-4/#the-scope-pseudo
>The :scope pseudo-class represents any element that is a :scope element. If the :scope elements are not explicitly specified, but the selector is scoped and the scoping root is an element, then :scope represents the scoping root; otherwise, it represents the root of the document (equivalent to :root). Specifications intending for this pseudo-class to match specific elements rather than the document’s root element must define either a scoping root (if using scoped selectors) or an explicit set of :scope elements.
Here, the spec says that if the scoping root is not an element, we must use the root of the document, which is a document. Since shadow root is not a document, we would not match. Now, it's possible that this is an editorial error in CSS selectors 4 and we want to say that it matches the root node (i.e. shadow root in the case). But as far as I read the current set of specifications, the current behavior of WebKit and Gecko are correct and Chrome's behavior is the one that is not spec compliant.
Andrei Anischevici
Comment 4
2021-11-20 11:31:58 PST
(In reply to Ryosuke Niwa from
comment #3
) Thank you for the prompt response, I really appreciate the detailed reply!
> This algorithm in turn does this: > "Return the result of match a selector against a tree with s (the parsed > selector) and node’s root (shadow root's root is itself) using scoping root > node (shadow root)"
> >The :scope pseudo-class represents any element that is a :scope element. If the :scope elements are not explicitly specified, but the selector is scoped and the scoping root is an element, then :scope represents the scoping root; otherwise, it represents the root of the document (equivalent to :root). Specifications intending for this pseudo-class to match specific elements rather than the document’s root element must define either a scoping root (if using scoped selectors) or an explicit set of :scope elements. > > Here, the spec says that if the scoping root is not an element, we must use > the root of the document, which is a document. Since shadow root is not a > document, we would not match.
Please allow me to politely disagree and suggest that this a misinterpretation of the spec, specifically this part:
> "but the selector is scoped and the scoping root is an element, then :scope represents the scoping root"
The shadow root IS an element, although it's a virtual element (since it's a DocumentFragment), it's still an element, according to the scoping root definition here
https://drafts.csswg.org/selectors-4/#scoping-root
"The root of the scoping subtree is called the scoping root, and may be either a true element (the scoping element) or a virtual one (such as a DocumentFragment)" "virtual one" == "virtual element" I totally agree that this "and the scoping root is an element" part in the ":scope" spec is somewhat confusing and should've been excluded altogether, since from the "scoping root" definition it's clear that it can't be anything else than an element.. Furthermore, if we look away from the specs for a bit and take a look at it from a purely logical standpoint: - Given a node tree, in this case a DocumentFragment (which supports querySelectorAll() in its API
https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment/querySelectorAll
) I need to be able to select the direct descendants of the tree root, i.e. the top-level elements using some selector. Currently ':scope > ...' is the only selector that can accomplish this and it's a real pain to work around this if it's not supported.. I see that there's a new ::shadow pseudo-element proposed that would allow an alternate solution to this issue
https://www.w3.org/TR/css-scoping-1/#shadow-pseudoelement
however, none of the browsers seem to implement it at this point..
Andrei Anischevici
Comment 5
2021-11-20 12:03:27 PST
(In reply to Andrei Anischevici from
comment #4
)
> I see that there's a new ::shadow pseudo-element proposed that would allow > an alternate solution to this issue >
https://www.w3.org/TR/css-scoping-1/#shadow-pseudoelement
> however, none of the browsers seem to implement it at this point..
Actually this no longer applies, since that suggestion is from the 2014 CSS Scoping Module Level 1 spec, and in the latest 2021 spec the ::shadow pseudo-element can no longer be found
https://drafts.csswg.org/css-scoping/
https://developers.google.com/web/updates/2017/10/remove-shadow-piercing
So, the only viable solution to the issue is to fix the ":scope" pseudo-class so it works on shadow roots.
Andrei Anischevici
Comment 6
2021-11-20 12:13:13 PST
Additionally, in the "Matching Selectors Against Shadow Trees" section of the Scoping spec at
https://drafts.csswg.org/css-scoping/#selectors-data-model
there's this note: "When a selector is matched against a tree, its tree context is the root of the root elements passed to the algorithm. If the tree context is a shadow root, that selector is being matched in the context of a shadow tree. For example, any selector in a stylesheet embedded in or linked from an element in a shadow tree is in the context of a shadow tree. So is the argument to querySelector() when called from a shadow root." Since the selector containing ":scope" is being matched in the context of the shadow tree, as specified here, it is expected that ":scope" would resolve to the shadow root, and not to the document root.
Emilio Cobos Álvarez (:emilio)
Comment 7
2021-11-20 12:21:29 PST
I'll go through this and the relevant Firefox bug next Monday, but in any case a document fragment never matches any selector. In the context of a shadow tree only :host can match the shadow host, and it is a featureless element so it should not match scope. It could match :host(:scope) though, maybe... I haven't checked whether that works in any browser.
Emilio Cobos Álvarez (:emilio)
Comment 8
2021-11-22 03:01:19 PST
I think this is invalid. You can use :host > div instead, which ought to work per spec. I filed
https://bugs.chromium.org/p/chromium/issues/detail?id=1272434
for Chromium.
Andrei Anischevici
Comment 9
2021-11-22 04:51:00 PST
(In reply to Emilio Cobos Álvarez (:emilio) from
comment #8
)
> I think this is invalid. You can use :host > div instead, which ought to > work per spec. I filed >
https://bugs.chromium.org/p/chromium/issues/detail?id=1272434
for Chromium.
Thank you for looking into this, Emilio! I have just tested the ':host > div' solution that you suggested, and it does indeed accomplish the selection of direct descendants across all browsers (excluding IE, which is expected), so we have a path forward.. If :scope is indeed not supposed to be working for shadow roots, I'd suggest improving the CSS Selectors Level 4 spec, so that this is mentioned explicitly in :scope and there's no ambiguity: "If the :scope elements are not explicitly specified, but the selector is scoped and the scoping root is an element, then :scope represents the scoping root;" change to => "If the :scope elements are not explicitly specified, but the selector is scoped and the scoping root is a true (not virtual) element, then :scope represents the scoping root;" would you agree?
Emilio Cobos Álvarez (:emilio)
Comment 10
2021-11-22 05:26:09 PST
Yeah, that seems reasonable. Maybe file it in
https://github.com/w3c/csswg-drafts/issues/new
?
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug