Bug 1545425 - Add parsing support for ::part(). r=heycam
authorEmilio Cobos Álvarez <emilio@crisal.io>
Wed, 01 May 2019 17:25:13 +0000
changeset 530953 63c513e31abb4f4ed16154af0f38d734d3c0764a
parent 530952 8c89fd37b79d353a6eb8794f3de11151f6924b26
child 530954 5cac03a223e560c730e9e1e58ad4a6f93ebba4da
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1545425
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1545425 - Add parsing support for ::part(). r=heycam Disabled for now of course. This should be pretty uncontroversial I'd think. Differential Revision: https://phabricator.services.mozilla.com/D28060
servo/components/malloc_size_of/lib.rs
servo/components/selectors/builder.rs
servo/components/selectors/matching.rs
servo/components/selectors/parser.rs
servo/components/selectors/tree.rs
servo/components/style/gecko/selector_parser.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/invalidation/element/element_wrapper.rs
servo/components/style/invalidation/element/invalidation_map.rs
servo/components/style/invalidation/element/invalidator.rs
--- a/servo/components/malloc_size_of/lib.rs
+++ b/servo/components/malloc_size_of/lib.rs
@@ -744,16 +744,17 @@ where
             Component::Combinator(..) |
             Component::ExplicitAnyNamespace |
             Component::ExplicitNoNamespace |
             Component::DefaultNamespace(..) |
             Component::Namespace(..) |
             Component::ExplicitUniversalType |
             Component::LocalName(..) |
             Component::ID(..) |
+            Component::Part(..) |
             Component::Class(..) |
             Component::AttributeInNoNamespaceExists { .. } |
             Component::AttributeInNoNamespace { .. } |
             Component::FirstChild |
             Component::LastChild |
             Component::OnlyChild |
             Component::Root |
             Component::Empty |
