diff options
author | Emilio Cobos Álvarez <emilio@crisal.io> | 2019-05-01 17:25:13 +0000 |
---|---|---|
committer | Emilio Cobos Álvarez <emilio@crisal.io> | 2019-05-07 12:55:44 +0200 |
commit | a23ad3be501723e14a97bfb3d0f1c2b6ec21cdab (patch) | |
tree | af2cfe9589c5fb020b76eb9424d7b05b3548b2af | |
parent | 9f73576f6a61b3f1866d5eb509b849f6ad5aea36 (diff) | |
download | servo-a23ad3be501723e14a97bfb3d0f1c2b6ec21cdab.tar.gz servo-a23ad3be501723e14a97bfb3d0f1c2b6ec21cdab.zip |
style: Add parsing support for ::part().
Disabled for now of course. This should be pretty uncontroversial I'd think.
Differential Revision: https://phabricator.services.mozilla.com/D28060
-rw-r--r-- | components/malloc_size_of/lib.rs | 1 | ||||
-rw-r--r-- | components/selectors/builder.rs | 1 | ||||
-rw-r--r-- | components/selectors/matching.rs | 3 | ||||
-rw-r--r-- | components/selectors/parser.rs | 95 | ||||
-rw-r--r-- | components/selectors/tree.rs | 5 | ||||
-rw-r--r-- | components/style/gecko/selector_parser.rs | 1 | ||||
-rw-r--r-- | components/style/gecko/wrapper.rs | 4 | ||||
-rw-r--r-- | components/style/invalidation/element/element_wrapper.rs | 4 | ||||
-rw-r--r-- | components/style/invalidation/element/invalidation_map.rs | 1 | ||||
-rw-r--r-- | components/style/invalidation/element/invalidator.rs | 8 |
10 files changed, 106 insertions, 17 deletions
diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index b59e26391ef..a305f86f7cb 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -747,6 +747,7 @@ where Component::ExplicitUniversalType | Component::LocalName(..) | Component::ID(..) | + Component::Part(..) | Component::Class(..) | Component::AttributeInNoNamespaceExists { .. } | Component::AttributeInNoNamespace { .. } | diff --git a/components/selectors/builder.rs b/components/selectors/builder.rs index 72404de1084..73c1575371c 100644 --- a/components/selectors/builder.rs +++ b/components/selectors/builder.rs @@ -270,6 +270,7 @@ where Component::Combinator(..) => { unreachable!("Found combinator in simple selectors vector?"); }, + Component::Part(..) | Component::PseudoElement(..) | Component::LocalName(..) => { specificity.element_selectors += 1 }, diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index f50dc8ddecf..aa200cc4b26 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -450,6 +450,7 @@ where element.containing_shadow_host() }, + Combinator::Part => element.containing_shadow_host(), Combinator::SlotAssignment => { debug_assert!( context.current_host.is_some(), @@ -517,6 +518,7 @@ where Combinator::Child | Combinator::Descendant | Combinator::SlotAssignment | + Combinator::Part | Combinator::PseudoElement => SelectorMatchingResult::NotMatchedGlobally, }; @@ -671,6 +673,7 @@ where 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() && diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index 21459e4e46d..45cbb56236f 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -68,24 +68,30 @@ bitflags! { /// 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; } } @@ -100,6 +106,14 @@ impl SelectorParsingState { !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) @@ -156,6 +170,7 @@ macro_rules! with_all_bounds { 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; @@ -196,6 +211,11 @@ pub trait Parser<'i> { 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 @@ -841,6 +861,9 @@ pub enum Combinator { /// 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 { @@ -934,8 +957,7 @@ pub enum Component<Impl: SelectorImpl> { 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 /// @@ -947,6 +969,9 @@ pub enum Component<Impl: SelectorImpl> { /// /// 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 @@ -1196,7 +1221,8 @@ impl ToCss for Combinator { 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(()), } } @@ -1236,6 +1262,11 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> { 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('#')?; @@ -1407,15 +1438,12 @@ where { 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 @@ -1425,7 +1453,11 @@ where }, }; - 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; } @@ -1463,6 +1495,8 @@ where 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))) } @@ -1553,6 +1587,7 @@ enum SimpleSelectorParseResult<Impl: SelectorImpl> { SimpleSelector(Component<Impl>), PseudoElement(Impl::PseudoElement), SlottedPseudo(Selector<Impl>), + PartPseudo(Impl::PartName), } #[derive(Debug)] @@ -1899,6 +1934,7 @@ where 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)); @@ -1955,6 +1991,11 @@ where 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); if !builder.is_empty() { @@ -2115,6 +2156,15 @@ where 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)); @@ -2298,6 +2348,7 @@ pub mod tests { type AttrValue = DummyAtom; type Identifier = DummyAtom; type ClassName = DummyAtom; + type PartName = DummyAtom; type LocalName = DummyAtom; type NamespaceUrl = DummyAtom; type NamespacePrefix = DummyAtom; @@ -2336,6 +2387,10 @@ pub mod tests { true } + fn parse_part(&self) -> bool { + true + } + fn parse_non_ts_pseudo_class( &self, location: SourceLocation, @@ -2910,6 +2965,14 @@ pub mod tests { 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()); diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs index badfca86ed6..b57876308a5 100644 --- a/components/selectors/tree.rs +++ b/components/selectors/tree.rs @@ -110,6 +110,11 @@ pub trait Element: Sized + Clone + Debug { 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. diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index ec0db9286ec..18718446c1a 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -289,6 +289,7 @@ impl ::selectors::SelectorImpl for SelectorImpl { type AttrValue = Atom; type Identifier = Atom; type ClassName = Atom; + type PartName = Atom; type LocalName = Atom; type NamespacePrefix = Atom; type NamespaceUrl = Namespace; diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 079dde2f513..f88e30ab777 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -2259,6 +2259,10 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { 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() { diff --git a/components/style/invalidation/element/element_wrapper.rs b/components/style/invalidation/element/element_wrapper.rs index c794eb15c2c..114cdc9cd8a 100644 --- a/components/style/invalidation/element/element_wrapper.rs +++ b/components/style/invalidation/element/element_wrapper.rs @@ -340,6 +340,10 @@ where } } + 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), diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs index e4cc8115760..63d6eb6acc4 100644 --- a/components/style/invalidation/element/invalidation_map.rs +++ b/components/style/invalidation/element/invalidation_map.rs @@ -98,6 +98,7 @@ impl Dependency { // 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"), } } } diff --git a/components/style/invalidation/element/invalidator.rs b/components/style/invalidation/element/invalidator.rs index 371c0771183..ec1548f4aa3 100644 --- a/components/style/invalidation/element/invalidator.rs +++ b/components/style/invalidation/element/invalidator.rs @@ -158,7 +158,10 @@ impl<'a> Invalidation<'a> { // 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, } } @@ -171,6 +174,9 @@ impl<'a> Invalidation<'a> { 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) }, |