diff options
Diffstat (limited to 'components')
-rw-r--r-- | components/selectors/builder.rs | 17 | ||||
-rw-r--r-- | components/selectors/matching.rs | 19 | ||||
-rw-r--r-- | components/selectors/parser.rs | 174 | ||||
-rw-r--r-- | components/selectors/tree.rs | 7 |
4 files changed, 175 insertions, 42 deletions
diff --git a/components/selectors/builder.rs b/components/selectors/builder.rs index c996a6a6491..b3d61b34706 100644 --- a/components/selectors/builder.rs +++ b/components/selectors/builder.rs @@ -264,12 +264,23 @@ fn complex_selector_specificity<Impl>(mut iter: slice::Iter<Component<Impl>>) -> Specificity where Impl: SelectorImpl { - fn simple_selector_specificity<Impl>(simple_selector: &Component<Impl>, - specificity: &mut Specificity) - where Impl: SelectorImpl + fn simple_selector_specificity<Impl>( + simple_selector: &Component<Impl>, + specificity: &mut Specificity, + ) + where + Impl: SelectorImpl { match *simple_selector { Component::Combinator(..) => unreachable!(), + // FIXME(emilio): Spec doesn't define any particular specificity for + // ::slotted(), so apply the general rule for pseudos per: + // + // https://github.com/w3c/csswg-drafts/issues/1915 + // + // Though other engines compute it dynamically, so maybe we should + // do that instead, eventually. + Component::Slotted(..) | Component::PseudoElement(..) | Component::LocalName(..) => { specificity.element_selectors += 1 diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index a2d85e8986d..d49d24ee075 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -393,6 +393,9 @@ where element.parent_element() } + Combinator::SlotAssignment => { + element.assigned_slot() + } Combinator::PseudoElement => { element.pseudo_element_originating_element() } @@ -453,6 +456,7 @@ where } Combinator::Child | Combinator::Descendant | + Combinator::SlotAssignment | Combinator::PseudoElement => { SelectorMatchingResult::NotMatchedGlobally } @@ -541,6 +545,21 @@ where { match *selector { Component::Combinator(_) => unreachable!(), + Component::Slotted(ref selectors) => { + context.shared.nesting_level += 1; + let result = + element.assigned_slot().is_some() && + selectors.iter().any(|s| { + matches_complex_selector( + s.iter(), + element, + context.shared, + flags_setter, + ) + }); + context.shared.nesting_level -= 1; + result + } Component::PseudoElement(ref pseudo) => { element.match_pseudo_element(pseudo, context.shared) } 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] diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs index d050c23a470..7a83feb83d0 100644 --- a/components/selectors/tree.rs +++ b/components/selectors/tree.rs @@ -84,6 +84,13 @@ pub trait Element: Sized + Clone + Debug { /// Whether this element is a `link`. fn is_link(&self) -> bool; + /// Returns the assigned <slot> element this element is assigned to. + /// + /// Necessary for the `::slotted` pseudo-class. + fn assigned_slot(&self) -> Option<Self> { + None + } + fn has_id(&self, id: &<Self::Impl as SelectorImpl>::Identifier, case_sensitivity: CaseSensitivity) |