--- a/servo/components/selectors/builder.rs
+++ b/servo/components/selectors/builder.rs
@@ -259,16 +259,17 @@ where
         specificity: &mut Specificity,
     ) where
         Impl: SelectorImpl,
     {
         match *simple_selector {
             Component::Combinator(..) => {
                 unreachable!("Found combinator in simple selectors vector?");
             },
+            Component::Part(..) |
             Component::PseudoElement(..) | Component::LocalName(..) => {
                 specificity.element_selectors += 1
             },
             Component::Slotted(ref selector) => {
                 specificity.element_selectors += 1;
                 // Note that due to the way ::slotted works we only compete with
                 // other ::slotted rules, so the above rule doesn't really
                 // matter, but we do it still for consistency with other
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -446,16 +446,17 @@ where
             // 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::Part => element.containing_shadow_host(),
         Combinator::SlotAssignment => {
             debug_assert!(
                 context.current_host.is_some(),
                 "Should not be trying to match slotted rules in a non-shadow-tree context"
             );
             debug_assert!(element
                 .assigned_slot()
                 .map_or(true, |s| s.is_html_slot_element()));
@@ -519,16 +520,17 @@ where
 
     let candidate_not_found = match combinator {
         Combinator::NextSibling | Combinator::LaterSibling => {
             SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant
         },
         Combinator::Child |
         Combinator::Descendant |
         Combinator::SlotAssignment |
+        Combinator::Part |
         Combinator::PseudoElement => SelectorMatchingResult::NotMatchedGlobally,
     };
 
     let mut next_element =
         next_element_for_combinator(element, combinator, &selector_iter, &context);
 
     // Stop matching :visited as soon as we find a link, or a combinator for
     // something that isn't an ancestor.
@@ -673,16 +675,17 @@ fn matches_simple_selector<E, F>(
 where
     E: Element,
     F: FnMut(&E, ElementSelectorFlags),
 {
     debug_assert!(context.shared.is_nested() || !context.shared.in_negation());
 
     match *selector {
         Component::Combinator(_) => unreachable!(),
+        Component::Part(ref part) => element.is_part(part),
         Component::Slotted(ref selector) => {
             // <slots> are never flattened tree slottables.
             !element.is_html_slot_element() &&
                 context.shared.nest(|context| {
                     matches_complex_selector(selector.iter(), element, context, flags_setter)
                 })
         },
         Component::PseudoElement(ref pseudo) => {
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -63,48 +63,62 @@ fn to_ascii_lowercase(s: &str) -> Cow<st
 }
 
 bitflags! {
     /// Flags that indicate at which point of parsing a selector are we.
     struct SelectorParsingState: u8 {
         /// Whether we're inside a negation. If we're inside a negation, we're
         /// not allowed to add another negation or such, for example.
         const INSIDE_NEGATION = 1 << 0;
-        /// Whether we've parsed an ::slotted() pseudo-element already.
+        /// Whether we've parsed a ::slotted() pseudo-element already.
         ///
         /// If so, then we can only parse a subset of pseudo-elements, and
         /// whatever comes after them if so.
         const AFTER_SLOTTED = 1 << 1;
+        /// Whether we've parsed a ::part() pseudo-element already.
+        ///
+        /// If so, then we can only parse a subset of pseudo-elements, and
+        /// whatever comes after them if so.
+        const AFTER_PART = 1 << 2;
         /// Whether we've parsed a pseudo-element (as in, an
-        /// `Impl::PseudoElement` thus not accounting for `::slotted`) already.
+        /// `Impl::PseudoElement` thus not accounting for `::slotted` or
+        /// `::part`) already.
         ///
         /// If so, then other pseudo-elements and most other selectors are
         /// disallowed.
-        const AFTER_PSEUDO_ELEMENT = 1 << 2;
+        const AFTER_PSEUDO_ELEMENT = 1 << 3;
         /// Whether we've parsed a non-stateful pseudo-element (again, as-in
         /// `Impl::PseudoElement`) already. If so, then other pseudo-classes are
         /// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set
         /// as well.
-        const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 3;
+        const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4;
         /// Whether we are after any of the pseudo-like things.
-        const AFTER_PSEUDO = Self::AFTER_SLOTTED.bits | Self::AFTER_PSEUDO_ELEMENT.bits;
+        const AFTER_PSEUDO = Self::AFTER_PART.bits | Self::AFTER_SLOTTED.bits | Self::AFTER_PSEUDO_ELEMENT.bits;
     }
 }
 
 impl SelectorParsingState {
     #[inline]
     fn allows_functional_pseudo_classes(self) -> bool {
         !self.intersects(SelectorParsingState::AFTER_PSEUDO)
     }
 
     #[inline]
     fn allows_slotted(self) -> bool {
         !self.intersects(SelectorParsingState::AFTER_PSEUDO)
     }
 
+    // TODO(emilio): Should we allow other ::part()s after ::part()?
+    //
+    // See https://github.com/w3c/csswg-drafts/issues/3841
+    #[inline]
+    fn allows_part(self) -> bool {
+        !self.intersects(SelectorParsingState::AFTER_PSEUDO)
+    }
+
     #[inline]
     fn allows_non_functional_pseudo_classes(self) -> bool {
         !self.intersects(SelectorParsingState::AFTER_SLOTTED | SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT)
     }
 
     #[inline]
     fn allows_tree_structural_pseudo_classes(self) -> bool {
         !self.intersects(SelectorParsingState::AFTER_PSEUDO)
@@ -151,16 +165,17 @@ macro_rules! with_all_bounds {
         /// NB: We need Clone so that we can derive(Clone) on struct with that
         /// are parameterized on SelectorImpl. See
         /// <https://github.com/rust-lang/rust/issues/26925>
         pub trait SelectorImpl: Clone + Debug + Sized + 'static {
             type ExtraMatchingData: Sized + Default + 'static;
             type AttrValue: $($InSelector)*;
             type Identifier: $($InSelector)*;
             type ClassName: $($InSelector)*;
+            type PartName: $($InSelector)*;
             type LocalName: $($InSelector)* + Borrow<Self::BorrowedLocalName>;
             type NamespaceUrl: $($CommonBounds)* + Default + Borrow<Self::BorrowedNamespaceUrl>;
             type NamespacePrefix: $($InSelector)* + Default;
             type BorrowedNamespaceUrl: ?Sized + Eq;
             type BorrowedLocalName: ?Sized + Eq;
 
             /// non tree-structural pseudo-classes
             /// (see: https://drafts.csswg.org/selectors/#structural-pseudos)
@@ -191,16 +206,21 @@ pub trait Parser<'i> {
     type Impl: SelectorImpl;
     type Error: 'i + From<SelectorParseErrorKind<'i>>;
 
     /// Whether to parse the `::slotted()` pseudo-element.
     fn parse_slotted(&self) -> bool {
         false
     }
 
+    /// Whether to parse the `::part()` pseudo-element.
+    fn parse_part(&self) -> bool {
+        false
+    }
+
     /// Whether to parse the `:host` pseudo-class.
     fn parse_host(&self) -> bool {
         false
     }
 
     /// This function can return an "Err" pseudo-element in order to support CSS2.1
     /// pseudo-elements.
     fn parse_non_ts_pseudo_class(
@@ -836,16 +856,19 @@ pub enum Combinator {
     /// It serializes as the empty string, and acts effectively as a child
     /// combinator in most cases.  If we ever actually start using a child
     /// combinator for this, we will need to fix up the way hashes are computed
     /// for revalidation selectors.
     PseudoElement,
     /// Another combinator used for ::slotted(), which represent the jump from
     /// a node to its assigned slot.
     SlotAssignment,
+    /// Another combinator used for `::part()`, which represents the jump from
+    /// the part to the containing shadow host.
+    Part,
 }
 
 impl Combinator {
     /// Returns true if this combinator is a child or descendant combinator.
     #[inline]
     pub fn is_ancestor(&self) -> bool {
         matches!(
             *self,
@@ -929,29 +952,31 @@ pub enum Component<Impl: SelectorImpl> {
     NthChild(i32, i32),
     NthLastChild(i32, i32),
     NthOfType(i32, i32),
     NthLastOfType(i32, i32),
     FirstOfType,
     LastOfType,
     OnlyOfType,
     NonTSPseudoClass(#[shmem(field_bound)] Impl::NonTSPseudoClass),
-    /// The ::slotted() pseudo-element (which isn't actually a pseudo-element,
-    /// and probably should be a pseudo-class):
+    /// The ::slotted() pseudo-element:
     ///
     /// https://drafts.csswg.org/css-scoping/#slotted-pseudo
     ///
     /// The selector here is a compound selector, that is, no combinators.
     ///
     /// NOTE(emilio): This should support a list of selectors, but as of this
     /// writing no other browser does, and that allows them to put ::slotted()
     /// in the rule hash, so we do that too.
     ///
     /// See https://github.com/w3c/csswg-drafts/issues/2158
     Slotted(Selector<Impl>),
+    /// The `::part` pseudo-element.
+    ///   https://drafts.csswg.org/css-shadow-parts/#part
+    Part(#[shmem(field_bound)] Impl::PartName),
     /// The `:host` pseudo-class:
     ///
     /// https://drafts.csswg.org/css-scoping/#host-selector
     ///
     /// NOTE(emilio): This should support a list of selectors, but as of this
     /// writing no other browser does, and that allows them to put :host()
     /// in the rule hash, so we do that too.
     ///
@@ -1191,17 +1216,18 @@ impl ToCss for Combinator {
     where
         W: fmt::Write,
     {
         match *self {
             Combinator::Child => dest.write_str(" > "),
             Combinator::Descendant => dest.write_str(" "),
             Combinator::NextSibling => dest.write_str(" + "),
             Combinator::LaterSibling => dest.write_str(" ~ "),
-            Combinator::PseudoElement => Ok(()),
+            Combinator::PseudoElement |
+            Combinator::Part |
             Combinator::SlotAssignment => Ok(()),
         }
     }
 }
 
 impl<Impl: SelectorImpl> ToCss for Component<Impl> {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
     where
@@ -1231,16 +1257,21 @@ impl<Impl: SelectorImpl> ToCss for Compo
 
         match *self {
             Combinator(ref c) => c.to_css(dest),
             Slotted(ref selector) => {
                 dest.write_str("::slotted(")?;
                 selector.to_css(dest)?;
                 dest.write_char(')')
             },
+            Part(ref part_name) => {
+                dest.write_str("::part(")?;
+                display_to_css_identifier(part_name, dest)?;
+                dest.write_char(')')
+            },
             PseudoElement(ref p) => p.to_css(dest),
             ID(ref s) => {
                 dest.write_char('#')?;
                 display_to_css_identifier(s, dest)
             },
             Class(ref s) => {
                 dest.write_char('.')?;
                 display_to_css_identifier(s, dest)
@@ -1402,35 +1433,36 @@ fn parse_selector<'i, 't, P, Impl>(
     input: &mut CssParser<'i, 't>,
 ) -> Result<Selector<Impl>, ParseError<'i, P::Error>>
 where
     P: Parser<'i, Impl = Impl>,
     Impl: SelectorImpl,
 {
     let mut builder = SelectorBuilder::default();
 
-    let mut has_pseudo_element;
-    let mut slotted;
+    let mut has_pseudo_element = false;
+    let mut slotted = false;
     'outer_loop: loop {
         // Parse a sequence of simple selectors.
-        match parse_compound_selector(parser, input, &mut builder)? {
-            Some(state) => {
-                has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
-                slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED);
-            },
+        let state = match parse_compound_selector(parser, input, &mut builder)? {
+            Some(state) => state,
             None => {
                 return Err(input.new_custom_error(if builder.has_combinators() {
                     SelectorParseErrorKind::DanglingCombinator
                 } else {
                     SelectorParseErrorKind::EmptySelector
                 }));
             },
         };
 
-        if has_pseudo_element || slotted {
+        if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
+            has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
+            slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED);
+            let part = state.intersects(SelectorParsingState::AFTER_PART);
+            debug_assert!(has_pseudo_element || slotted || part);
             break;
         }
 
         // Parse a combinator.
         let combinator;
         let mut any_whitespace = false;
         loop {
             let before_this_token = input.state();
@@ -1458,16 +1490,18 @@ where
                         break 'outer_loop;
                     }
                 },
             }
         }
         builder.push_combinator(combinator);
     }
 
+    // TODO(emilio): We'll have to flag part() somehow as well, but we need more
+    // bits!
     Ok(Selector(builder.build(has_pseudo_element, slotted)))
 }
 
 impl<Impl: SelectorImpl> Selector<Impl> {
     /// Parse a selector, without any pseudo-element.
     #[inline]
     pub fn parse<'i, 't, P>(
         parser: &P,
@@ -1548,16 +1582,17 @@ where
     }
 }
 
 #[derive(Debug)]
 enum SimpleSelectorParseResult<Impl: SelectorImpl> {
     SimpleSelector(Component<Impl>),
     PseudoElement(Impl::PseudoElement),
     SlottedPseudo(Selector<Impl>),
+    PartPseudo(Impl::PartName),
 }
 
 #[derive(Debug)]
 enum QNamePrefix<Impl: SelectorImpl> {
     ImplicitNoNamespace,                          // `foo` in attr selectors
     ImplicitAnyNamespace,                         // `foo` in type selectors, without a default ns
     ImplicitDefaultNamespace(Impl::NamespaceUrl), // `foo` in type selectors, with a default ns
     ExplicitNoNamespace,                          // `|foo`
@@ -1894,16 +1929,17 @@ where
         match parse_one_simple_selector(parser, input, SelectorParsingState::INSIDE_NEGATION)? {
             Some(SimpleSelectorParseResult::SimpleSelector(s)) => {
                 sequence.push(s);
             },
             None => {
                 return Err(input.new_custom_error(SelectorParseErrorKind::EmptyNegation));
             },
             Some(SimpleSelectorParseResult::PseudoElement(_)) |
+            Some(SimpleSelectorParseResult::PartPseudo(_)) |
             Some(SimpleSelectorParseResult::SlottedPseudo(_)) => {
                 let e = SelectorParseErrorKind::NonSimpleSelectorInNegation;
                 return Err(input.new_custom_error(e));
             },
         }
     }
 
     // Success.
@@ -1950,16 +1986,21 @@ where
             };
 
         empty = false;
 
         match parse_result {
             SimpleSelectorParseResult::SimpleSelector(s) => {
                 builder.push_simple_selector(s);
             },
+            SimpleSelectorParseResult::PartPseudo(part_name) => {
+                state.insert(SelectorParsingState::AFTER_PART);
+                builder.push_combinator(Combinator::Part);
+                builder.push_simple_selector(Component::Part(part_name));
+            },
             SimpleSelectorParseResult::SlottedPseudo(selector) => {
                 state.insert(SelectorParsingState::AFTER_SLOTTED);
                 builder.push_combinator(Combinator::SlotAssignment);
                 builder.push_simple_selector(Component::Slotted(selector));
             },
             SimpleSelectorParseResult::PseudoElement(p) => {
                 state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
                 if !p.accepts_state_pseudo_classes() {
@@ -2106,16 +2147,25 @@ where
             };
             let is_pseudo_element =
                 !is_single_colon || is_css2_pseudo_element(&name);
             if is_pseudo_element {
                 if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) {
                     return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
                 }
                 let pseudo_element = if is_functional {
+                    if P::parse_part(parser) && name.eq_ignore_ascii_case("part") {
+                        if !state.allows_part() {
+                            return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
+                        }
+                        let name = input.parse_nested_block(|input| {
+                            Ok(input.expect_ident()?.as_ref().into())
+                        })?;
+                        return Ok(Some(SimpleSelectorParseResult::PartPseudo(name)));
+                    }
                     if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") {
                         if !state.allows_slotted() {
                             return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
                         }
                         let selector = input.parse_nested_block(|input| {
                             parse_inner_compound_selector(parser, input)
                         })?;
                         return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector)));
@@ -2289,16 +2339,17 @@ pub mod tests {
         }
     }
 
     impl SelectorImpl for DummySelectorImpl {
         type ExtraMatchingData = ();
         type AttrValue = DummyAtom;
         type Identifier = DummyAtom;
         type ClassName = DummyAtom;
+        type PartName = DummyAtom;
         type LocalName = DummyAtom;
         type NamespaceUrl = DummyAtom;
         type NamespacePrefix = DummyAtom;
         type BorrowedLocalName = DummyAtom;
         type BorrowedNamespaceUrl = DummyAtom;
         type NonTSPseudoClass = PseudoClass;
         type PseudoElement = PseudoElement;
     }
@@ -2327,16 +2378,20 @@ pub mod tests {
     impl<'i> Parser<'i> for DummyParser {
         type Impl = DummySelectorImpl;
         type Error = SelectorParseErrorKind<'i>;
 
         fn parse_slotted(&self) -> bool {
             true
         }
 
+        fn parse_part(&self) -> bool {
+            true
+        }
+
         fn parse_non_ts_pseudo_class(
             &self,
             location: SourceLocation,
             name: CowRcStr<'i>,
         ) -> Result<PseudoClass, SelectorParseError<'i>> {
             match_ignore_ascii_case! { &name,
                 "hover" => return Ok(PseudoClass::Hover),
                 "active" => return Ok(PseudoClass::Active),
@@ -2907,16 +2962,24 @@ pub mod tests {
             )]))
         );
 
         assert!(parse("::slotted()").is_err());
         assert!(parse("::slotted(div)").is_ok());
         assert!(parse("::slotted(div).foo").is_err());
         assert!(parse("::slotted(div + bar)").is_err());
         assert!(parse("::slotted(div) + foo").is_err());
+
+        assert!(parse("::part()").is_err());
+        assert!(parse("::part(42)").is_err());
+        // Though note https://github.com/w3c/csswg-drafts/issues/3502
+        assert!(parse("::part(foo bar)").is_err());
+        assert!(parse("::part(foo):hover").is_ok());
+        assert!(parse("::part(foo) + bar").is_err());
+
         assert!(parse("div ::slotted(div)").is_ok());
         assert!(parse("div + slot::slotted(div)").is_ok());
         assert!(parse("div + slot::slotted(div.foo)").is_ok());
         assert!(parse("slot::slotted(div,foo)::first-line").is_err());
         assert!(parse("::slotted(div)::before").is_ok());
         assert!(parse("slot::slotted(div,foo)").is_err());
     }
 
--- a/servo/components/selectors/tree.rs
+++ b/servo/components/selectors/tree.rs
@@ -109,16 +109,21 @@ pub trait Element: Sized + Clone + Debug
     ) -> bool;
 
     fn has_class(
         &self,
         name: &<Self::Impl as SelectorImpl>::ClassName,
         case_sensitivity: CaseSensitivity,
     ) -> bool;
 
+    fn is_part(
+        &self,
+        name: &<Self::Impl as SelectorImpl>::PartName,
+    ) -> bool;
+
     /// Returns whether this element matches `:empty`.
     ///
     /// That is, whether it does not contain any child element or any non-zero-length text node.
     /// See http://dev.w3.org/csswg/selectors-3/#empty-pseudo
     fn is_empty(&self) -> bool;
 
     /// Returns whether this element matches `:root`,
     /// i.e. whether it is the root element of a document.
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -284,16 +284,17 @@ impl ::selectors::parser::NonTSPseudoCla
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct SelectorImpl;
 
 impl ::selectors::SelectorImpl for SelectorImpl {
     type ExtraMatchingData = InvalidationMatchingData;
     type AttrValue = Atom;
     type Identifier = Atom;
     type ClassName = Atom;
+    type PartName = Atom;
     type LocalName = Atom;
     type NamespacePrefix = Atom;
     type NamespaceUrl = Namespace;
     type BorrowedNamespaceUrl = WeakNamespace;
     type BorrowedLocalName = WeakAtom;
 
     type PseudoElement = PseudoElement;
     type NonTSPseudoClass = NonTSPseudoClass;
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -2259,16 +2259,20 @@ impl<'le> ::selectors::Element for Gecko
         let element_id = match snapshot_helpers::get_id(self.attrs()) {
             Some(id) => id,
             None => return false,
         };
 
         case_sensitivity.eq_atom(element_id, id)
     }
 
+    fn is_part(&self, _name: &Atom) -> bool {
+        unimplemented!();
+    }
+
     #[inline(always)]
     fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
         let attr = match self.get_class_attr() {
             Some(c) => c,
             None => return false,
         };
 
         snapshot_helpers::has_class(name, case_sensitivity, attr)
--- a/servo/components/style/invalidation/element/element_wrapper.rs
+++ b/servo/components/style/invalidation/element/element_wrapper.rs
@@ -335,16 +335,20 @@ where
         match self.snapshot() {
             Some(snapshot) if snapshot.has_attrs() => snapshot
                 .id_attr()
                 .map_or(false, |atom| case_sensitivity.eq_atom(&atom, id)),
             _ => self.element.has_id(id, case_sensitivity),
         }
     }
 
+    fn is_part(&self, _name: &Atom) -> bool {
+        unimplemented!();
+    }
+
     fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
         match self.snapshot() {
             Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity),
             _ => self.element.has_class(name, case_sensitivity),
         }
     }
 
     fn is_empty(&self) -> bool {
--- a/servo/components/style/invalidation/element/invalidation_map.rs
+++ b/servo/components/style/invalidation/element/invalidation_map.rs
@@ -93,16 +93,17 @@ impl Dependency {
             },
             Some(Combinator::LaterSibling) | Some(Combinator::NextSibling) => {
                 DependencyInvalidationKind::Siblings
             },
             // TODO(emilio): We could look at the selector itself to see if it's
             // an eager pseudo, and return only Descendants here if not.
             Some(Combinator::PseudoElement) => DependencyInvalidationKind::ElementAndDescendants,
             Some(Combinator::SlotAssignment) => DependencyInvalidationKind::SlottedElements,
+            Some(Combinator::Part) => unimplemented!("Need to add invalidation for shadow parts"),
         }
     }
 }
 
 impl SelectorMapEntry for Dependency {
     fn selector(&self) -> SelectorIter<SelectorImpl> {
         self.selector.iter_from(self.selector_offset)
     }
--- a/servo/components/style/invalidation/element/invalidator.rs
+++ b/servo/components/style/invalidation/element/invalidator.rs
@@ -153,29 +153,35 @@ impl<'a> Invalidation<'a> {
         }
 
         // TODO(emilio): For pseudo-elements this should be mostly false, except
         // for the weird pseudos in <input type="number">.
         //
         // We should be able to do better here!
         match self.selector.combinator_at_parse_order(self.offset - 1) {
             Combinator::Descendant | Combinator::LaterSibling | Combinator::PseudoElement => true,
-            Combinator::SlotAssignment | Combinator::NextSibling | Combinator::Child => false,
+            Combinator::Part |
+            Combinator::SlotAssignment |
+            Combinator::NextSibling |
+            Combinator::Child => false,
         }
     }
 
     fn kind(&self) -> InvalidationKind {
         if self.offset == 0 {
             return InvalidationKind::Descendant(DescendantInvalidationKind::Dom);
         }
 
         match self.selector.combinator_at_parse_order(self.offset - 1) {
             Combinator::Child | Combinator::Descendant | Combinator::PseudoElement => {
                 InvalidationKind::Descendant(DescendantInvalidationKind::Dom)
             },
+            Combinator::Part => {
+                unimplemented!("Need to add invalidation for shadow parts");
+            },
             Combinator::SlotAssignment => {
                 InvalidationKind::Descendant(DescendantInvalidationKind::Slotted)
             },
             Combinator::NextSibling | Combinator::LaterSibling => InvalidationKind::Sibling,
         }
     }
 }