<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE bugzilla SYSTEM "https://bugs.webkit.org/page.cgi?id=bugzilla.dtd">

<bugzilla version="5.0.4.1"
          urlbase="https://bugs.webkit.org/"
          
          maintainer="admin@webkit.org"
>

    <bug>
          <bug_id>233380</bug_id>
          
          <creation_ts>2021-11-19 13:18:22 -0800</creation_ts>
          <short_desc>CSS :scope pseudo selector doesn&apos;t work in shadowRoot.querySelectorAll</short_desc>
          <delta_ts>2021-11-22 05:26:09 -0800</delta_ts>
          <reporter_accessible>1</reporter_accessible>
          <cclist_accessible>1</cclist_accessible>
          <classification_id>1</classification_id>
          <classification>Unclassified</classification>
          <product>WebKit</product>
          <component>CSS</component>
          <version>WebKit Nightly Build</version>
          <rep_platform>All</rep_platform>
          <op_sys>All</op_sys>
          <bug_status>RESOLVED</bug_status>
          <resolution>INVALID</resolution>
          
          
          <bug_file_loc></bug_file_loc>
          <status_whiteboard></status_whiteboard>
          <keywords></keywords>
          <priority>P2</priority>
          <bug_severity>Normal</bug_severity>
          <target_milestone>---</target_milestone>
          
          <blocked>148695</blocked>
          <everconfirmed>1</everconfirmed>
          <reporter name="Andrei Anischevici">andrei002</reporter>
          <assigned_to name="Nobody">webkit-unassigned</assigned_to>
          <cc>emilio</cc>
    
    <cc>koivisto</cc>
    
    <cc>rniwa</cc>
    
    <cc>simon.fraser</cc>
          

      

      

      

          <comment_sort_order>oldest_to_newest</comment_sort_order>  
          <long_desc isprivate="0" >
    <commentid>1816975</commentid>
    <comment_count>0</comment_count>
    <who name="Andrei Anischevici">andrei002</who>
    <bug_when>2021-11-19 13:18:22 -0800</bug_when>
    <thetext>CSS :scope pseudo selector doesn&apos;t work in querySelectorAll() when it is called on the shadowRoot of a custom element.
Due to this bug, it&apos;s impossible to select direct descendants of the shadowRoot via CSS selector.

Sample code to reproduce the issue:

-------------------------------

  const content = `
    &lt;div&gt;
      &lt;input type=&quot;radio&quot; id=&quot;btn1&quot; name=&quot;btn1&quot; value=&quot;Button 1&quot;&gt;
      &lt;label for=&quot;btn1&quot;&gt;Button 1&lt;/label&gt;&lt;br&gt;
      &lt;div&gt;
        &lt;input type=&quot;radio&quot; id=&quot;btn2&quot; name=&quot;btn2&quot; value=&quot;Button 2&quot;&gt;
        &lt;label for=&quot;btn2&quot;&gt;Button 2&lt;/label&gt;&lt;br&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  `;
  customElements.define(
    &quot;my-button&quot;,
    class extends HTMLElement {
      constructor() {
        super();

        const shadowRoot = this.attachShadow({ mode: &quot;open&quot; });
        shadowRoot.innerHTML = content;
      }
    }
  );

  const container = document.createElement(&quot;DIV&quot;);
  container.setAttribute(&quot;id&quot;, &quot;container&quot;);
  container.innerHTML = `
    &lt;my-button&gt;FirstButton&lt;/my-button&gt;
    &lt;my-button&gt;SecondButton&lt;/my-button&gt;
  `;

 document.body.appendChild(container);

 document.body.querySelectorAll(&apos;:scope &gt; DIV&apos;); // works

 document.getElementById(&apos;container&apos;).children[0].shadowRoot.querySelectorAll(&apos;:scope &gt; DIV&apos;) // 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.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1817071</commentid>
    <comment_count>1</comment_count>
    <who name="Ryosuke Niwa">rniwa</who>
    <bug_when>2021-11-19 20:46:44 -0800</bug_when>
    <thetext>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.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1817119</commentid>
    <comment_count>2</comment_count>
    <who name="Andrei Anischevici">andrei002</who>
    <bug_when>2021-11-20 09:15:22 -0800</bug_when>
    <thetext>(In reply to Ryosuke Niwa from comment #1)
&gt; As mentioned in Mozilla bug, this seems working as intended. If this
&gt; 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&apos;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&apos;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&apos;s no way of selecting direct descendants of the shadow root, other than parsing the selector and manually matching each shadow root child.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1817120</commentid>
    <comment_count>3</comment_count>
    <who name="Ryosuke Niwa">rniwa</who>
    <bug_when>2021-11-20 09:26:17 -0800</bug_when>
    <thetext>(In reply to Andrei Anischevici from comment #2)
&gt; (In reply to Ryosuke Niwa from comment #1)
&gt; &gt; As mentioned in Mozilla bug, this seems working as intended. If this
&gt; &gt; behavior is desirable, perhaps you need to open a spec issue for CSS WG.
&gt; 
&gt; As I also mentioned in the Mozilla bug, this is not working as intended and
&gt; the comment with which that bug was initially resolved does not apply, as
&gt; we&apos;re not crossing the shadow boundary with a single selector, but instead
&gt; doing the selection from the shadow root node, which is expected to be
&gt; working, similarly to how it&apos;s working for the document root node. That
&gt; ticket has since been reopened and being looked into.

Well, :scope is spec&apos;ed to behave like this. node.querySelector is defined here:
https://dom.spec.whatwg.org/#dom-parentnode-queryselector

which invokes the algorithm &quot;to scope-match a selectors string&quot; with &quot;this&quot;, which is shadow root in this case:
https://dom.spec.whatwg.org/#scope-match-a-selectors-string

This algorithm in turn does this:
&quot;Return the result of match a selector against a tree with s (the parsed selector) and node’s root (shadow root&apos;s root is itself) using scoping root node (shadow root)&quot;

Now take a look at the definition of a scoping root:
https://drafts.csswg.org/selectors-4/#scoping-root
&quot;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&quot;

And now the definition of &quot;:scope&quot;:
https://drafts.csswg.org/selectors-4/#the-scope-pseudo

&gt;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&apos;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&apos;s behavior is the one that is not spec compliant.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1817130</commentid>
    <comment_count>4</comment_count>
    <who name="Andrei Anischevici">andrei002</who>
    <bug_when>2021-11-20 11:31:58 -0800</bug_when>
    <thetext>(In reply to Ryosuke Niwa from comment #3)

Thank you for the prompt response, I really appreciate the detailed reply!

&gt; This algorithm in turn does this:
&gt; &quot;Return the result of match a selector against a tree with s (the parsed
&gt; selector) and node’s root (shadow root&apos;s root is itself) using scoping root
&gt; node (shadow root)&quot;

&gt; &gt;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.
&gt; 
&gt; Here, the spec says that if the scoping root is not an element, we must use
&gt; the root of the document, which is a document. Since shadow root is not a
&gt; document, we would not match.

Please allow me to politely disagree and suggest that this a misinterpretation of the spec, specifically this part:
&gt; &quot;but the selector is scoped and the scoping root is an element, then :scope represents the scoping root&quot;

The shadow root IS an element, although it&apos;s a virtual element (since it&apos;s a DocumentFragment), it&apos;s still an element, according to the scoping root definition here https://drafts.csswg.org/selectors-4/#scoping-root

&quot;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)&quot;

&quot;virtual one&quot; == &quot;virtual element&quot;

I totally agree that this &quot;and the scoping root is an element&quot; part in the &quot;:scope&quot; spec is somewhat confusing and should&apos;ve been excluded altogether, since from the &quot;scoping root&quot; definition it&apos;s clear that it can&apos;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 &apos;:scope &gt; ...&apos; is the only selector that can accomplish this and it&apos;s a real pain to work around this if it&apos;s not supported..

I see that there&apos;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..</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1817131</commentid>
    <comment_count>5</comment_count>
    <who name="Andrei Anischevici">andrei002</who>
    <bug_when>2021-11-20 12:03:27 -0800</bug_when>
    <thetext>(In reply to Andrei Anischevici from comment #4)
&gt; I see that there&apos;s a new ::shadow pseudo-element proposed that would allow
&gt; an alternate solution to this issue
&gt; https://www.w3.org/TR/css-scoping-1/#shadow-pseudoelement
&gt; 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 &quot;:scope&quot; pseudo-class so it works on shadow roots.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1817132</commentid>
    <comment_count>6</comment_count>
    <who name="Andrei Anischevici">andrei002</who>
    <bug_when>2021-11-20 12:13:13 -0800</bug_when>
    <thetext>Additionally, in the &quot;Matching Selectors Against Shadow Trees&quot; section of the Scoping spec at https://drafts.csswg.org/css-scoping/#selectors-data-model
there&apos;s this note:

&quot;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.&quot;

Since the selector containing &quot;:scope&quot; is being matched in the context of the shadow tree, as specified here, it is expected that &quot;:scope&quot; would resolve to the shadow root, and not to the document root.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1817134</commentid>
    <comment_count>7</comment_count>
    <who name="Emilio Cobos Álvarez (:emilio)">emilio</who>
    <bug_when>2021-11-20 12:21:29 -0800</bug_when>
    <thetext>I&apos;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&apos;t checked whether that works in any browser.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1817261</commentid>
    <comment_count>8</comment_count>
    <who name="Emilio Cobos Álvarez (:emilio)">emilio</who>
    <bug_when>2021-11-22 03:01:19 -0800</bug_when>
    <thetext>I think this is invalid. You can use :host &gt; div instead, which ought to work per spec. I filed https://bugs.chromium.org/p/chromium/issues/detail?id=1272434 for Chromium.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1817268</commentid>
    <comment_count>9</comment_count>
    <who name="Andrei Anischevici">andrei002</who>
    <bug_when>2021-11-22 04:51:00 -0800</bug_when>
    <thetext>(In reply to Emilio Cobos Álvarez (:emilio) from comment #8)
&gt; I think this is invalid. You can use :host &gt; div instead, which ought to
&gt; work per spec. I filed
&gt; https://bugs.chromium.org/p/chromium/issues/detail?id=1272434 for Chromium.

Thank you for looking into this, Emilio!

I have just tested the &apos;:host &gt; div&apos; 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&apos;d suggest improving the CSS Selectors Level 4 spec, so that this is mentioned explicitly in :scope and there&apos;s no ambiguity:

&quot;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;&quot;

change to =&gt;

&quot;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;&quot;

would you agree?</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1817270</commentid>
    <comment_count>10</comment_count>
    <who name="Emilio Cobos Álvarez (:emilio)">emilio</who>
    <bug_when>2021-11-22 05:26:09 -0800</bug_when>
    <thetext>Yeah, that seems reasonable. Maybe file it in https://github.com/w3c/csswg-drafts/issues/new?</thetext>
  </long_desc>
      
      

    </bug>

</bugzilla>