Bug 992245: Implement the non-functional :host selector. r?xidorn draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sat, 07 Apr 2018 19:46:00 +0200
changeset 779025 79c9b8511955aa1c649fdf96ac736d63ccad8a0b
parent 779022 efc150accdfc1f3103e5944e119f1355bd260b46
push id105635
push userbmo:emilio@crisal.io
push dateSun, 08 Apr 2018 15:28:13 +0000
reviewersxidorn
bugs992245
milestone61.0a1
Bug 992245: Implement the non-functional :host selector. r?xidorn Kinda tricky because :host only matches rules on the shadow root where the rules come from. So we need to be careful during invalidation and style sharing. I didn't use the non_ts_pseudo_class_list bits because as soon as we implement the :host(..) bits we're going to need to special-case it anyway. The general schema is the following: * Rightmost featureless :host selectors are handled inserting them in the host_rules hashmap. Note that we only insert featureless stuff there. We could insert all of them and just filter during matching, but that's slightly annoying. * The other selectors, like non-featureless :host or what not, are added to the normal cascade data. This is harmless, since the shadow host rules are never matched against the host, so we know they'll just never match, and avoids adding more special-cases. * Featureless :host selectors to the left of a combinator are handled during matching, in the special-case of next_element_for_combinator in selectors. This prevents this from being more invasive, and keeps the usual fast path slim, but it's a bit hard to match the spec and the implementation. We could keep a copy of the SelectorIter instead in the matching context to make the handling of featureless-ness explicit in match_non_ts_pseudo_class, but we'd still need the special-case anyway, so I'm not fond of it. * We take advantage of one thing that makes this sound. As you may have noticed, if you had `root` element which is a ShadowRoot, and you matched something like `div:host` against it, using a MatchingContext with current_host == root, we'd incorrectly report a match. But this is impossible due to the following constraints: * Shadow root rules aren't matched against the host during styling (except these featureless selectors). * CSSOM's current_host needs to be the _containing_ host, not the element itself if you're a Shadow host. Both Blink and WebKit pass the added tests. But of course all this stuff was untested initially, and my initial naive implementation would fail them. MozReview-Commit-ID: KayYNfTXb5h
servo/components/selectors/context.rs
servo/components/selectors/matching.rs
servo/components/selectors/parser.rs
servo/components/style/data.rs
servo/components/style/dom.rs
servo/components/style/dom_apis.rs
servo/components/style/gecko/selector_parser.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/invalidation/element/state_and_attributes.rs
servo/components/style/sharing/mod.rs
servo/components/style/stylist.rs
servo/ports/geckolib/glue.rs
testing/web-platform/meta/MANIFEST.json
testing/web-platform/meta/css/css-scoping/css-scoping-shadow-host-rule.html.ini
testing/web-platform/meta/css/css-scoping/shadow-cascade-order-001.html.ini
testing/web-platform/tests/css/css-scoping/host-cssom-001.html
testing/web-platform/tests/css/css-scoping/host-descendant-001.html
testing/web-platform/tests/css/css-scoping/host-descendant-002.html
testing/web-platform/tests/css/css-scoping/host-descendant-invalidation.html
testing/web-platform/tests/css/css-scoping/host-multiple-001.html
testing/web-platform/tests/css/css-scoping/host-nested-001.html
testing/web-platform/tests/css/css-scoping/host-slotted-001.html
--- a/servo/components/selectors/context.rs
+++ b/servo/components/selectors/context.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use attr::CaseSensitivity;
 use bloom::BloomFilter;
 use nth_index_cache::NthIndexCache;
 use parser::SelectorImpl;
-use tree::OpaqueElement;
+use tree::{Element, OpaqueElement};
 
 /// What kind of selector matching mode we should use.
 ///
 /// There are two modes of selector matching. The difference is only noticeable
 /// in presence of pseudo-elements.
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub enum MatchingMode {
     /// Don't ignore any pseudo-element selectors.
@@ -113,16 +113,19 @@ where
     /// scoping element is not relevant anymore, so we use a single field for
     /// them.
     ///
     /// When this is None, :scope will match the root element.
     ///
     /// See https://drafts.csswg.org/selectors-4/#scope-pseudo
     pub scope_element: Option<OpaqueElement>,
 
+    /// The current shadow host we're collecting :host rules for.
+    pub current_host: Option<OpaqueElement>,
+
     /// Controls how matching for links is handled.
     visited_handling: VisitedHandlingMode,
 
     /// The current nesting level of selectors that we're matching.
     ///
     /// FIXME(emilio): Consider putting the mutable stuff in a Cell, then make
     /// MatchingContext immutable again.
     nesting_level: usize,
@@ -173,24 +176,35 @@ where
         Self {
             matching_mode,
             bloom_filter,
             visited_handling,
             nth_index_cache,
             quirks_mode,
             classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
             scope_element: None,
+            current_host: None,
             nesting_level: 0,
             in_negation: false,
             pseudo_element_matching_fn: None,
             extra_data: Default::default(),
             _impl: ::std::marker::PhantomData,
         }
     }
 
+    /// Override the quirks mode we're matching against.
+    ///
+    /// FIXME(emilio): This is a hack for XBL quirks-mode mismatches.
+    #[inline]
+    pub fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) {
+        self.quirks_mode = quirks_mode;
+        self.classes_and_ids_case_sensitivity =
+            quirks_mode.classes_and_ids_case_sensitivity();
+    }
+
     /// Whether we're matching a nested selector.
     #[inline]
     pub fn is_nested(&self) -> bool {
         self.nesting_level != 0
     }
 
     /// Whether we're matching inside a :not(..) selector.
     #[inline]
@@ -261,9 +275,35 @@ where
         F: FnOnce(&mut Self) -> R,
     {
         let original_handling_mode = self.visited_handling;
         self.visited_handling = handling_mode;
         let result = f(self);
         self.visited_handling = original_handling_mode;
         result
     }
+
+    /// Runs F with a given shadow host which is the root of the tree whose
+    /// rules we're matching.
+    #[inline]
+    pub fn with_shadow_host<F, E, R>(
+        &mut self,
+        host: Option<E>,
+        f: F,
+    ) -> R
+    where
+        E: Element,
+        F: FnOnce(&mut Self) -> R,
+    {
+        let original_host = self.current_host.take();
+        self.current_host = host.map(|h| h.opaque());
+        let result = f(self);
+        self.current_host = original_host;
+        result
+    }
+
+    /// Returns the current shadow host whose shadow root we're matching rules
+    /// against.
+    #[inline]
+    pub fn shadow_host(&self) -> Option<OpaqueElement> {
+        self.current_host.clone()
+    }
 }
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -461,25 +461,19 @@ where
             //
             // and also:
             //
             //   When considered within its own shadow trees, the shadow host is
             //   featureless. Only the :host, :host(), and :host-context()
             //   pseudo-classes are allowed to match it.
             //
             // Since we know that the parent is a shadow root, we necessarily
