diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2019-05-08 07:46:34 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-08 07:46:34 -0400 |
commit | ea71b3522061c48256ab1c5a27fb2db8f2607e0e (patch) | |
tree | 8744328a89c07011db8748a73b53af924281710c /components/selectors/parser.rs | |
parent | 115a4ac4ff0c38fbe1c705e4aaa76d1dfcdeaab6 (diff) | |
parent | 1bb15d881951425dd62f5d5b3677a18d1019167d (diff) | |
download | servo-ea71b3522061c48256ab1c5a27fb2db8f2607e0e.tar.gz servo-ea71b3522061c48256ab1c5a27fb2db8f2607e0e.zip |
Auto merge of #23325 - emilio:gecko-sync, r=emilio
style: Sync changes from mozilla-central.
See each individual commit for details.
<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/23325)
<!-- Reviewable:end -->
Diffstat (limited to 'components/selectors/parser.rs')
-rw-r--r-- | components/selectors/parser.rs | 446 |
1 files changed, 259 insertions, 187 deletions
diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index 0515eaca193..a924bbc17d0 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -30,10 +30,7 @@ pub trait PseudoElement: Sized + ToCss { /// Whether the pseudo-element supports a given state selector to the right /// of it. - fn supports_pseudo_class( - &self, - _pseudo_class: &<Self::Impl as SelectorImpl>::NonTSPseudoClass, - ) -> bool { + fn accepts_state_pseudo_classes(&self) -> bool { false } @@ -50,6 +47,11 @@ pub trait NonTSPseudoClass: Sized + ToCss { /// Whether this pseudo-class is :active or :hover. fn is_active_or_hover(&self) -> bool; + + /// Whether this pseudo-class belongs to: + /// + /// https://drafts.csswg.org/selectors-4/#useraction-pseudos + fn is_user_action_state(&self) -> bool; } /// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a @@ -64,6 +66,72 @@ fn to_ascii_lowercase(s: &str) -> Cow<str> { } } +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 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` or + /// `::part`) already. + /// + /// If so, then other pseudo-elements and most other selectors are + /// disallowed. + 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 << 4; + /// Whether we are after any of the pseudo-like things. + 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) + } +} + pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>; #[derive(Clone, Debug, PartialEq)] @@ -76,6 +144,7 @@ pub enum SelectorParseErrorKind<'i> { NonCompoundSelector, NonPseudoElementAfterSlotted, InvalidPseudoElementAfterSlotted, + InvalidState, UnexpectedTokenInAttributeSelector(Token<'i>), PseudoElementExpectedColon(Token<'i>), PseudoElementExpectedIdent(Token<'i>), @@ -108,6 +177,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; @@ -143,17 +213,16 @@ pub trait Parser<'i> { type Impl: SelectorImpl; type Error: 'i + From<SelectorParseErrorKind<'i>>; - /// Whether the name is a pseudo-element that can be specified with - /// the single colon syntax in addition to the double-colon syntax. - fn pseudo_element_allows_single_colon(name: &str) -> bool { - is_css2_pseudo_element(name) - } - /// 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 @@ -694,7 +763,8 @@ impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> { /// combinators to the left. #[inline] pub(crate) fn is_featureless_host_selector(&mut self) -> bool { - self.all(|component| matches!(*component, Component::Host(..))) && + self.selector_length() > 0 && + self.all(|component| matches!(*component, Component::Host(..))) && self.next_sequence().is_none() } @@ -798,6 +868,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 { @@ -891,8 +964,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 /// @@ -904,6 +976,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 @@ -1153,8 +1228,7 @@ impl ToCss for Combinator { Combinator::Descendant => dest.write_str(" "), Combinator::NextSibling => dest.write_str(" + "), Combinator::LaterSibling => dest.write_str(" ~ "), - Combinator::PseudoElement => Ok(()), - Combinator::SlotAssignment => Ok(()), + Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => Ok(()), } } } @@ -1193,6 +1267,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('#')?; @@ -1364,15 +1443,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((has_pseudo, slot)) => { - has_pseudo_element = has_pseudo; - slotted = slot; - }, + 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 @@ -1382,7 +1458,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; } @@ -1420,6 +1500,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))) } @@ -1510,6 +1592,7 @@ enum SimpleSelectorParseResult<Impl: SelectorImpl> { SimpleSelector(Component<Impl>), PseudoElement(Impl::PseudoElement), SlottedPseudo(Selector<Impl>), + PartPseudo(Impl::PartName), } #[derive(Debug)] @@ -1848,7 +1931,7 @@ where Err(e) => return Err(e.into()), }; if !is_type_sel { - match parse_one_simple_selector(parser, input, /* inside_negation = */ true)? { + match parse_one_simple_selector(parser, input, SelectorParsingState::INSIDE_NEGATION)? { Some(SimpleSelectorParseResult::SimpleSelector(s)) => { sequence.push(s); }, @@ -1856,6 +1939,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)); @@ -1875,14 +1959,11 @@ where /// /// `Err(())` means invalid selector. /// `Ok(None)` is an empty selector -/// -/// 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, bool)>, ParseError<'i, P::Error>> +) -> Result<Option<SelectorParsingState>, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl, @@ -1901,122 +1982,48 @@ where empty = false; } - let mut pseudo = false; - let mut slot = false; + let mut state = SelectorParsingState::empty(); loop { - let parse_result = - match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? { - None => break, - Some(result) => result, - }; + let parse_result = match parse_one_simple_selector(parser, input, state)? { + None => break, + Some(result) => result, + }; empty = false; - let slotted_selector; - let pseudo_element; - match parse_result { SimpleSelectorParseResult::SimpleSelector(s) => { builder.push_simple_selector(s); - continue; }, - SimpleSelectorParseResult::PseudoElement(p) => { - slotted_selector = None; - pseudo_element = Some(p); + 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) => { - slotted_selector = Some(selector); - let maybe_pseudo = - parse_one_simple_selector(parser, input, /* inside_negation = */ false)?; - - pseudo_element = match maybe_pseudo { - None => None, - Some(SimpleSelectorParseResult::PseudoElement(pseudo)) => { - if !pseudo.valid_after_slotted() { - return Err(input.new_custom_error( - SelectorParseErrorKind::InvalidPseudoElementAfterSlotted, - )); - } - Some(pseudo) - }, - Some(SimpleSelectorParseResult::SimpleSelector(..)) | - Some(SimpleSelectorParseResult::SlottedPseudo(..)) => { - return Err(input.new_custom_error( - SelectorParseErrorKind::NonPseudoElementAfterSlotted, - )); - }, - }; + state.insert(SelectorParsingState::AFTER_SLOTTED); + if !builder.is_empty() { + builder.push_combinator(Combinator::SlotAssignment); + } + builder.push_simple_selector(Component::Slotted(selector)); }, - } - - debug_assert!(slotted_selector.is_some() || pseudo_element.is_some()); - // Try to parse state to the right of the pseudo-element. - // - // There are only 3 allowable state selectors that can go on - // pseudo-elements as of right now. - let mut state_selectors = SmallVec::<[Component<Impl>; 3]>::new(); - if let Some(ref p) = pseudo_element { - loop { - let location = input.current_source_location(); - match input.next_including_whitespace() { - Ok(&Token::Colon) => {}, - Ok(&Token::WhiteSpace(_)) | Err(_) => break, - Ok(t) => { - let e = SelectorParseErrorKind::PseudoElementExpectedColon(t.clone()); - return Err(location.new_custom_error(e)); - }, + SimpleSelectorParseResult::PseudoElement(p) => { + state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT); + if !p.accepts_state_pseudo_classes() { + state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT); } - - let location = input.current_source_location(); - // TODO(emilio): Functional pseudo-classes too? - // We don't need it for now. - let name = match input.next_including_whitespace()? { - &Token::Ident(ref name) => name.clone(), - t => { - return Err(location.new_custom_error( - SelectorParseErrorKind::NoIdentForPseudo(t.clone()), - )); - }, - }; - - let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name.clone())?; - if !p.supports_pseudo_class(&pseudo_class) { - return Err(input.new_custom_error( - SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name), - )); + if !builder.is_empty() { + builder.push_combinator(Combinator::PseudoElement); } - state_selectors.push(Component::NonTSPseudoClass(pseudo_class)); - } - } - - if let Some(slotted) = slotted_selector { - slot = true; - if !builder.is_empty() { - builder.push_combinator(Combinator::SlotAssignment); - } - builder.push_simple_selector(Component::Slotted(slotted)); - } - - if let Some(p) = pseudo_element { - pseudo = true; - if !builder.is_empty() { - builder.push_combinator(Combinator::PseudoElement); - } - - builder.push_simple_selector(Component::PseudoElement(p)); - - for state_selector in state_selectors.drain() { - builder.push_simple_selector(state_selector); - } + builder.push_simple_selector(Component::PseudoElement(p)); + }, } - - break; } if empty { // An empty selector is invalid. Ok(None) } else { - Ok(Some((pseudo, slot))) + Ok(Some(state)) } } @@ -2024,12 +2031,16 @@ fn parse_functional_pseudo_class<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, name: CowRcStr<'i>, - inside_negation: bool, + state: SelectorParsingState, ) -> Result<Component<Impl>, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl, { + if !state.allows_functional_pseudo_classes() { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } + debug_assert!(state.allows_tree_structural_pseudo_classes()); match_ignore_ascii_case! { &name, "nth-child" => return Ok(parse_nth_pseudo_class(input, Component::NthChild)?), "nth-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthOfType)?), @@ -2037,11 +2048,12 @@ where "nth-last-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthLastOfType)?), "host" => return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input)?))), "not" => { - if inside_negation { + if state.intersects(SelectorParsingState::INSIDE_NEGATION) { return Err(input.new_custom_error( SelectorParseErrorKind::UnexpectedIdent("not".into()) )); } + debug_assert!(state.is_empty()); return parse_negation(parser, input) }, _ => {} @@ -2064,7 +2076,7 @@ where /// Returns whether the name corresponds to a CSS2 pseudo-element that /// can be specified with the single colon syntax (in addition to the /// double-colon syntax, which can be used for all pseudo-elements). -pub fn is_css2_pseudo_element(name: &str) -> bool { +fn is_css2_pseudo_element(name: &str) -> bool { // ** Do not add to this list! ** match_ignore_ascii_case! { name, "before" | "after" | "first-line" | "first-letter" => true, @@ -2080,37 +2092,52 @@ pub fn is_css2_pseudo_element(name: &str) -> bool { fn parse_one_simple_selector<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, - inside_negation: bool, + state: SelectorParsingState, ) -> Result<Option<SimpleSelectorParseResult<Impl>>, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl, { let start = input.state(); - // FIXME: remove clone() when lifetimes are non-lexical - match input.next_including_whitespace().map(|t| t.clone()) { - Ok(Token::IDHash(id)) => { + let token = match input.next_including_whitespace().map(|t| t.clone()) { + Ok(t) => t, + Err(..) => { + input.reset(&start); + return Ok(None); + }, + }; + + Ok(Some(match token { + Token::IDHash(id) => { + if state.intersects(SelectorParsingState::AFTER_PSEUDO) { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } let id = Component::ID(id.as_ref().into()); - Ok(Some(SimpleSelectorParseResult::SimpleSelector(id))) + SimpleSelectorParseResult::SimpleSelector(id) }, - Ok(Token::Delim('.')) => { + Token::Delim('.') => { + if state.intersects(SelectorParsingState::AFTER_PSEUDO) { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } let location = input.current_source_location(); - match *input.next_including_whitespace()? { - Token::Ident(ref class) => { - let class = Component::Class(class.as_ref().into()); - Ok(Some(SimpleSelectorParseResult::SimpleSelector(class))) - }, + let class = match *input.next_including_whitespace()? { + Token::Ident(ref class) => class, ref t => { let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone()); - Err(location.new_custom_error(e)) + return Err(location.new_custom_error(e)); }, - } + }; + let class = Component::Class(class.as_ref().into()); + SimpleSelectorParseResult::SimpleSelector(class) }, - Ok(Token::SquareBracketBlock) => { + Token::SquareBracketBlock => { + if state.intersects(SelectorParsingState::AFTER_PSEUDO) { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?; - Ok(Some(SimpleSelectorParseResult::SimpleSelector(attr))) + SimpleSelectorParseResult::SimpleSelector(attr) }, - Ok(Token::Colon) => { + Token::Colon => { let location = input.current_source_location(); let (is_single_colon, next_token) = match input.next_including_whitespace()?.clone() { Token::Colon => (false, input.next_including_whitespace()?.clone()), @@ -2124,72 +2151,102 @@ where return Err(input.new_custom_error(e)); }, }; - let is_pseudo_element = - !is_single_colon || P::pseudo_element_allows_single_colon(&name); + let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name); if is_pseudo_element { - let parse_result = if is_functional { + 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) })?; - SimpleSelectorParseResult::SlottedPseudo(selector) - } else { - let selector = input.parse_nested_block(|input| { - P::parse_functional_pseudo_element(parser, name, input) - })?; - SimpleSelectorParseResult::PseudoElement(selector) + return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector))); } + input.parse_nested_block(|input| { + P::parse_functional_pseudo_element(parser, name, input) + })? } else { - SimpleSelectorParseResult::PseudoElement(P::parse_pseudo_element( - parser, location, name, - )?) + P::parse_pseudo_element(parser, location, name)? }; - Ok(Some(parse_result)) + + if state.intersects(SelectorParsingState::AFTER_SLOTTED) && + !pseudo_element.valid_after_slotted() + { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } + SimpleSelectorParseResult::PseudoElement(pseudo_element) } else { let pseudo_class = if is_functional { input.parse_nested_block(|input| { - parse_functional_pseudo_class(parser, input, name, inside_negation) + parse_functional_pseudo_class(parser, input, name, state) })? } else { - parse_simple_pseudo_class(parser, location, name)? + parse_simple_pseudo_class(parser, location, name, state)? }; - Ok(Some(SimpleSelectorParseResult::SimpleSelector( - pseudo_class, - ))) + SimpleSelectorParseResult::SimpleSelector(pseudo_class) } }, _ => { input.reset(&start); - Ok(None) + return Ok(None); }, - } + })) } fn parse_simple_pseudo_class<'i, P, Impl>( parser: &P, location: SourceLocation, name: CowRcStr<'i>, + state: SelectorParsingState, ) -> Result<Component<Impl>, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl, { - (match_ignore_ascii_case! { &name, - "first-child" => Ok(Component::FirstChild), - "last-child" => Ok(Component::LastChild), - "only-child" => Ok(Component::OnlyChild), - "root" => Ok(Component::Root), - "empty" => Ok(Component::Empty), - "scope" => Ok(Component::Scope), - "host" if P::parse_host(parser) => Ok(Component::Host(None)), - "first-of-type" => Ok(Component::FirstOfType), - "last-of-type" => Ok(Component::LastOfType), - "only-of-type" => Ok(Component::OnlyOfType), - _ => Err(()) - }) - .or_else(|()| { - P::parse_non_ts_pseudo_class(parser, location, name).map(Component::NonTSPseudoClass) - }) + if !state.allows_non_functional_pseudo_classes() { + return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState)); + } + + if state.allows_tree_structural_pseudo_classes() { + match_ignore_ascii_case! { &name, + "first-child" => return Ok(Component::FirstChild), + "last-child" => return Ok(Component::LastChild), + "only-child" => return Ok(Component::OnlyChild), + "root" => return Ok(Component::Root), + "empty" => return Ok(Component::Empty), + "scope" => return Ok(Component::Scope), + "host" if P::parse_host(parser) => return Ok(Component::Host(None)), + "first-of-type" => return Ok(Component::FirstOfType), + "last-of-type" => return Ok(Component::LastOfType), + "only-of-type" => return Ok(Component::OnlyOfType), + _ => {}, + } + } + + let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name)?; + if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) && + !pseudo_class.is_user_action_state() + { + return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState)); + } + Ok(Component::NonTSPseudoClass(pseudo_class)) } // NB: pub module in order to access the DummyParser @@ -2218,11 +2275,8 @@ pub mod tests { impl parser::PseudoElement for PseudoElement { type Impl = DummySelectorImpl; - fn supports_pseudo_class(&self, pc: &PseudoClass) -> bool { - match *pc { - PseudoClass::Hover => true, - PseudoClass::Active | PseudoClass::Lang(..) => false, - } + fn accepts_state_pseudo_classes(&self) -> bool { + true } fn valid_after_slotted(&self) -> bool { @@ -2237,6 +2291,11 @@ pub mod tests { fn is_active_or_hover(&self) -> bool { matches!(*self, PseudoClass::Active | PseudoClass::Hover) } + + #[inline] + fn is_user_action_state(&self) -> bool { + self.is_active_or_hover() + } } impl ToCss for PseudoClass { @@ -2302,6 +2361,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; @@ -2340,6 +2400,10 @@ pub mod tests { true } + fn parse_part(&self) -> bool { + true + } + fn parse_non_ts_pseudo_class( &self, location: SourceLocation, @@ -2789,11 +2853,11 @@ pub mod tests { specificity(0, 2, 1) | HAS_PSEUDO_BIT, )])) ); - assert!(parse("::before:hover:active").is_err()); + assert!(parse("::before:hover:lang(foo)").is_err()); assert!(parse("::before:hover .foo").is_err()); assert!(parse("::before .foo").is_err()); assert!(parse("::before ~ bar").is_err()); - assert!(parse("::before:active").is_err()); + assert!(parse("::before:active").is_ok()); // https://github.com/servo/servo/issues/15335 assert!(parse(":: before").is_err()); @@ -2914,6 +2978,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()); |