Bug 1465291: Make pseudo-elements work with :host. r?xidorn draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Mon, 04 Jun 2018 16:27:00 +0200
changeset 803556 69d599553a9b2d1325c2d3749d299ad1a60f875d
parent 803555 80fd1fc133a998be4f1d5aff10637a26a8a42dfd
push id112143
push userbmo:emilio@crisal.io
push dateMon, 04 Jun 2018 14:35:05 +0000
reviewersxidorn
bugs1465291
milestone62.0a1
Bug 1465291: Make pseudo-elements work with :host. r?xidorn Imported WebKit's test as a WPT. MozReview-Commit-ID: 19ZThuoqKLW
servo/components/selectors/parser.rs
servo/components/style/stylist.rs
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/css/css-scoping/shadow-host-with-before-after.html
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -539,20 +539,36 @@ impl<Impl: SelectorImpl> Selector<Impl> 
     pub fn iter(&self) -> SelectorIter<Impl> {
         SelectorIter {
             iter: self.iter_raw_match_order(),
             next_combinator: None,
         }
     }
 
     /// Whether this selector is a featureless :host selector, with no
-    /// combinators to the left.
+    /// combinators to the left, and optionally has a pseudo-element to the
+    /// right.
     #[inline]
-    pub fn is_featureless_host_selector(&self) -> bool {
-        self.iter().is_featureless_host_selector()
+    pub fn is_featureless_host_selector_or_pseudo_element(&self) -> bool {
+        let mut iter = self.iter();
+        if !self.has_pseudo_element() {
+            return iter.is_featureless_host_selector();
+        }
+
+        // Skip the pseudo-element.
+        for _ in &mut iter { }
+
+        match iter.next_sequence() {
+            None => return false,
+            Some(combinator) => {
+                debug_assert_eq!(combinator, Combinator::PseudoElement);
+            }
+        }
+
+        iter.is_featureless_host_selector()
     }
 
     /// Returns an iterator over this selector in matching order (right-to-left),
     /// skipping the rightmost |offset| Components.
     #[inline]
     pub fn iter_from(&self, offset: usize) -> SelectorIter<Impl> {
         let iter = self.0.slice[offset..].iter();
         SelectorIter {
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -1953,17 +1953,17 @@ pub struct CascadeData {
 
     /// The `:host` pseudo rules that are the rightmost selector.
     ///
     /// Note that as of right now these can't affect invalidation in any way,
     /// until we support the :host(<selector>) notation.
     ///
     /// Also, note that other engines don't accept stuff like :host::before /
     /// :host::after, so we don't need to store pseudo rules at all.
-    host_rules: Option<Box<SelectorMap<Rule>>>,
+    host_rules: Option<Box<ElementAndPseudoRules>>,
 
     /// The data coming from ::slotted() pseudo-element rules.
     ///
     /// We need to store them separately because an element needs to match
     /// ::slotted() pseudo-element rules in different shadow roots.
     ///
     /// In particular, we need to go through all the style data in all the
     /// containing style scopes starting from the closest assigned slot.
@@ -2117,21 +2117,17 @@ impl CascadeData {
     }
     #[inline]
     fn normal_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
         self.normal_rules.rules(pseudo)
     }
 
     #[inline]
     fn host_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
-        if pseudo.is_some() {
-            return None;
-        }
-
-        self.host_rules.as_ref().map(|rules| &**rules)
+        self.host_rules.as_ref().and_then(|d| d.rules(pseudo))
     }
 
     #[inline]
     fn slotted_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
         self.slotted_rules.as_ref().and_then(|d| d.rules(pseudo))
     }
 
     /// Collects all the applicable media query results into `results`.
@@ -2253,30 +2249,30 @@ impl CascadeData {
                                         rule.selector.clone(),
                                         rule.hashes.clone(),
                                     ),
                                     quirks_mode,
                                 )?;
                             }
                         }
 
