diff options
author | Emilio Cobos Álvarez <emilio@crisal.io> | 2017-12-10 14:23:27 +0100 |
---|---|---|
committer | Emilio Cobos Álvarez <emilio@crisal.io> | 2017-12-14 06:28:11 +0100 |
commit | 7886e033aa7e813a9ed3becc456ee9ed059364cc (patch) | |
tree | 6ff62f4ec8593d29ef3e5fba392ee07304cb8b5c /components/selectors/parser.rs | |
parent | 0fa605d2439c4f640b7f180c18d768a7a14e7f1e (diff) | |
download | servo-7886e033aa7e813a9ed3becc456ee9ed059364cc.tar.gz servo-7886e033aa7e813a9ed3becc456ee9ed059364cc.zip |
selectors: Add parsing support for ::slotted().
Without turning it on yet, of course.
The reason why I didn't use the general PseudoElement mechanism is because this
pseudo is a bit of its own thing, and I found easier to make ::selectors know
about it (because you need to jump to the assigned slot) than the other way
around.
Also, we need to support ::slotted(..)::before and such, and supporting multiple
pseudo-elements like that breaks some other invariants around the SelectorMap,
and fixing those would require special-casing slotted a lot more in other parts
of the code.
Let me know if you think otherwise.
I also don't like much the boolean tuple return value, but I plan to do some
cleanup in the area in a bit, so it should go away soon, I'd hope.
Diffstat (limited to 'components/selectors/parser.rs')
-rw-r--r-- | components/selectors/parser.rs | 174 |
1 files changed, 135 insertions, 39 deletions
diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index cf84648760b..91417a8cf62 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -132,6 +132,11 @@ pub trait Parser<'i> { is_css2_pseudo_element(name) } + /// Whether to parse the `::slotted()` pseudo-element. + fn parse_slotted(&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( @@ -353,6 +358,13 @@ impl<Impl: SelectorImpl> SelectorMethods for Component<Impl> { } match *self { + Slotted(ref selectors) => { + for selector in selectors.iter() { + if !selector.visit(visitor) { + return false; + } + } + } Negation(ref negated) => { for component in negated.iter() { if !component.visit(visitor) { @@ -658,15 +670,20 @@ pub enum Combinator { /// 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, } impl Combinator { /// Returns true if this combinator is a child or descendant combinator. #[inline] pub fn is_ancestor(&self) -> bool { - matches!(*self, Combinator::Child | - Combinator::Descendant | - Combinator::PseudoElement) + matches!(*self, + Combinator::Child | + Combinator::Descendant | + Combinator::PseudoElement | + Combinator::SlotAssignment) } /// Returns true if this combinator is a pseudo-element combinator. @@ -716,16 +733,16 @@ pub enum Component<Impl: SelectorImpl> { // Use a Box in the less common cases with more data to keep size_of::<Component>() small. AttributeOther(Box<AttrSelectorWithNamespace<Impl>>), - // Pseudo-classes - // - // CSS3 Negation only takes a simple simple selector, but we still need to - // treat it as a compound selector because it might be a type selector which - // we represent as a namespace and a localname. - // - // Note: if/when we upgrade this to CSS4, which supports combinators, we - // need to think about how this should interact with visit_complex_selector, - // and what the consumers of those APIs should do about the presence of - // combinators in negation. + /// Pseudo-classes + /// + /// CSS3 Negation only takes a simple simple selector, but we still need to + /// treat it as a compound selector because it might be a type selector + /// which we represent as a namespace and a localname. + /// + /// Note: if/when we upgrade this to CSS4, which supports combinators, we + /// need to think about how this should interact with + /// visit_complex_selector, and what the consumers of those APIs should do + /// about the presence of combinators in negation. Negation(Box<[Component<Impl>]>), FirstChild, LastChild, OnlyChild, Root, @@ -739,6 +756,13 @@ pub enum Component<Impl: SelectorImpl> { LastOfType, OnlyOfType, NonTSPseudoClass(Impl::NonTSPseudoClass), + /// The ::slotted() pseudo-element (which isn't actually a pseudo-element, + /// and probably should be a pseudo-class): + /// + /// https://drafts.csswg.org/css-scoping/#slotted-pseudo + /// + /// The selectors here are compound selectors, that is, no combinators. + Slotted(Box<[Selector<Impl>]>), PseudoElement(Impl::PseudoElement), } @@ -868,13 +892,15 @@ impl<Impl: SelectorImpl> ToCss for Selector<Impl> { let mut perform_step_2 = true; if first_non_namespace == compound.len() - 1 { match (combinators.peek(), &compound[first_non_namespace]) { - // We have to be careful here, because if there is a pseudo - // element "combinator" there isn't really just the one - // simple selector. Technically this compound selector - // contains the pseudo element selector as - // well--Combinator::PseudoElement doesn't exist in the + // We have to be careful here, because if there is a + // pseudo element "combinator" there isn't really just + // the one simple selector. Technically this compound + // selector contains the pseudo element selector as well + // -- Combinator::PseudoElement, just like + // Combinator::SlotAssignment, don't exist in the // spec. - (Some(&&Component::Combinator(Combinator::PseudoElement)), _) => (), + (Some(&&Component::Combinator(Combinator::PseudoElement)), _) | + (Some(&&Component::Combinator(Combinator::SlotAssignment)), _) => (), (_, &Component::ExplicitUniversalType) => { // Iterate over everything so we serialize the namespace // too. @@ -942,6 +968,7 @@ impl ToCss for Combinator { Combinator::NextSibling => dest.write_str(" + "), Combinator::LaterSibling => dest.write_str(" ~ "), Combinator::PseudoElement => Ok(()), + Combinator::SlotAssignment => Ok(()), } } } @@ -970,6 +997,16 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> { Combinator(ref c) => { c.to_css(dest) } + Slotted(ref selectors) => { + dest.write_str("::slotted(")?; + let mut iter = selectors.iter(); + iter.next().expect("At least one selector").to_css(dest)?; + for other in iter { + dest.write_str(", ")?; + other.to_css(dest)?; + } + dest.write_char(')') + } PseudoElement(ref p) => { p.to_css(dest) } @@ -1120,10 +1157,14 @@ where let mut builder = SelectorBuilder::default(); let mut has_pseudo_element; + let mut slotted; 'outer_loop: loop { // Parse a sequence of simple selectors. - has_pseudo_element = match parse_compound_selector(parser, input, &mut builder)? { - Some(has_pseudo_element) => has_pseudo_element, + match parse_compound_selector(parser, input, &mut builder)? { + Some((has_pseudo, slot)) => { + has_pseudo_element = has_pseudo; + slotted = slot; + } None => { return Err(input.new_custom_error(if builder.has_combinators() { SelectorParseErrorKind::DanglingCombinator @@ -1132,7 +1173,8 @@ where })) } }; - if has_pseudo_element { + + if has_pseudo_element || slotted { break; } @@ -1263,6 +1305,7 @@ where enum SimpleSelectorParseResult<Impl: SelectorImpl> { SimpleSelector(Component<Impl>), PseudoElement(Impl::PseudoElement), + SlottedPseudo(Box<[Selector<Impl>]>), } #[derive(Debug)] @@ -1575,7 +1618,8 @@ where None => { return Err(input.new_custom_error(SelectorParseErrorKind::EmptyNegation)); }, - Some(SimpleSelectorParseResult::PseudoElement(_)) => { + Some(SimpleSelectorParseResult::PseudoElement(_)) | + Some(SimpleSelectorParseResult::SlottedPseudo(_)) => { return Err(input.new_custom_error(SelectorParseErrorKind::NonSimpleSelectorInNegation)); } } @@ -1592,12 +1636,13 @@ where /// `Err(())` means invalid selector. /// `Ok(None)` is an empty selector /// -/// The boolean represent whether a pseudo-element has been parsed. +/// The booleans represent whether a pseudo-element has been parsed, and whether +/// ::slotted() has been parsed, respectively. fn parse_compound_selector<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, builder: &mut SelectorBuilder<Impl>, -) -> Result<Option<bool>, ParseError<'i, P::Error>> +) -> Result<Option<(bool, bool)>, ParseError<'i, P::Error>> where P: Parser<'i, Impl=Impl>, Impl: SelectorImpl, @@ -1605,6 +1650,7 @@ where input.skip_whitespace(); let mut empty = true; + let mut slot = false; if !parse_type_selector(parser, input, builder)? { if let Some(url) = parser.default_namespace() { // If there was no explicit type selector, but there is a @@ -1618,13 +1664,18 @@ where let mut pseudo = false; loop { - match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? { - None => break, - Some(SimpleSelectorParseResult::SimpleSelector(s)) => { + let parse_result = + match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? { + None => break, + Some(result) => result, + }; + + match parse_result { + SimpleSelectorParseResult::SimpleSelector(s) => { builder.push_simple_selector(s); empty = false } - Some(SimpleSelectorParseResult::PseudoElement(p)) => { + SimpleSelectorParseResult::PseudoElement(p) => { // Try to parse state to its right. There are only 3 allowable // state selectors that can go on pseudo-elements. let mut state_selectors = SmallVec::<[Component<Impl>; 3]>::new(); @@ -1673,13 +1724,25 @@ where empty = false; break } + SimpleSelectorParseResult::SlottedPseudo(selectors) => { + empty = false; + slot = true; + if !builder.is_empty() { + builder.push_combinator(Combinator::SlotAssignment); + } + builder.push_simple_selector(Component::Slotted(selectors)); + // FIXME(emilio): ::slotted() should support ::before and + // ::after after it, so we shouldn't break, but we shouldn't + // push more type selectors either. + break; + } } } if empty { // An empty selector is invalid. Ok(None) } else { - Ok(Some(pseudo)) + Ok(Some((pseudo, slot))) } } @@ -1790,14 +1853,33 @@ where let is_pseudo_element = !is_single_colon || P::pseudo_element_allows_single_colon(&name); if is_pseudo_element { - let pseudo_element = if is_functional { - input.parse_nested_block(|input| { - P::parse_functional_pseudo_element(parser, name, input) - })? + let parse_result = if is_functional { + if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") { + SimpleSelectorParseResult::SlottedPseudo( + input.parse_nested_block(|input| { + parse_compound_selector_list( + parser, + input, + ) + })? + ) + } else { + SimpleSelectorParseResult::PseudoElement( + input.parse_nested_block(|input| { + P::parse_functional_pseudo_element( + parser, + name, + input, + ) + })? + ) + } } else { - P::parse_pseudo_element(parser, location, name)? + SimpleSelectorParseResult::PseudoElement( + P::parse_pseudo_element(parser, location, name)? + ) }; - Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo_element))) + Ok(Some(parse_result)) } else { let pseudo_class = if is_functional { input.parse_nested_block(|input| { @@ -1979,6 +2061,10 @@ pub mod tests { type Impl = DummySelectorImpl; type Error = SelectorParseErrorKind<'i>; + fn parse_slotted(&self) -> bool { + true + } + fn parse_non_ts_pseudo_class( &self, location: SourceLocation, @@ -2085,9 +2171,9 @@ pub mod tests { #[test] fn test_parsing() { - assert!(parse("").is_err()) ; - assert!(parse(":lang(4)").is_err()) ; - assert!(parse(":lang(en US)").is_err()) ; + assert!(parse("").is_err()); + assert!(parse(":lang(4)").is_err()); + assert!(parse(":lang(en US)").is_err()); assert_eq!(parse("EeÉ"), Ok(SelectorList::from_vec(vec!( Selector::from_vec(vec!( Component::LocalName(LocalName { @@ -2394,6 +2480,16 @@ pub mod tests { ].into_boxed_slice() )), specificity(0, 0, 0)) )))); + + 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("div ::slotted(div)").is_ok()); + assert!(parse("div + slot::slotted(div)").is_ok()); + assert!(parse("div + slot::slotted(div.foo)").is_ok()); + assert!(parse("div + slot::slotted(.foo, bar, .baz)").is_ok()); } #[test] |