-            // are in a shadow tree of the host.
-            let all_selectors_could_match = selector.clone().all(|component| {
-                match *component {
-                    Component::NonTSPseudoClass(ref pc) => pc.is_host(),
-                    _ => false,
-                }
-            });
-
-            if !all_selectors_could_match {
+            // are in a shadow tree of the host, and the next selector will only
+            // match if the selector is a featureless :host selector.
+            if !selector.clone().is_featureless_host_selector() {
                 return None;
             }
 
             element.containing_shadow_host()
         }
         Combinator::SlotAssignment => {
             debug_assert!(element.assigned_slot().map_or(true, |s| s.is_html_slot_element()));
             element.assigned_slot()
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -508,16 +508,23 @@ impl<Impl: SelectorImpl> Selector<Impl> 
     #[inline]
     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.
+    #[inline]
+    pub fn is_featureless_host_selector(&self) -> bool {
+        self.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 {
             iter: iter,
             next_combinator: None,
@@ -600,16 +607,28 @@ pub struct SelectorIter<'a, Impl: 'a + S
 impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
     /// Prepares this iterator to point to the next sequence to the left,
     /// returning the combinator if the sequence was found.
     #[inline]
     pub fn next_sequence(&mut self) -> Option<Combinator> {
         self.next_combinator.take()
     }
 
+    /// Whether this selector is a featureless host selector, with no
+    /// combinators to the left.
+    #[inline]
+    pub(crate) fn is_featureless_host_selector(&mut self) -> bool {
+        self.all(|component| {
+            match *component {
+                Component::NonTSPseudoClass(ref pc) => pc.is_host(),
+                _ => false,
+            }
+        }) && self.next_sequence().is_none()
+    }
+
     /// Returns remaining count of the simple selectors and combinators in the Selector.
     #[inline]
     pub fn selector_length(&self) -> usize {
         self.iter.len()
     }
 }
 
 impl<'a, Impl: SelectorImpl> Iterator for SelectorIter<'a, Impl> {
--- a/servo/components/style/data.rs
+++ b/servo/components/style/data.rs
@@ -257,18 +257,18 @@ impl ElementData {
                 element.implemented_pseudo_element());
 
         if !element.has_snapshot() || element.handled_snapshot() {
             return InvalidationResult::empty();
         }
 
         let mut non_document_styles = SmallVec::<[_; 3]>::new();
         let matches_doc_author_rules =
-            element.each_applicable_non_document_style_rule_data(|data, quirks_mode| {
-                non_document_styles.push((data, quirks_mode))
+            element.each_applicable_non_document_style_rule_data(|data, quirks_mode, host| {
+                non_document_styles.push((data, quirks_mode, host.map(|h| h.opaque())))
             });
 
         let mut processor = StateAndAttrInvalidationProcessor::new(
             shared_context,
             &non_document_styles,
             matches_doc_author_rules,
             element,
             self,
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -767,34 +767,47 @@ pub trait TElement
         F: FnMut(&'a CascadeData, QuirksMode),
     {
         false
     }
 
     /// Executes the callback for each applicable style rule data which isn't
     /// the main document's data (which stores UA / author rules).
     ///
+    /// The element passed to the callback is the containing shadow host for the
+    /// data if it comes from Shadow DOM, None if it comes from XBL.
+    ///
     /// Returns whether normal document author rules should apply.
     fn each_applicable_non_document_style_rule_data<'a, F>(&self, mut f: F) -> bool
     where
         Self: 'a,
-        F: FnMut(&'a CascadeData, QuirksMode),
+        F: FnMut(&'a CascadeData, QuirksMode, Option<Self>),
     {
-        let mut doc_rules_apply = !self.each_xbl_cascade_data(&mut f);
+        let mut doc_rules_apply = !self.each_xbl_cascade_data(|data, quirks_mode| {
+            f(data, quirks_mode, None);
+        });
 
         if let Some(shadow) = self.containing_shadow() {
             doc_rules_apply = false;
-            f(shadow.style_data(), self.as_node().owner_doc().quirks_mode());
+            f(
+                shadow.style_data(),
+                self.as_node().owner_doc().quirks_mode(),
+                Some(shadow.host()),
+            );
         }
 
         let mut current = self.assigned_slot();
         while let Some(slot) = current {
             // Slots can only have assigned nodes when in a shadow tree.
-            let data = slot.containing_shadow().unwrap().style_data();
-            f(data, self.as_node().owner_doc().quirks_mode());
+            let shadow = slot.containing_shadow().unwrap();
+            f(
+                shadow.style_data(),
+                self.as_node().owner_doc().quirks_mode(),
+                Some(shadow.host()),
+            );
             current = slot.assigned_slot();
         }
 
         doc_rules_apply
     }
 
     /// Does a rough (and cheap) check for whether or not transitions might need to be updated that
     /// will quickly return false for the common case of no transitions specified or running. If
--- a/servo/components/style/dom_apis.rs
+++ b/servo/components/style/dom_apis.rs
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Generic implementations of some DOM APIs so they can be shared between Servo
 //! and Gecko.
 
 use Atom;
 use context::QuirksMode;
-use dom::{TDocument, TElement, TNode};
+use dom::{TDocument, TElement, TNode, TShadowRoot};
 use invalidation::element::invalidator::{DescendantInvalidationLists, Invalidation};
 use invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
 use selectors::{Element, NthIndexCache, SelectorList};
 use selectors::attr::CaseSensitivity;
 use selectors::matching::{self, MatchingContext, MatchingMode};
 use selectors::parser::{Combinator, Component, LocalName};
 use smallvec::SmallVec;
 use std::borrow::Borrow;
@@ -28,16 +28,17 @@ where
 {
     let mut context = MatchingContext::new(
         MatchingMode::Normal,
         None,
         None,
         quirks_mode,
     );
     context.scope_element = Some(element.opaque());
+    context.current_host = element.containing_shadow_host().map(|e| e.opaque());
     matching::matches_selector_list(selector_list, element, &mut context)
 }
 
 /// <https://dom.spec.whatwg.org/#dom-element-closest>
 pub fn element_closest<E>(
     element: E,
     selector_list: &SelectorList<E::Impl>,
     quirks_mode: QuirksMode,
@@ -49,16 +50,17 @@ where
 
     let mut context = MatchingContext::new(
         MatchingMode::Normal,
         None,
         Some(&mut nth_index_cache),
         quirks_mode,
     );
     context.scope_element = Some(element.opaque());
+    context.current_host = element.containing_shadow_host().map(|e| e.opaque());
 
     let mut current = Some(element);
     while let Some(element) = current.take() {
         if matching::matches_selector_list(selector_list, &element, &mut context) {
             return Some(element);
         }
         current = element.parent_element();
     }
@@ -542,16 +544,20 @@ where
         MatchingMode::Normal,
         None,
         Some(&mut nth_index_cache),
         quirks_mode,
     );
 
     let root_element = root.as_element();
     matching_context.scope_element = root_element.map(|e| e.opaque());
+    matching_context.current_host = match root_element {
+        Some(root) => root.containing_shadow_host().map(|host| host.opaque()),
+        None => root.as_shadow_root().map(|root| root.host().opaque()),
+    };
 
     let fast_result = query_selector_fast::<E, Q>(
         root,
         selector_list,
         results,
         &mut matching_context,
     );
 
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -51,16 +51,20 @@ macro_rules! pseudo_class_name {
             )*
             /// The `:dir` pseudo-class.
             Dir(Box<Direction>),
             /// The non-standard `:-moz-any` pseudo-class.
             ///
             /// TODO(emilio): We disallow combinators and pseudos here, so we
             /// should use SimpleSelector instead
             MozAny(Box<[Selector<SelectorImpl>]>),
+            /// The `:host` pseudo-class:
+            ///
+            /// https://drafts.csswg.org/css-scoping/#host-selector
+            Host,
             /// The non-standard `:-moz-locale-dir` pseudo-class.
             MozLocaleDir(Box<Direction>),
         }
     }
 }
 apply_non_ts_list!(pseudo_class_name);
 
 impl ToCss for NonTSPseudoClass {
@@ -89,16 +93,19 @@ impl ToCss for NonTSPseudoClass {
                         dir.to_css(&mut CssWriter::new(dest))?;
                         return dest.write_char(')')
                     },
                     NonTSPseudoClass::Dir(ref dir) => {
                         dest.write_str(":dir(")?;
                         dir.to_css(&mut CssWriter::new(dest))?;
                         return dest.write_char(')')
                     },
+                    NonTSPseudoClass::Host => {
+                        return dest.write_str(":host");
+                    }
                     NonTSPseudoClass::MozAny(ref selectors) => {
                         dest.write_str(":-moz-any(")?;
                         let mut iter = selectors.iter();
                         let first = iter.next().expect(":-moz-any must have at least 1 selector");
                         first.to_css(dest)?;
                         for selector in iter {
                             dest.write_str(", ")?;
                             selector.to_css(dest)?;
@@ -112,17 +119,18 @@ impl ToCss for NonTSPseudoClass {
         dest.write_str(ser)
     }
 }
 
 impl Visit for NonTSPseudoClass {
     type Impl = SelectorImpl;
 
     fn visit<V>(&self, visitor: &mut V) -> bool
-        where V: SelectorVisitor<Impl = Self::Impl>,
+    where
+        V: SelectorVisitor<Impl = Self::Impl>,
     {
         if let NonTSPseudoClass::MozAny(ref selectors) = *self {
             for selector in selectors.iter() {
                 if !selector.visit(visitor) {
                     return false;
                 }
             }
         }
@@ -137,16 +145,17 @@ impl NonTSPseudoClass {
     /// None otherwise. It doesn't check whether the pseudo-class is enabled
     /// in a particular state.
     pub fn parse_non_functional(name: &str) -> Option<Self> {
         macro_rules! pseudo_class_parse {
             (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
              string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*]) => {
                 match_ignore_ascii_case! { &name,
                     $($css => Some(NonTSPseudoClass::$name),)*
+                    "host" => Some(NonTSPseudoClass::Host),
                     _ => None,
                 }
             }
         }
         apply_non_ts_list!(pseudo_class_parse)
     }
 
     /// Returns true if this pseudo-class has any of the given flags set.
@@ -158,16 +167,17 @@ impl NonTSPseudoClass {
         macro_rules! pseudo_class_check_is_enabled_in {
             (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
             string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*]) => {
                 match *self {
                     $(NonTSPseudoClass::$name => check_flag!($flags),)*
                     $(NonTSPseudoClass::$s_name(..) => check_flag!($s_flags),)*
                     // TODO(emilio): Maybe -moz-locale-dir shouldn't be
                     // content-exposed.
+                    NonTSPseudoClass::Host |
                     NonTSPseudoClass::MozLocaleDir(_) |
                     NonTSPseudoClass::Dir(_) |
                     NonTSPseudoClass::MozAny(_) => false,
                 }
             }
         }
         apply_non_ts_list!(pseudo_class_check_is_enabled_in)
     }
@@ -202,16 +212,17 @@ impl NonTSPseudoClass {
             ($state:ident) => (ElementState::$state);
         }
         macro_rules! pseudo_class_state {
             (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
              string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*]) => {
                 match *self {
                     $(NonTSPseudoClass::$name => flag!($state),)*
                     $(NonTSPseudoClass::$s_name(..) => flag!($s_state),)*
+                    NonTSPseudoClass::Host |
                     NonTSPseudoClass::Dir(..) |
                     NonTSPseudoClass::MozLocaleDir(..) |
                     NonTSPseudoClass::MozAny(..) => ElementState::empty(),
                 }
             }
         }
         apply_non_ts_list!(pseudo_class_state)
     }
@@ -225,16 +236,18 @@ impl NonTSPseudoClass {
         }
     }
 
     /// Returns true if the given pseudoclass should trigger style sharing cache
     /// revalidation.
     pub fn needs_cache_revalidation(&self) -> bool {
         self.state_flag().is_empty() &&
         !matches!(*self,
+                  // We handle this with the style sharing checks.
+                  NonTSPseudoClass::Host |
                   // :-moz-any is handled by the revalidation visitor walking
                   // the things inside it; it does not need to cause
                   // revalidation on its own.
                   NonTSPseudoClass::MozAny(_) |
                   // :dir() depends on state only, but doesn't use state_flag
                   // because its semantics don't quite match.  Nevertheless, it
                   // doesn't need cache revalidation, because we already compare
                   // states for elements and candidates.
@@ -273,17 +286,17 @@ impl ::selectors::parser::NonTSPseudoCla
 
     #[inline]
     fn is_active_or_hover(&self) -> bool {
         matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
     }
 
     #[inline]
     fn is_host(&self) -> bool {
-        false // TODO(emilio)
+        matches!(*self, NonTSPseudoClass::Host)
     }
 }
 
 /// The dummy struct we use to implement our selector parsing.
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct SelectorImpl;
 
 impl ::selectors::SelectorImpl for SelectorImpl {
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -2181,16 +2181,21 @@ impl<'le> ::selectors::Element for Gecko
             }
             NonTSPseudoClass::Dir(ref dir) => {
                 match **dir {
                     Direction::Ltr => self.state().intersects(ElementState::IN_LTR_STATE),
                     Direction::Rtl => self.state().intersects(ElementState::IN_RTL_STATE),
                     Direction::Other(..) => false,
                 }
             }
+            NonTSPseudoClass::Host => {
+                let matches = context.shadow_host().map_or(false, |host| host == self.opaque());
+                debug_assert!(!matches || self.shadow_root().is_some());
+                matches
+            }
         }
     }
 
     fn match_pseudo_element(
         &self,
         pseudo_element: &PseudoElement,
         _context: &mut MatchingContext<Self::Impl>,
     ) -> bool {
--- a/servo/components/style/invalidation/element/state_and_attributes.rs
+++ b/servo/components/style/invalidation/element/state_and_attributes.rs
@@ -16,16 +16,17 @@ use invalidation::element::invalidator::
 use invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
 use invalidation::element::restyle_hints::RestyleHint;
 use selector_map::SelectorMap;
 use selector_parser::Snapshot;
 use selectors::NthIndexCache;
 use selectors::attr::CaseSensitivity;
 use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode};
 use selectors::matching::matches_selector;
+use selectors::OpaqueElement;
 use smallvec::SmallVec;
 use stylesheets::origin::{Origin, OriginSet};
 use stylist::CascadeData;
 
 #[derive(Debug, PartialEq)]
 enum VisitedDependent {
     Yes,
     No,
@@ -33,46 +34,45 @@ enum VisitedDependent {
 
 /// The collector implementation.
 struct Collector<'a, 'b: 'a, 'selectors: 'a, E>
 where
     E: TElement,
 {
     element: E,
     wrapper: ElementWrapper<'b, E>,
-    nth_index_cache: Option<&'a mut NthIndexCache>,
     snapshot: &'a Snapshot,
-    quirks_mode: QuirksMode,
+    matching_context: &'a mut MatchingContext<'b, E::Impl>,
     lookup_element: E,
     removed_id: Option<&'a WeakAtom>,
     added_id: Option<&'a WeakAtom>,
     classes_removed: &'a SmallVec<[Atom; 8]>,
     classes_added: &'a SmallVec<[Atom; 8]>,
     state_changes: ElementState,
     descendant_invalidations: &'a mut DescendantInvalidationLists<'selectors>,
     sibling_invalidations: &'a mut InvalidationVector<'selectors>,
     invalidates_self: bool,
 }
 
 /// An invalidation processor for style changes due to state and attribute
 /// changes.
 pub struct StateAndAttrInvalidationProcessor<'a, 'b: 'a, E: TElement> {
     shared_context: &'a SharedStyleContext<'b>,
-    shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode)],
+    shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode, Option<OpaqueElement>)],
     matches_document_author_rules: bool,
     element: E,
     data: &'a mut ElementData,
     matching_context: MatchingContext<'a, E::Impl>,
 }
 
 impl<'a, 'b: 'a, E: TElement> StateAndAttrInvalidationProcessor<'a, 'b, E> {
     /// Creates a new StateAndAttrInvalidationProcessor.
     pub fn new(
         shared_context: &'a SharedStyleContext<'b>,
-        shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode)],
+        shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode, Option<OpaqueElement>)],
         matches_document_author_rules: bool,
         element: E,
         data: &'a mut ElementData,
         nth_index_cache: &'a mut NthIndexCache,
     ) -> Self {
         let matching_context = MatchingContext::new_for_visited(
             MatchingMode::Normal,
             None,
@@ -232,18 +232,17 @@ where
 
         let invalidated_self = {
             let mut collector = Collector {
                 wrapper,
                 lookup_element,
                 state_changes,
                 element,
                 snapshot: &snapshot,
-                quirks_mode: self.shared_context.quirks_mode(),
-                nth_index_cache: self.matching_context.nth_index_cache.as_mut().map(|c| &mut **c),
+                matching_context: &mut self.matching_context,
                 removed_id: id_removed,
                 added_id: id_added,
                 classes_removed: &classes_removed,
                 classes_added: &classes_added,
                 descendant_invalidations,
                 sibling_invalidations,
                 invalidates_self: false,
             };
@@ -255,21 +254,22 @@ where
             };
 
             for (cascade_data, origin) in self.shared_context.stylist.iter_origins() {
                 if document_origins.contains(origin.into()) {
                     collector.collect_dependencies_in_invalidation_map(cascade_data.invalidation_map());
                 }
             }
 
-            for &(ref data, quirks_mode) in self.shadow_rule_datas {
+            for &(ref data, quirks_mode, ref host) in self.shadow_rule_datas {
                 // FIXME(emilio): Replace with assert / remove when we figure
                 // out what to do with the quirks mode mismatches
                 // (that is, when bug 1406875 is properly fixed).
-                collector.quirks_mode = quirks_mode;
+                collector.matching_context.set_quirks_mode(quirks_mode);
+                collector.matching_context.current_host = host.clone();
                 collector.collect_dependencies_in_invalidation_map(data.invalidation_map());
             }
 
             collector.invalidates_self
         };
 
         // If we generated a ton of descendant invalidations, it's probably not
         // worth to go ahead and try to process them.
@@ -328,17 +328,17 @@ impl<'a, 'b, 'selectors, E> Collector<'a
 where
     E: TElement,
     'selectors: 'a,
 {
     fn collect_dependencies_in_invalidation_map(
         &mut self,
         map: &'selectors InvalidationMap,
     ) {
-        let quirks_mode = self.quirks_mode;
+        let quirks_mode = self.matching_context.quirks_mode();
         let removed_id = self.removed_id;
         if let Some(ref id) = removed_id {
             if let Some(deps) = map.id_to_selector.get(id, quirks_mode) {
                 for dep in deps {
                     self.scan_dependency(dep, VisitedDependent::No);
                 }
             }
         }
@@ -381,34 +381,34 @@ where
     }
 
     fn collect_dependencies_in_map(
         &mut self,
         map: &'selectors SelectorMap<Dependency>,
     ) {
         map.lookup_with_additional(
             self.lookup_element,
-            self.quirks_mode,
+            self.matching_context.quirks_mode(),
             self.removed_id,
             self.classes_removed,
             |dependency| {
                 self.scan_dependency(dependency, VisitedDependent::No);
                 true
             },
         );
     }
 
     fn collect_state_dependencies(
         &mut self,
         map: &'selectors SelectorMap<StateDependency>,
         state_changes: ElementState,
     ) {
         map.lookup_with_additional(
             self.lookup_element,
-            self.quirks_mode,
+            self.matching_context.quirks_mode(),
             self.removed_id,
             self.classes_removed,
             |dependency| {
                 if !dependency.state.intersects(state_changes) {
                     return true;
                 }
                 let visited_dependent =
                     if dependency.state.intersects(ElementState::IN_VISITED_OR_UNVISITED_STATE) {
@@ -424,61 +424,39 @@ where
 
     /// Check whether a dependency should be taken into account, using a given
     /// visited handling mode.
     fn check_dependency(
         &mut self,
         visited_handling_mode: VisitedHandlingMode,
         dependency: &Dependency,
     ) -> bool {
-        let matches_now = {
-            let mut context = MatchingContext::new_for_visited(
-                MatchingMode::Normal,
-                None,
-                self.nth_index_cache.as_mut().map(|c| &mut **c),
-                visited_handling_mode,
-                self.quirks_mode,
-            );
-
+        let element = &self.element;
+        let wrapper = &self.wrapper;
+        self.matching_context.with_visited_handling_mode(visited_handling_mode, |mut context| {
             let matches_now = matches_selector(
                 &dependency.selector,
                 dependency.selector_offset,
                 None,
-                &self.element,
+                element,
                 &mut context,
                 &mut |_, _| {},
             );
 
-            matches_now
-        };
-
-        let matched_then = {
-            let mut context = MatchingContext::new_for_visited(
-                MatchingMode::Normal,
-                None,
-                self.nth_index_cache.as_mut().map(|c| &mut **c),
-                visited_handling_mode,
-                self.quirks_mode,
-            );
-
             let matched_then = matches_selector(
                 &dependency.selector,
                 dependency.selector_offset,
                 None,
-                &self.wrapper,
+                wrapper,
                 &mut context,
                 &mut |_, _| {},
             );
 
-            matched_then
-        };
-
-        // Check for mismatches in both the match result and also the status
-        // of whether a relevant link was found.
-        matched_then != matches_now
+            matched_then != matches_now
+        })
     }
 
     fn scan_dependency(
         &mut self,
         dependency: &'selectors Dependency,
         is_visited_dependent: VisitedDependent,
     ) {
         debug!("TreeStyleInvalidator::scan_dependency({:?}, {:?}, {:?})",
--- a/servo/components/style/sharing/mod.rs
+++ b/servo/components/style/sharing/mod.rs
@@ -548,16 +548,26 @@ impl<E: TElement> StyleSharingCache<E> {
             }
         };
 
         if element.is_native_anonymous() {
             debug!("Failing to insert into the cache: NAC");
             return;
         }
 
+        // We can't share style across shadow hosts right now, because they may
+        // match different :host rules.
+        //
+        // TODO(emilio): We could share across the ones that don't have :host
+        // rules or have the same.
+        if element.shadow_root().is_some() {
+            debug!("Failing to insert into the cache: Shadow Host");
+            return;
+        }
+
         // If the element has running animations, we can't share style.
         //
         // This is distinct from the specifies_{animations,transitions} check below,
         // because:
         //   * Animations can be triggered directly via the Web Animations API.
         //   * Our computed style can still be affected by animations after we no
         //     longer match any animation rules, since removing animations involves
         //     a sequential task and an additional traversal.
@@ -711,16 +721,21 @@ impl<E: TElement> StyleSharingCache<E> {
         // assigned to yet another slot in another shadow root.
         if target.element.assigned_slot() != candidate.element.assigned_slot() {
             // TODO(emilio): We could have a look at whether the shadow roots
             // actually have slotted rules and such.
             trace!("Miss: Different assigned slots");
             return None;
         }
 
+        if target.element.shadow_root().is_some() {
+            trace!("Miss: Shadow host");
+            return None;
+        }
+
         if target.matches_user_and_author_rules() !=
             candidate.element.matches_user_and_author_rules() {
             trace!("Miss: User and Author Rules");
             return None;
         }
 
         // It's possible that there are no styles for either id.
         let may_match_different_id_rules =
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -586,35 +586,34 @@ impl Stylist {
         self.stylesheets.remove_stylesheet(Some(&self.device), sheet, guard)
     }
 
     /// Returns whether for any of the applicable style rule data a given
     /// condition is true.
     pub fn any_applicable_rule_data<E, F>(&self, element: E, mut f: F) -> bool
     where
         E: TElement,
-        F: FnMut(&CascadeData, QuirksMode) -> bool,
+        F: FnMut(&CascadeData) -> bool,
     {
-        if f(&self.cascade_data.user_agent.cascade_data, self.quirks_mode()) {
+        if f(&self.cascade_data.user_agent.cascade_data) {
             return true;
         }
 
         let mut maybe = false;
 
         let doc_author_rules_apply =
-            element.each_applicable_non_document_style_rule_data(|data, quirks_mode| {
-                maybe = maybe || f(&*data, quirks_mode);
+            element.each_applicable_non_document_style_rule_data(|data, _, _| {
+                maybe = maybe || f(&*data);
             });
 
         if maybe || !doc_author_rules_apply {
             return maybe;
         }
 
-        f(&self.cascade_data.author, self.quirks_mode()) ||
-        f(&self.cascade_data.user, self.quirks_mode())
+        f(&self.cascade_data.author) || f(&self.cascade_data.user)
     }
 
     /// Computes the style for a given "precomputed" pseudo-element, taking the
     /// universal rules and applying them.
     ///
     /// If `inherit_all` is true, then all properties are inherited from the
     /// parent; otherwise, non-inherited properties are reset to their initial
     /// values. The flow constructor uses this flag when constructing anonymous
@@ -1256,52 +1255,70 @@ impl Stylist {
 
         // XBL / Shadow DOM rules, which are author rules too.
         //
         // TODO(emilio): Cascade order here is wrong for Shadow DOM. In
         // particular, normally document rules override ::slotted() rules, but
         // for !important it should be the other way around. So probably we need
         // to add some sort of AuthorScoped cascade level or something.
         if matches_author_rules && !only_default_rules {
+            if let Some(shadow) = rule_hash_target.shadow_root() {
+                if let Some(map) = shadow.style_data().host_rules(pseudo_element) {
+                    context.with_shadow_host(Some(rule_hash_target), |context| {
+                        map.get_all_matching_rules(
+                            element,
+                            rule_hash_target,
+                            applicable_declarations,
+                            context,
+                            flags_setter,
+                            CascadeLevel::AuthorNormal,
+                        );
+                    });
+                }
+            }
+
             // Match slotted rules in reverse order, so that the outer slotted
             // rules come before the inner rules (and thus have less priority).
             let mut slots = SmallVec::<[_; 3]>::new();
             let mut current = rule_hash_target.assigned_slot();
             while let Some(slot) = current {
                 slots.push(slot);
                 current = slot.assigned_slot();
             }
 
             for slot in slots.iter().rev() {
-                let styles = slot.containing_shadow().unwrap().style_data();
+                let shadow = slot.containing_shadow().unwrap();
+                let styles = shadow.style_data();
                 if let Some(map) = styles.slotted_rules(pseudo_element) {
-                    map.get_all_matching_rules(
-                        element,
-                        rule_hash_target,
-                        applicable_declarations,
-                        context,
-                        flags_setter,
-                        CascadeLevel::AuthorNormal,
-                    );
+                    context.with_shadow_host(Some(shadow.host()), |context| {
+                        map.get_all_matching_rules(
+                            element,
+                            rule_hash_target,
+                            applicable_declarations,
+                            context,
+                            flags_setter,
+                            CascadeLevel::AuthorNormal,
+                        );
+                    });
                 }
             }
 
-            // TODO(emilio): We need to look up :host rules if the element is a
-            // shadow host, when we implement that.
             if let Some(containing_shadow) = rule_hash_target.containing_shadow() {
                 let cascade_data = containing_shadow.style_data();
                 if let Some(map) = cascade_data.normal_rules(pseudo_element) {
-                    map.get_all_matching_rules(
-                        element,
-                        rule_hash_target,
-                        applicable_declarations,
-                        context,
-                        flags_setter,
-                        CascadeLevel::AuthorNormal,
-                    );
+                    context.with_shadow_host(Some(containing_shadow.host()), |context| {
+                        map.get_all_matching_rules(
+                            element,
+                            rule_hash_target,
+                            applicable_declarations,
+                            context,
+                            flags_setter,
+                            CascadeLevel::AuthorNormal,
+                        );
+                    });
                 }
 
                 match_document_author_rules = false;
             }
         }
 
         // FIXME(emilio): It looks very wrong to match XBL rules even for
         // getDefaultComputedStyle!
@@ -1416,17 +1433,17 @@ impl Stylist {
         // If id needs to be compared case-insensitively, the logic below
         // wouldn't work. Just conservatively assume it may have such rules.
         match self.quirks_mode().classes_and_ids_case_sensitivity() {
             CaseSensitivity::AsciiCaseInsensitive => return true,
             CaseSensitivity::CaseSensitive => {}
         }
 
         let hash = id.get_hash();
-        self.any_applicable_rule_data(element, |data, _| {
+        self.any_applicable_rule_data(element, |data| {
             data.mapped_ids.might_contain_hash(hash)
         })
     }
 
     /// Returns the registered `@keyframes` animation for the specified name.
     ///
     /// FIXME(emilio): This needs to account for the element rules.
     #[inline]
@@ -1478,32 +1495,34 @@ impl Stylist {
                         &mut matching_context,
                         flags_setter
                     ));
                     true
                 }
             );
         }
 
-        element.each_applicable_non_document_style_rule_data(|data, quirks_mode| {
-            data.selectors_for_cache_revalidation.lookup(
-                element,
-                quirks_mode,
-                |selector_and_hashes| {
-                    results.push(matches_selector(
-                        &selector_and_hashes.selector,
-                        selector_and_hashes.selector_offset,
-                        Some(&selector_and_hashes.hashes),
-                        &element,
-                        &mut matching_context,
-                        flags_setter
-                    ));
-                    true
-                }
-            );
+        element.each_applicable_non_document_style_rule_data(|data, quirks_mode, host| {
+            matching_context.with_shadow_host(host, |matching_context| {
+                data.selectors_for_cache_revalidation.lookup(
+                    element,
+                    quirks_mode,
+                    |selector_and_hashes| {
+                        results.push(matches_selector(
+                            &selector_and_hashes.selector,
+                            selector_and_hashes.selector_offset,
+                            Some(&selector_and_hashes.hashes),
+                            &element,
+                            matching_context,
+                            flags_setter
+                        ));
+                        true
+                    }
+                );
+            })
         });
 
         results
     }
 
     /// Computes styles for a given declaration with parent_style.
     ///
     /// FIXME(emilio): the lack of pseudo / cascade flags look quite dubious,
@@ -1924,16 +1943,25 @@ impl ElementAndPseudoRules {
 /// FIXME(emilio): Consider renaming and splitting in `CascadeData` and
 /// `InvalidationData`? That'd make `clear_cascade_data()` clearer.
 #[derive(Debug, MallocSizeOf)]
 pub struct CascadeData {
     /// The data coming from normal style rules that apply to elements at this
     /// cascade level.
     normal_rules: ElementAndPseudoRules,
 
+    /// 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>>>,
+
     /// 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.
     slotted_rules: Option<Box<ElementAndPseudoRules>>,
@@ -2000,16 +2028,17 @@ pub struct CascadeData {
     num_declarations: usize,
 }
 
 impl CascadeData {
     /// Creates an empty `CascadeData`.
     pub fn new() -> Self {
         Self {
             normal_rules: ElementAndPseudoRules::default(),
+            host_rules: None,
             slotted_rules: None,
             invalidation_map: InvalidationMap::new(),
             attribute_dependencies: NonCountingBloomFilter::new(),
             style_attribute_dependency: false,
             state_dependencies: ElementState::empty(),
             document_state_dependencies: DocumentState::empty(),
             mapped_ids: NonCountingBloomFilter::new(),
             selectors_for_cache_revalidation: SelectorMap::new(),
@@ -2087,16 +2116,25 @@ impl CascadeData {
         self.attribute_dependencies.might_contain_hash(local_name.get_hash())
     }
     #[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)
+    }
+
+    #[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`.
     ///
     /// This duplicates part of the logic in `add_stylesheet`, which is
     /// a bit unfortunate.
@@ -2219,29 +2257,33 @@ impl CascadeData {
                                         rule.selector.clone(),
                                         rule.hashes.clone(),
                                     ),
                                     quirks_mode
                                 )?;
                             }
                         }
 
-                        let rules = if selector.is_slotted() {
-                            self.slotted_rules.get_or_insert_with(|| {
-                                Box::new(Default::default())
-                            })
+                        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)?;
                         } else {
-                            &mut self.normal_rules
-                        };
+                            let rules = if selector.is_slotted() {
+                                self.slotted_rules.get_or_insert_with(|| {
+                                    Box::new(Default::default())
+                                })
+                            } else {
+                                &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/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -4909,17 +4909,17 @@ pub extern "C" fn Servo_StyleSet_MightHa
     element: RawGeckoElementBorrowed,
     local_name: *mut nsAtom,
 ) -> bool {
     let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
     let element = GeckoElement(element);
 
     unsafe {
         Atom::with(local_name, |atom| {
-            data.stylist.any_applicable_rule_data(element, |data, _| {
+            data.stylist.any_applicable_rule_data(element, |data| {
                 data.might_have_attribute_dependency(atom)
             })
         })
     }
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_HasStateDependency(
@@ -4927,17 +4927,17 @@ pub extern "C" fn Servo_StyleSet_HasStat
     element: RawGeckoElementBorrowed,
     state: u64,
 ) -> bool {
     let element = GeckoElement(element);
 
     let state = ElementState::from_bits_truncate(state);
     let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
 
-    data.stylist.any_applicable_rule_data(element, |data, _| {
+    data.stylist.any_applicable_rule_data(element, |data| {
         data.has_state_dependency(state)
     })
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_HasDocumentStateDependency(
     raw_data: RawServoStyleSetBorrowed,
     state: u64,
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -125506,16 +125506,76 @@
       [
        "/css/css-scoping/reference/green-box.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "css/css-scoping/host-descendant-001.html": [
+    [
+     "/css/css-scoping/host-descendant-001.html",
+     [
+      [
+       "/css/css-scoping/reference/green-box.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-scoping/host-descendant-002.html": [
+    [
+     "/css/css-scoping/host-descendant-002.html",
+     [
+      [
+       "/css/css-scoping/reference/green-box.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-scoping/host-multiple-001.html": [
+    [
+     "/css/css-scoping/host-multiple-001.html",
+     [
+      [
+       "/css/css-scoping/reference/green-box.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-scoping/host-nested-001.html": [
+    [
+     "/css/css-scoping/host-nested-001.html",
+     [
+      [
+       "/css/css-scoping/reference/green-box.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-scoping/host-slotted-001.html": [
+    [
+     "/css/css-scoping/host-slotted-001.html",
+     [
+      [
+       "/css/css-scoping/reference/green-box.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-scoping/shadow-assign-dynamic-001.html": [
     [
      "/css/css-scoping/shadow-assign-dynamic-001.html",
      [
       [
        "/css/css-scoping/reference/green-box.html",
        "=="
       ]
@@ -313690,16 +313750,28 @@
     ]
    ],
    "css/css-ruby/line-break-around-ruby-001.html": [
     [
      "/css/css-ruby/line-break-around-ruby-001.html",
      {}
     ]
    ],
+   "css/css-scoping/host-cssom-001.html": [
+    [
+     "/css/css-scoping/host-cssom-001.html",
+     {}
+    ]
+   ],
+   "css/css-scoping/host-descendant-invalidation.html": [
+    [
+     "/css/css-scoping/host-descendant-invalidation.html",
+     {}
+    ]
+   ],
    "css/css-scoping/shadow-cascade-order-001.html": [
     [
      "/css/css-scoping/shadow-cascade-order-001.html",
      {}
     ]
    ],
    "css/css-scoping/slotted-invalidation.html": [
     [
@@ -481454,17 +481526,17 @@
    "f5f4f2279506dc6106de38080a639979ad7e05d0",
    "testharness"
   ],
   "css/css-align/content-distribution/place-content-shorthand-003.html": [
    "966e8dd3893c487b02bb139ac26862995b655603",
    "testharness"
   ],
   "css/css-align/content-distribution/place-content-shorthand-004.html": [
-   "e755317eedc580d6373df24596590d22fc9aad5d",
+   "52566e7e88edf9f57f441498b9d366b66be50fd7",
    "testharness"
   ],
   "css/css-align/content-distribution/place-content-shorthand-005.html": [
    "6b3d7b9ae7d5b28510385cccaaade09268409cab",
    "testharness"
   ],
   "css/css-align/content-distribution/place-content-shorthand-006.html": [
    "a40c054505716dda72b642ada40d8796a5d68795",
@@ -481546,17 +481618,17 @@
    "638f5cbcc320477d495b9b0a752e6aaa048fee5e",
    "testharness"
   ],
   "css/css-align/default-alignment/place-items-shorthand-006.html": [
    "b7106d0ee863673c0d9a6160d035706edb3c67a8",
    "testharness"
   ],
   "css/css-align/default-alignment/shorthand-serialization-001.html": [
-   "0126d0a56ec9af324a886d286563cf8f39fc1b78",
+   "6c85da43be475b680d5351bdb969d09b90a6d97e",
    "testharness"
   ],
   "css/css-align/distribution-values/space-evenly-001.html": [
    "6fd28a5bf615ce822ed935de90ce5c1a41d39104",
    "reftest"
   ],
   "css/css-align/gaps/column-gap-animation-001.html": [
    "00023e39b6fafbfb31cabb30ed4ddf77a71b248f",
@@ -481678,17 +481750,17 @@
    "a266e09207859b058f445acb7c472156e525dd21",
    "testharness"
   ],
   "css/css-align/self-alignment/place-self-shorthand-003.html": [
    "2f17a907183eee3c3383894212f7d3d84770db2d",
    "testharness"
   ],
   "css/css-align/self-alignment/place-self-shorthand-004.html": [
-   "4f153c62e5d1eecf9eb4678036123d0460d7ecf1",
+   "2259cbd38befc9cf4fcb3e7a567fe3197740f613",
    "testharness"
   ],
   "css/css-align/self-alignment/place-self-shorthand-005.html": [
    "5a11a37355d05683ac1adbf1a5f6e1d0f5c88863",
    "testharness"
   ],
   "css/css-align/self-alignment/place-self-shorthand-006.html": [
    "d355eac038a7307e7a969632bc745ad25d8d6f43",
@@ -486298,17 +486370,17 @@
    "056b3597f3555c803c74a8f6277a06626efd12ea",
    "reftest"
   ],
   "css/css-content/attr-case-insensitive-ref.html": [
    "30577fc39afb6ac028e25be11f363e060c0850b2",
    "support"
   ],
   "css/css-content/attr-case-insensitive.html": [
-   "1118de214d8118e6b973f70b4496a4e5d51f3600",
+   "6b6cf2c15295940fb8831d17209635dc4e31cd78",
    "reftest"
   ],
   "css/css-content/element-replacement-ref.html": [
    "f1ad3fca133b1b671e45ae1307fbe9454c40e3ec",
    "support"
   ],
   "css/css-content/element-replacement.html": [
    "f491ddf2b3062ea2f9b616c968c88b9cc95f22eb",
@@ -506665,16 +506737,44 @@
   "css/css-scoping/css-scoping-shadow-with-rules-no-style-leak.html": [
    "1021b74fe5071a7df6dd9ef085c35e81b29d4fa2",
    "reftest"
   ],
   "css/css-scoping/css-scoping-shadow-with-rules.html": [
    "69774c51e6df49c891ecaaf2c384552106a1eb32",
    "reftest"
   ],
+  "css/css-scoping/host-cssom-001.html": [
+   "f77b672837e1c9728e53d74b533d79530fbd1249",
+   "testharness"
+  ],
+  "css/css-scoping/host-descendant-001.html": [
+   "703ba0d07ece44f4cc017b5351dea3057337f234",
+   "reftest"
+  ],
+  "css/css-scoping/host-descendant-002.html": [
+   "c96ebd0da1c7b2ce00e56bbef54bdd382789e2f2",
+   "reftest"
+  ],
+  "css/css-scoping/host-descendant-invalidation.html": [
+   "ec27e3cbe587470ecb945357c74954baf139d797",
+   "testharness"
+  ],
+  "css/css-scoping/host-multiple-001.html": [
+   "eb45ceb8c80d2cfbeb5bd317ab906f0881a13435",
+   "reftest"
+  ],
+  "css/css-scoping/host-nested-001.html": [
+   "a0b74d2e6bf24e9142904a925f95e969d206db20",
+   "reftest"
+  ],
+  "css/css-scoping/host-slotted-001.html": [
+   "918bd04b95a276c6035383f2fe4dcfe4274bceeb",
+   "reftest"
+  ],
   "css/css-scoping/reference/green-box.html": [
    "a736f68dc602c0fccab56ec5cc6234cb3298c88d",
    "support"
   ],
   "css/css-scoping/shadow-assign-dynamic-001.html": [
    "c57e0fd5aa5be63e1cadf65a4e382798c5e05ec4",
    "reftest"
   ],
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-scoping/css-scoping-shadow-host-rule.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[css-scoping-shadow-host-rule.html]
-  expected: FAIL
--- a/testing/web-platform/meta/css/css-scoping/shadow-cascade-order-001.html.ini
+++ b/testing/web-platform/meta/css/css-scoping/shadow-cascade-order-001.html.ini
@@ -1,14 +1,9 @@
 [shadow-cascade-order-001.html]
-  [C2. document vs :host with !important, :host rule should win for open mode.]
-    expected: FAIL
-
-  [C6. :host with !important vs inline, :host rule should win for open mode.]
-    expected: FAIL
 
   [D1. document vs ::slotted both with !important, ::slotted rule should win for open mode.]
     expected: FAIL
 
   [D2. document vs :host both with !important, :host rule should win for open mode.]
     expected: FAIL
 
   [D4. ::slotted vs :host both with !important, later in tree-of-trees rule should win for open mode.]
@@ -18,28 +13,19 @@
     expected: FAIL
 
   [D6. :host vs inline both with !important, :host rule should win for open mode.]
     expected: FAIL
 
   [E2. all styles with !important applied, rule in the last tree-of-trees should win for open mode.]
     expected: FAIL
 
-  [F5. document vs :host with !important, important rule should win for open mode.]
-    expected: FAIL
-
   [F6. all rules with !important, the last rule in tree-of-trees should win for open mode.]
     expected: FAIL
 
-  [C2. document vs :host with !important, :host rule should win for closed mode.]
-    expected: FAIL
-
-  [C6. :host with !important vs inline, :host rule should win for closed mode.]
-    expected: FAIL
-
   [D1. document vs ::slotted both with !important, ::slotted rule should win for closed mode.]
     expected: FAIL
 
   [D2. document vs :host both with !important, :host rule should win for closed mode.]
     expected: FAIL
 
   [D4. ::slotted vs :host both with !important, later in tree-of-trees rule should win for closed mode.]
     expected: FAIL
@@ -48,14 +34,11 @@
     expected: FAIL
 
   [D6. :host vs inline both with !important, :host rule should win for closed mode.]
     expected: FAIL
 
   [E2. all styles with !important applied, rule in the last tree-of-trees should win for closed mode.]
     expected: FAIL
 
-  [F5. document vs :host with !important, important rule should win for closed mode.]
-    expected: FAIL
-
   [F6. all rules with !important, the last rule in tree-of-trees should win for closed mode.]
     expected: FAIL
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-cssom-001.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>CSS Test: :host in CSSOM</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<div id="host"></div>
+<script>
+test(function() {
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `<div></div>`;
+  assert_false(host.matches(":host"), ":host shouldn't match from CSSOM from outside the shadow tree");
+  assert_true(root.firstElementChild.matches(":host div"), ":host should match from within the shadow tree");
+  assert_equals(root.querySelector(":host div"), root.firstElementChild, ":host should match from within the shadow tree");
+})
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-descendant-001.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"><div></div></div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      :host ::slotted(div) { width: 100px; height: 100px; background: green; }
+      * :host ::slotted(div) { background: red; }
+    </style>
+    <slot></slot>
+  `;
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-descendant-002.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"></div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      :host { width: 100px; height: 100px; background: green; }
+      * :host { background: red; }
+    </style>
+  `;
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-descendant-invalidation.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>CSS Test: :host is accounted for during invalidation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<div id="host"><div id="slotted"></div></div>
+<script>
+test(function() {
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      :host ::slotted(div) { width: 100px; height: 100px; background: red; }
+      :host ::slotted(.foo) { background: green; }
+    </style>
+    <slot></slot>
+  `;
+  assert_equals(getComputedStyle(slotted).backgroundColor, "rgb(255, 0, 0)");
+  host.firstElementChild.classList.add('foo');
+  assert_equals(getComputedStyle(slotted).backgroundColor, "rgb(0, 128, 0)");
+});
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-multiple-001.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>CSS Test: :host multiple times in the same compound selector.</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"></div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      :host:host { width: 100px; height: 100px; background: green }
+    </style>
+  `;
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-nested-001.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<title>CSS Test: :host doesn't match nested shadow hosts.</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<style>
+  #host {
+    width: 100px;
+    height: 100px;
+  }
+</style>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host">FAIL</div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      div {
+        background: green;
+        width: 100px;
+        height: 100px;
+      }
+      :host { background: red !important }
+      div:host { background: red !important }
+    </style>
+    <div id="nested-host">FAIL - nested shadow host</div>
+  `;
+  root.getElementById("nested-host").attachShadow({ mode: "open" });
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-slotted-001.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<title>CSS Test: :host matches while collecting ::slotted rules</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"><div></div></div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      ::slotted(div) { width: 100px; height: 100px; background: red; }
+      :host ::slotted(div) { background: green; }
+    </style>
+    <slot></slot>
+  `;
+</script>