-                        if selector.is_featureless_host_selector() {
-                            let host_rules = self.host_rules
-                                .get_or_insert_with(|| Box::new(Default::default()));
-                            host_rules.insert(rule, quirks_mode)?;
+                        // NOTE(emilio): It's fine to look at :host and then at
+                        // ::slotted(..), since :host::slotted(..) could never
+                        // possibly match, as <slot> is not a valid shadow host.
+                        let rules = if selector.is_featureless_host_selector_or_pseudo_element() {
+                            self.host_rules
+                                .get_or_insert_with(|| Box::new(Default::default()))
+                        } else if selector.is_slotted() {
+                            self.slotted_rules
+                                .get_or_insert_with(|| Box::new(Default::default()))
                         } else {
-                            let rules = if selector.is_slotted() {
-                                self.slotted_rules
-                                    .get_or_insert_with(|| Box::new(Default::default()))
-                            } else {
-                                &mut self.normal_rules
-                            };
+                            &mut self.normal_rules
+                        };
 
-                            rules.insert(rule, pseudo_element, quirks_mode)?;
-                        }
+                        rules.insert(rule, pseudo_element, quirks_mode)?;
                     }
                     self.rules_source_order += 1;
                 },
                 CssRule::Import(ref lock) => {
                     if rebuild_kind.should_rebuild_invalidation() {
                         let import_rule = lock.read_with(guard);
                         self.effective_media_query_results
                             .saw_effective(import_rule);
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -128984,16 +128984,28 @@
       [
        "/css/css-scoping/reference/green-box.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "css/css-scoping/shadow-host-with-before-after.html": [
+    [
+     "/css/css-scoping/shadow-host-with-before-after.html",
+     [
+      [
+       "/css/css-scoping/reference/green-box.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-scoping/shadow-reassign-dynamic-001.html": [
     [
      "/css/css-scoping/shadow-reassign-dynamic-001.html",
      [
       [
        "/css/css-scoping/reference/green-box.html",
        "=="
       ]
@@ -519948,17 +519960,17 @@
    "2680b68f5c1720ce6d2b82d942ea21b0b1518587",
    "support"
   ],
   "css/css-scoping/shadow-assign-dynamic-001.html": [
    "c57e0fd5aa5be63e1cadf65a4e382798c5e05ec4",
    "reftest"
   ],
   "css/css-scoping/shadow-at-import.html": [
-   "40f2606177ad3143774d97060ac1bbfa9743801f",
+   "67295000ad3c24c2d9ab0ac556d34758f3ce654c",
    "reftest"
   ],
   "css/css-scoping/shadow-cascade-order-001.html": [
    "46913ea7e47811b11be898de5c3bd0a330ea6637",
    "testharness"
   ],
   "css/css-scoping/shadow-disabled-sheet-001.html": [
    "3de2d23c1b3339b964ec2c009832a3207a3b9dc4",
@@ -519979,16 +519991,20 @@
   "css/css-scoping/shadow-fallback-dynamic-004.html": [
    "71dcc60c4ff59690927c1575fff2eecf85ee558f",
    "reftest"
   ],
   "css/css-scoping/shadow-fallback-dynamic-005.html": [
    "ab3c3d205e59df800ba5b4217245b83685521c31",
    "reftest"
   ],
+  "css/css-scoping/shadow-host-with-before-after.html": [
+   "b33e82815776f05b6292a88bbb95daa882a50ac6",
+   "reftest"
+  ],
   "css/css-scoping/shadow-reassign-dynamic-001.html": [
    "11ed4da2e6ce88d8a2b98a8f1c814417ef7770dd",
    "reftest"
   ],
   "css/css-scoping/shadow-root-insert-into-document.html": [
    "2cee9fff35c9222074f4ef78dcfcb8a3e02bbc98",
    "reftest"
   ],
@@ -546044,17 +546060,17 @@
    "f131f271cb2f747e845584abcc445348e8c86521",
    "support"
   ],
   "css/cssom/StyleSheetList.html": [
    "0a1cd8ed56ac3a5b1a9556835d94fb80325199bf",
    "testharness"
   ],
   "css/cssom/at-namespace.html": [
-   "96da2dd244e9e19ff8ca1ca81b06c3ebdcee8267",
+   "cd3845557f5c40f51f7e3cbdfff52f440fe689b6",
    "testharness"
   ],
   "css/cssom/computed-style-001.html": [
    "0331a648e6b0d56f0e7365f1ff7d991ea77ce3e4",
    "testharness"
   ],
   "css/cssom/computed-style-002.html": [
    "d6579788bcfaf1d4c09324ba877a26ff95d6965d",
@@ -546176,29 +546192,29 @@
    "89c506ea58f2ad38eb9ecc1e5f422b81a45b07fa",
    "testharness"
   ],
   "css/cssom/inline-style-001.html": [
    "4c58b6153eabe796749dcaf181e03d7dce2c9c07",
    "testharness"
   ],
   "css/cssom/insertRule-charset-no-index.html": [
-   "cd3a96351a4c8dcd417fb03963f9d4fb0760c746",
+   "2be98274fe292089f381d216dc415ddc812a105f",
    "testharness"
   ],
   "css/cssom/insertRule-import-no-index.html": [
-   "ba89bad41a8d243f89ec91a0c02a34e97b378bc8",
+   "44ef5a2c490675d0088651dc101dbbb1fc83fdd1",
    "testharness"
   ],
   "css/cssom/insertRule-namespace-no-index.html": [
-   "109ed203fabac2da4279419deb34d5bc5a393d09",
+   "b9b63240c4a7bf52524b8e3dd36d6ca2ecb4bcdc",
    "testharness"
   ],
   "css/cssom/insertRule-no-index.html": [
-   "812f2b02d7694dd270b7a3e1ef205b99890ab216",
+   "825eb56d8e78bbdbd3bfb1861e6d40c245cd8f4b",
    "testharness"
   ],
   "css/cssom/insertRule-syntax-error-01.html": [
    "36f824b24dd56e20b7c524111512d8743745daaa",
    "testharness"
   ],
   "css/cssom/interfaces.html": [
    "42e325d3d7f6be7f557915072f61900ff611cef8",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/shadow-host-with-before-after.html
@@ -0,0 +1,87 @@
+<!doctype html>
+<title>CSS Test: Pseudo-elements and :host selector.</title>
+<link rel="author" title="Antti Koivisto" href="mailto:koivisto@iki.fi"/>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"/>
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model">
+<link rel="match" href="reference/green-box.html"/>
+<style>
+.test {
+    width: 100px;
+    height: 25px;
+    background: red;
+    color: red;
+}
+#host1, #host2  {
+    color: green;
+}
+#host3 div, #host4 div {
+    width: 50%;
+    height: 100%;
+    background: green;
+    display: inline-block;
+}
+</style>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host1" class="test"></div>
+<div id="host2" class="test"></div>
+<div id="host3" class="test"><div>text</div></div>
+<div id="host4" class="test"><div>text</div></div>
+<script>
+
+host1.attachShadow({mode: 'closed'}).innerHTML = `<style>
+    :host::before, :host::after {
+        background: green;
+        width: 50%;
+        height: 100%;
+        background: green;
+        display: inline-block;
+        content: "test";
+    }
+    </style>`;
+
+host2.attachShadow({mode: 'closed'}).innerHTML = `<style>
+    :host(.green)::before, :host(.green)::after {
+        background: green;
+        width: 50%;
+        height: 100%;
+        background: green;
+        display: inline-block;
+        content: "test";
+    }
+    </style>`;
+
+getComputedStyle(host2).backgroundColor;
+host2.classList.add('green');
+
+host3.attachShadow({mode: 'closed'}).innerHTML = `<style>
+    :host {
+        color: green !important;
+    }
+    :host::before {
+        background: green;
+        width: 50%;
+        height: 100%;
+        background: green;
+        display: inline-block;
+        content: "test";
+    }
+    </style><slot></slot>`;
+
+host4.attachShadow({mode: 'closed'}).innerHTML = `<style>
+    :host(.green) {
+        color: green !important;
+    }
+    :host(.green)::after {
+        background: green;
+        width: 50%;
+        height: 100%;
+        background: green;
+        display: inline-block;
+        content: "test";
+    }
+    </style><slot></slot>`;
+
+getComputedStyle(host4).backgroundColor;
+host4.classList.add('green');
+
+</script>