/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::attr::{AttrSelectorOperator, AttrSelectorWithOptionalNamespace}; use crate::attr::{NamespaceConstraint, ParsedAttrSelectorOperation}; use crate::attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE}; use crate::bloom::BLOOM_HASH_MASK; use crate::builder::{SelectorBuilder, SelectorFlags, SpecificityAndFlags}; use crate::context::QuirksMode; use crate::sink::Push; pub use crate::visitor::SelectorVisitor; use cssparser::parse_nth; use cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind}; use cssparser::{CowRcStr, Delimiter, SourceLocation}; use cssparser::{Parser as CssParser, ToCss, Token}; use precomputed_hash::PrecomputedHash; use smallvec::{smallvec, SmallVec}; use std::borrow::Borrow; use std::fmt::{self, Debug}; use std::iter::Rev; use std::slice; /// A trait that represents a pseudo-element. pub trait PseudoElement<'i>: Sized + ToCss { /// The `SelectorImpl` this pseudo-element is used for. type Impl: SelectorImpl<'i>; /// Whether the pseudo-element supports a given state selector to the right /// of it. fn accepts_state_pseudo_classes(&self) -> bool { false } /// Whether this pseudo-element is valid after a ::slotted(..) pseudo. fn valid_after_slotted(&self) -> bool { false } fn is_webkit_scrollbar(&self) -> bool { false } fn is_view_transition(&self) -> bool { false } fn is_unknown(&self) -> bool { false } } /// A trait that represents a pseudo-class. pub trait NonTSPseudoClass<'i>: Sized + ToCss { /// The `SelectorImpl` this pseudo-element is used for. type Impl: SelectorImpl<'i>; /// 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; fn is_valid_before_webkit_scrollbar(&self) -> bool { true } fn is_valid_after_webkit_scrollbar(&self) -> bool { false } fn visit(&self, _visitor: &mut V) -> bool where V: SelectorVisitor<'i, Impl = Self::Impl>, { true } } /// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a /// Cow::Owned if `s` had to be converted into ASCII lowercase. fn to_ascii_lowercase<'i>(s: CowRcStr<'i>) -> CowRcStr<'i> { if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { let mut string = s.to_string(); string[first_uppercase..].make_ascii_lowercase(); string.into() } else { s } } bitflags! { /// Flags that indicate at which point of parsing a selector are we. #[derive(PartialEq, Eq, Clone, Copy)] struct SelectorParsingState: u16 { /// Whether we should avoid adding default namespaces to selectors that /// aren't type or universal selectors. const SKIP_DEFAULT_NAMESPACE = 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(); /// Whether we explicitly disallow combinators. const DISALLOW_COMBINATORS = 1 << 5; /// Whether we explicitly disallow pseudo-element-like things. const DISALLOW_PSEUDOS = 1 << 6; /// Whether we have seen a nesting selector. const AFTER_NESTING = 1 << 7; const AFTER_WEBKIT_SCROLLBAR = 1 << 8; const AFTER_VIEW_TRANSITION = 1 << 9; const AFTER_UNKNOWN_PSEUDO_ELEMENT = 1 << 10; } } impl SelectorParsingState { #[inline] fn allows_pseudos(self) -> bool { // NOTE(emilio): We allow pseudos after ::part and such. !self.intersects(Self::AFTER_PSEUDO_ELEMENT | Self::DISALLOW_PSEUDOS) } #[inline] fn allows_slotted(self) -> bool { !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS) } #[inline] fn allows_part(self) -> bool { !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS) } // TODO(emilio): Maybe some of these should be allowed, but this gets us on // the safe side for now, matching previous behavior. Gotta be careful with // the ones like :-moz-any, which allow nested selectors but don't carry the // state, and so on. #[inline] fn allows_custom_functional_pseudo_classes(self) -> bool { !self.intersects(Self::AFTER_PSEUDO) } #[inline] fn allows_non_functional_pseudo_classes(self) -> bool { !self.intersects(Self::AFTER_SLOTTED | Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT) } #[inline] fn allows_tree_structural_pseudo_classes(self) -> bool { !self.intersects(Self::AFTER_PSEUDO) } #[inline] fn allows_combinators(self) -> bool { !self.intersects(Self::DISALLOW_COMBINATORS) } } pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>; #[derive(Clone, Debug, PartialEq)] pub enum SelectorParseErrorKind<'i> { NoQualifiedNameInAttributeSelector(Token<'i>), EmptySelector, DanglingCombinator, InvalidPseudoClassBeforeWebKitScrollbar, InvalidPseudoClassAfterWebKitScrollbar, InvalidPseudoClassAfterPseudoElement, InvalidState, MissingNestingSelector, MissingNestingPrefix, UnexpectedTokenInAttributeSelector(Token<'i>), PseudoElementExpectedIdent(Token<'i>), UnsupportedPseudoElement(CowRcStr<'i>), UnsupportedPseudoClass(CowRcStr<'i>), AmbiguousCssModuleClass(CowRcStr<'i>), UnexpectedIdent(CowRcStr<'i>), ExpectedNamespace(CowRcStr<'i>), ExpectedBarInAttr(Token<'i>), BadValueInAttr(Token<'i>), InvalidQualNameInAttr(Token<'i>), ExplicitNamespaceUnexpectedToken(Token<'i>), ClassNeedsIdent(Token<'i>), UnexpectedSelectorAfterPseudoElement(Token<'i>), } macro_rules! with_all_bounds { ( [ $( $InSelector: tt )* ] [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ] ) => { /// This trait allows to define the parser implementation in regards /// of pseudo-classes/elements /// /// NB: We need Clone so that we can derive(Clone) on struct with that /// are parameterized on SelectorImpl. See /// pub trait SelectorImpl<'i>: Clone + Debug + Sized + 'static { type ExtraMatchingData: Sized + Default + 'static; type AttrValue: $($InSelector)*; type Identifier: $($InSelector)*; type LocalName: $($InSelector)* + Borrow; type NamespaceUrl: $($CommonBounds)* + $($FromStr)* + Default + Borrow; 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) type NonTSPseudoClass: $($CommonBounds)* + NonTSPseudoClass<'i, Impl = Self>; type VendorPrefix: Sized + Eq + $($CommonBounds)* + ToCss; /// pseudo-elements type PseudoElement: $($CommonBounds)* + PseudoElement<'i, Impl = Self>; fn to_css(selectors: &SelectorList<'i, Self>, dest: &mut W) -> fmt::Result { serialize_selector_list(selectors.0.iter(), dest) } } } } macro_rules! with_bounds { ( [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ]) => { with_all_bounds! { [$($CommonBounds)* + $($FromStr)* + ToCss] [$($CommonBounds)*] [$($FromStr)*] } } } #[cfg(feature = "serde")] with_bounds! { [Clone + PartialEq + Eq + std::hash::Hash] [From> + From> + AsRef] } #[cfg(not(feature = "serde"))] with_bounds! { [Clone + PartialEq + Eq + std::hash::Hash] [From>] } pub trait Parser<'i> { type Impl: SelectorImpl<'i>; type Error: 'i + From>; /// 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 `:where` pseudo-class. fn parse_is_and_where(&self) -> bool { false } /// The error recovery that selector lists inside :is() and :where() have. fn is_and_where_error_recovery(&self) -> ParseErrorRecovery { ParseErrorRecovery::IgnoreInvalidSelector } /// Whether the given function name is an alias for the `:is()` function. fn parse_any_prefix(&self, _name: &str) -> Option<>::VendorPrefix> { None } /// Whether to parse the `:host` pseudo-class. fn parse_host(&self) -> bool { false } /// Parses non-tree-structural pseudo-classes. Tree structural pseudo-classes, /// like `:first-child`, are built into this library. /// /// This function can return an "Err" pseudo-element in order to support CSS2.1 /// pseudo-elements. fn parse_non_ts_pseudo_class( &self, location: SourceLocation, name: CowRcStr<'i>, ) -> Result<>::NonTSPseudoClass, ParseError<'i, Self::Error>> { Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name))) } fn parse_non_ts_functional_pseudo_class<'t>( &self, name: CowRcStr<'i>, arguments: &mut CssParser<'i, 't>, ) -> Result<>::NonTSPseudoClass, ParseError<'i, Self::Error>> { Err(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name))) } fn parse_pseudo_element( &self, location: SourceLocation, name: CowRcStr<'i>, ) -> Result<>::PseudoElement, ParseError<'i, Self::Error>> { Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name))) } fn parse_functional_pseudo_element<'t>( &self, name: CowRcStr<'i>, arguments: &mut CssParser<'i, 't>, ) -> Result<>::PseudoElement, ParseError<'i, Self::Error>> { Err(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name))) } fn default_namespace(&self) -> Option<>::NamespaceUrl> { None } fn namespace_for_prefix( &self, _prefix: &>::NamespacePrefix, ) -> Option<>::NamespaceUrl> { None } fn is_nesting_allowed(&self) -> bool { false } fn deep_combinator_enabled(&self) -> bool { false } } #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(bound( serialize = "Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize", deserialize = "Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>" )) )] #[cfg_attr( feature = "jsonschema", derive(schemars::JsonSchema), schemars( rename = "SelectorList", bound = "Impl: schemars::JsonSchema, Impl::NonTSPseudoClass: schemars::JsonSchema, Impl::PseudoElement: schemars::JsonSchema, Impl::VendorPrefix: schemars::JsonSchema" ) )] pub struct SelectorList<'i, Impl: SelectorImpl<'i>>( #[cfg_attr(feature = "serde", serde(borrow))] pub SmallVec<[Selector<'i, Impl>; 1]>, ); #[cfg(feature = "into_owned")] impl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for SelectorList<'i, Impl> where Impl: static_self::IntoOwned<'any, Owned = NewSel>, NewSel: SelectorImpl<'any>, Component<'i, Impl>: static_self::IntoOwned<'any, Owned = Component<'any, NewSel>>, { type Owned = SelectorList<'any, NewSel>; fn into_owned(self) -> Self::Owned { SelectorList(self.0.into_owned()) } } /// How to treat invalid selectors in a selector list. pub enum ParseErrorRecovery { /// Discard the entire selector list, this is the default behavior for /// almost all of CSS. DiscardList, /// Ignore invalid selectors, potentially creating an empty selector list. /// /// This is the error recovery mode of :is() and :where() IgnoreInvalidSelector, } #[derive(Eq, PartialEq, Clone, Copy)] pub enum NestingRequirement { None, Prefixed, Contained, Implicit, } impl<'i, Impl: SelectorImpl<'i>> SelectorList<'i, Impl> { /// Parse a comma-separated list of Selectors. /// /// /// Return the Selectors or Err if there is an invalid selector. pub fn parse<'t, P>( parser: &P, input: &mut CssParser<'i, 't>, error_recovery: ParseErrorRecovery, nesting_requirement: NestingRequirement, ) -> Result> where P: Parser<'i, Impl = Impl>, { Self::parse_with_state( parser, input, &mut SelectorParsingState::empty(), error_recovery, nesting_requirement, ) } #[inline] fn parse_with_state<'t, P>( parser: &P, input: &mut CssParser<'i, 't>, state: &mut SelectorParsingState, recovery: ParseErrorRecovery, nesting_requirement: NestingRequirement, ) -> Result> where P: Parser<'i, Impl = Impl>, { let original_state = *state; let mut values = SmallVec::new(); loop { let selector = input.parse_until_before(Delimiter::Comma, |input| { let mut selector_state = original_state; let result = parse_selector(parser, input, &mut selector_state, nesting_requirement); if selector_state.contains(SelectorParsingState::AFTER_NESTING) { state.insert(SelectorParsingState::AFTER_NESTING) } result }); let was_ok = selector.is_ok(); match selector { Ok(selector) => values.push(selector), Err(err) => match recovery { ParseErrorRecovery::DiscardList => return Err(err), ParseErrorRecovery::IgnoreInvalidSelector => {} }, } loop { match input.next() { Err(_) => return Ok(SelectorList(values)), Ok(&Token::Comma) => break, Ok(_) => { debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); } } } } } pub fn parse_relative<'t, P>( parser: &P, input: &mut CssParser<'i, 't>, error_recovery: ParseErrorRecovery, nesting_requirement: NestingRequirement, ) -> Result> where P: Parser<'i, Impl = Impl>, { Self::parse_relative_with_state( parser, input, &mut SelectorParsingState::empty(), error_recovery, nesting_requirement, ) } #[inline] fn parse_relative_with_state<'t, P>( parser: &P, input: &mut CssParser<'i, 't>, state: &mut SelectorParsingState, recovery: ParseErrorRecovery, nesting_requirement: NestingRequirement, ) -> Result> where P: Parser<'i, Impl = Impl>, { let original_state = *state; let mut values = SmallVec::new(); loop { let selector = input.parse_until_before(Delimiter::Comma, |input| { let mut selector_state = original_state; let result = parse_relative_selector(parser, input, &mut selector_state, nesting_requirement); if selector_state.contains(SelectorParsingState::AFTER_NESTING) { state.insert(SelectorParsingState::AFTER_NESTING) } result }); let was_ok = selector.is_ok(); match selector { Ok(selector) => values.push(selector), Err(err) => match recovery { ParseErrorRecovery::DiscardList => return Err(err), ParseErrorRecovery::IgnoreInvalidSelector => {} }, } loop { match input.next() { Err(_) => return Ok(SelectorList(values)), Ok(&Token::Comma) => break, Ok(_) => { debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); } } } } } /// Creates a new SelectorList. pub fn new(v: SmallVec<[Selector<'i, Impl>; 1]>) -> Self { SelectorList(v) } /// Creates a SelectorList from a Vec of selectors. Used in tests. pub fn from_vec(v: Vec>) -> Self { SelectorList(SmallVec::from_vec(v)) } } impl<'i, Impl: SelectorImpl<'i>> From> for SelectorList<'i, Impl> { fn from(selector: Selector<'i, Impl>) -> Self { SelectorList(smallvec![selector]) } } impl<'i, Impl: SelectorImpl<'i>> From> for SelectorList<'i, Impl> { fn from(component: Component<'i, Impl>) -> Self { SelectorList::from(Selector::from(component)) } } /// Parses one compound selector suitable for nested stuff like :-moz-any, etc. fn parse_inner_compound_selector<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, state: &mut SelectorParsingState, ) -> Result, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, { let mut child_state = *state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS; let result = parse_selector(parser, input, &mut child_state, NestingRequirement::None)?; if child_state.contains(SelectorParsingState::AFTER_NESTING) { state.insert(SelectorParsingState::AFTER_NESTING) } Ok(result) } /// Ancestor hashes for the bloom filter. We precompute these and store them /// inline with selectors to optimize cache performance during matching. /// This matters a lot. /// /// We use 4 hashes, which is copied from Gecko, who copied it from WebKit. /// Note that increasing the number of hashes here will adversely affect the /// cache hit when fast-rejecting long lists of Rules with inline hashes. /// /// Because the bloom filter only uses the bottom 24 bits of the hash, we pack /// the fourth hash into the upper bits of the first three hashes in order to /// shrink Rule (whose size matters a lot). This scheme minimizes the runtime /// overhead of the packing for the first three hashes (we just need to mask /// off the upper bits) at the expense of making the fourth somewhat more /// complicated to assemble, because we often bail out before checking all the /// hashes. #[derive(Clone, Debug, Eq, PartialEq)] pub struct AncestorHashes { pub packed_hashes: [u32; 3], } fn collect_ancestor_hashes<'i, Impl: SelectorImpl<'i>>( iter: SelectorIter<'_, 'i, Impl>, quirks_mode: QuirksMode, hashes: &mut [u32; 4], len: &mut usize, ) -> bool where Impl::Identifier: PrecomputedHash, Impl::LocalName: PrecomputedHash, Impl::NamespaceUrl: PrecomputedHash, { for component in AncestorIter::new(iter) { let hash = match *component { Component::LocalName(LocalName { ref name, ref lower_name, }) => { // Only insert the local-name into the filter if it's all // lowercase. Otherwise we would need to test both hashes, and // our data structures aren't really set up for that. if name != lower_name { continue; } name.precomputed_hash() } Component::DefaultNamespace(ref url) | Component::Namespace(_, ref url) => url.precomputed_hash(), // In quirks mode, class and id selectors should match // case-insensitively, so just avoid inserting them into the filter. Component::ID(ref id) if quirks_mode != QuirksMode::Quirks => id.precomputed_hash(), Component::Class(ref class) if quirks_mode != QuirksMode::Quirks => class.precomputed_hash(), Component::Is(ref list) | Component::Where(ref list) => { // :where and :is OR their selectors, so we can't put any hash // in the filter if there's more than one selector, as that'd // exclude elements that may match one of the other selectors. if list.len() == 1 && !collect_ancestor_hashes(list[0].iter(), quirks_mode, hashes, len) { return false; } continue; } _ => continue, }; hashes[*len] = hash & BLOOM_HASH_MASK; *len += 1; if *len == hashes.len() { return false; } } true } impl AncestorHashes { pub fn new<'i, Impl: SelectorImpl<'i>>(selector: &Selector<'i, Impl>, quirks_mode: QuirksMode) -> Self where Impl::Identifier: PrecomputedHash, Impl::LocalName: PrecomputedHash, Impl::NamespaceUrl: PrecomputedHash, { // Compute ancestor hashes for the bloom filter. let mut hashes = [0u32; 4]; let mut len = 0; collect_ancestor_hashes(selector.iter(), quirks_mode, &mut hashes, &mut len); debug_assert!(len <= 4); // Now, pack the fourth hash (if it exists) into the upper byte of each of // the other three hashes. if len == 4 { let fourth = hashes[3]; hashes[0] |= (fourth & 0x000000ff) << 24; hashes[1] |= (fourth & 0x0000ff00) << 16; hashes[2] |= (fourth & 0x00ff0000) << 8; } AncestorHashes { packed_hashes: [hashes[0], hashes[1], hashes[2]], } } /// Returns the fourth hash, reassembled from parts. pub fn fourth_hash(&self) -> u32 { ((self.packed_hashes[0] & 0xff000000) >> 24) | ((self.packed_hashes[1] & 0xff000000) >> 16) | ((self.packed_hashes[2] & 0xff000000) >> 8) } } pub fn namespace_empty_string<'i, Impl: SelectorImpl<'i>>() -> Impl::NamespaceUrl { // Rust type’s default, not default namespace Impl::NamespaceUrl::default() } /// A Selector stores a sequence of simple selectors and combinators. The /// iterator classes allow callers to iterate at either the raw sequence level or /// at the level of sequences of simple selectors separated by combinators. Most /// callers want the higher-level iterator. /// /// We store compound selectors internally right-to-left (in matching order). /// Additionally, we invert the order of top-level compound selectors so that /// each one matches left-to-right. This is because matching namespace, local name, /// id, and class are all relatively cheap, whereas matching pseudo-classes might /// be expensive (depending on the pseudo-class). Since authors tend to put the /// pseudo-classes on the right, it's faster to start matching on the left. /// /// This reordering doesn't change the semantics of selector matching, and we /// handle it in to_css to make it invisible to serialization. #[derive(Clone, PartialEq, Eq, Hash)] pub struct Selector<'i, Impl: SelectorImpl<'i>>(SpecificityAndFlags, Vec>); #[cfg(feature = "into_owned")] impl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for Selector<'i, Impl> where Impl: static_self::IntoOwned<'any, Owned = NewSel>, NewSel: SelectorImpl<'any>, Component<'i, Impl>: static_self::IntoOwned<'any, Owned = Component<'any, NewSel>>, { type Owned = Selector<'any, NewSel>; fn into_owned(self) -> Self::Owned { Selector(self.0, self.1.into_owned()) } } impl<'i, Impl: SelectorImpl<'i>> Selector<'i, Impl> { #[inline] pub fn specificity(&self) -> u32 { self.0.specificity() } #[inline] pub fn has_pseudo_element(&self) -> bool { self.0.has_pseudo_element() } #[inline] pub fn is_slotted(&self) -> bool { self.0.is_slotted() } #[inline] pub fn is_part(&self) -> bool { self.0.is_part() } #[inline] pub fn append(&mut self, component: Component<'i, Impl>) { let index = self .1 .iter() .position(|c| matches!(*c, Component::Combinator(..) | Component::PseudoElement(..))) .unwrap_or(self.1.len()); self.1.insert(index, component); } #[inline] pub fn parts(&self) -> Option<&[Impl::Identifier]> { if !self.is_part() { return None; } let mut iter = self.iter(); if self.has_pseudo_element() { // Skip the pseudo-element. for _ in &mut iter {} let combinator = iter.next_sequence()?; debug_assert_eq!(combinator, Combinator::PseudoElement); } for component in iter { if let Component::Part(ref part) = *component { return Some(part); } } debug_assert!(false, "is_part() lied somehow?"); None } #[inline] pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> { if !self.has_pseudo_element() { return None; } for component in self.iter() { if let Component::PseudoElement(ref pseudo) = *component { return Some(pseudo); } } debug_assert!(false, "has_pseudo_element lied!"); None } /// Whether this selector (pseudo-element part excluded) matches every element. /// /// Used for "pre-computed" pseudo-elements in components/style/stylist.rs #[inline] pub fn is_universal(&self) -> bool { self.iter_raw_match_order().all(|c| { matches!( *c, Component::ExplicitUniversalType | Component::ExplicitAnyNamespace | Component::Combinator(Combinator::PseudoElement) | Component::PseudoElement(..) ) }) } #[inline] pub fn has_combinator(&self) -> bool { self .iter_raw_match_order() .any(|c| matches!(*c, Component::Combinator(combinator) if combinator.is_tree_combinator())) } /// Returns an iterator over this selector in matching order (right-to-left). /// When a combinator is reached, the iterator will return None, and /// next_sequence() may be called to continue to the next sequence. #[inline] pub fn iter(&self) -> SelectorIter<'_, 'i, Impl> { SelectorIter { iter: self.iter_raw_match_order(), next_combinator: None, } } /// Whether this selector is a featureless :host selector, with no /// combinators to the left, and optionally has a pseudo-element to the /// right. #[inline] pub fn is_featureless_host_selector_or_pseudo_element(&self) -> bool { let mut iter = self.iter(); if !self.has_pseudo_element() { return iter.is_featureless_host_selector(); } // Skip the pseudo-element. for _ in &mut iter {} match iter.next_sequence() { None => return false, Some(combinator) => { debug_assert_eq!(combinator, Combinator::PseudoElement); } } iter.is_featureless_host_selector() } /// Returns an iterator over this selector in matching order (right-to-left), /// skipping the rightmost |offset| Components. #[inline] pub fn iter_from(&self, offset: usize) -> SelectorIter<'_, 'i, Impl> { let iter = self.1[offset..].iter(); SelectorIter { iter, next_combinator: None, } } /// Returns the combinator at index `index` (zero-indexed from the right), /// or panics if the component is not a combinator. #[inline] pub fn combinator_at_match_order(&self, index: usize) -> Combinator { match self.1[index] { Component::Combinator(c) => c, ref other => panic!("Not a combinator: {:?}, {:?}, index: {}", other, self, index), } } /// Returns an iterator over the entire sequence of simple selectors and /// combinators, in matching order (from right to left). #[inline] pub fn iter_raw_match_order(&self) -> slice::Iter> { self.1.iter() } #[inline] pub fn iter_mut_raw_match_order(&mut self) -> slice::IterMut> { self.1.iter_mut() } /// Returns the combinator at index `index` (zero-indexed from the left), /// or panics if the component is not a combinator. #[inline] pub fn combinator_at_parse_order(&self, index: usize) -> Combinator { match self.1[self.len() - index - 1] { Component::Combinator(c) => c, ref other => panic!("Not a combinator: {:?}, {:?}, index: {}", other, self, index), } } /// Returns an iterator over the sequence of simple selectors and /// combinators, in parse order (from left to right), starting from /// `offset`. #[inline] pub fn iter_raw_parse_order_from(&self, offset: usize) -> Rev>> { self.1[..self.len() - offset].iter().rev() } /// Creates a Selector from a vec of Components, specified in parse order. Used in tests. #[allow(unused)] pub(crate) fn from_vec(vec: Vec>, specificity: u32, flags: SelectorFlags) -> Self { let mut builder = SelectorBuilder::default(); for component in vec.into_iter() { if let Some(combinator) = component.as_combinator() { builder.push_combinator(combinator); } else { builder.push_simple_selector(component); } } let spec = SpecificityAndFlags { specificity, flags }; let (spec, components) = builder.build_with_specificity_and_flags(spec); Selector(spec, components) } #[cfg(feature = "serde")] #[inline] pub(crate) fn new(spec: SpecificityAndFlags, components: Vec>) -> Self { Selector(spec, components) } /// Returns count of simple selectors and combinators in the Selector. #[inline] pub fn len(&self) -> usize { self.1.len() } /// Traverse selector components inside `self`. /// /// Implementations of this method should call `SelectorVisitor` methods /// or other impls of `Visit` as appropriate based on the fields of `Self`. /// /// A return value of `false` indicates terminating the traversal. /// It should be propagated with an early return. /// On the contrary, `true` indicates that all fields of `self` have been traversed: /// /// ```rust,ignore /// if !visitor.visit_simple_selector(&self.some_simple_selector) { /// return false; /// } /// if !self.some_component.visit(visitor) { /// return false; /// } /// true /// ``` pub fn visit(&self, visitor: &mut V) -> bool where V: SelectorVisitor<'i, Impl = Impl>, { let mut current = self.iter(); let mut combinator = None; loop { if !visitor.visit_complex_selector(combinator) { return false; } for selector in &mut current { if !selector.visit(visitor) { return false; } } combinator = current.next_sequence(); if combinator.is_none() { break; } } true } } impl<'i, Impl: SelectorImpl<'i>> From> for Selector<'i, Impl> { fn from(component: Component<'i, Impl>) -> Self { let mut builder = SelectorBuilder::default(); if let Some(combinator) = component.as_combinator() { builder.push_combinator(combinator); } else { builder.push_simple_selector(component); } let (spec, components) = builder.build(false, false, false); Selector(spec, components) } } impl<'i, Impl: SelectorImpl<'i>> From>> for Selector<'i, Impl> { fn from(vec: Vec>) -> Self { let mut builder = SelectorBuilder::default(); for component in vec.into_iter() { if let Some(combinator) = component.as_combinator() { builder.push_combinator(combinator); } else { builder.push_simple_selector(component); } } let (spec, components) = builder.build(false, false, false); Selector(spec, components) } } #[derive(Clone)] pub struct SelectorIter<'a, 'i, Impl: SelectorImpl<'i>> { iter: slice::Iter<'a, Component<'i, Impl>>, next_combinator: Option, } impl<'a, 'i, Impl: 'a + SelectorImpl<'i>> SelectorIter<'a, 'i, Impl> { /// Prepares this iterator to point to the next sequence to the left, /// returning the combinator if the sequence was found. #[inline] pub fn next_sequence(&mut self) -> Option { self.next_combinator.take() } /// Whether this selector is a featureless host selector, with no /// combinators to the left. #[inline] pub(crate) fn is_featureless_host_selector(&mut self) -> bool { self.selector_length() > 0 && self.all(|component| matches!(*component, Component::Host(..))) && self.next_sequence().is_none() } #[inline] pub(crate) fn matches_for_stateless_pseudo_element(&mut self) -> bool { let first = match self.next() { Some(c) => c, // Note that this is the common path that we keep inline: the // pseudo-element not having anything to its right. None => return true, }; self.matches_for_stateless_pseudo_element_internal(first) } #[inline(never)] fn matches_for_stateless_pseudo_element_internal(&mut self, first: &Component<'i, Impl>) -> bool { if !first.matches_for_stateless_pseudo_element() { return false; } for component in self { // The only other parser-allowed Components in this sequence are // state pseudo-classes, or one of the other things that can contain // them. if !component.matches_for_stateless_pseudo_element() { return false; } } true } /// Returns remaining count of the simple selectors and combinators in the Selector. #[inline] pub fn selector_length(&self) -> usize { self.iter.len() } } impl<'a, 'i, Impl: SelectorImpl<'i>> Iterator for SelectorIter<'a, 'i, Impl> { type Item = &'a Component<'i, Impl>; #[inline] fn next(&mut self) -> Option { debug_assert!(self.next_combinator.is_none(), "You should call next_sequence!"); match *self.iter.next()? { Component::Combinator(c) => { self.next_combinator = Some(c); None } ref x => Some(x), } } } impl<'a, 'i, Impl: SelectorImpl<'i>> fmt::Debug for SelectorIter<'a, 'i, Impl> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let iter = self.iter.clone().rev(); for component in iter { component.to_css(f)? } Ok(()) } } /// An iterator over all simple selectors belonging to ancestors. struct AncestorIter<'a, 'i, Impl: SelectorImpl<'i>>(SelectorIter<'a, 'i, Impl>); impl<'a, 'i, Impl: 'a + SelectorImpl<'i>> AncestorIter<'a, 'i, Impl> { /// Creates an AncestorIter. The passed-in iterator is assumed to point to /// the beginning of the child sequence, which will be skipped. fn new(inner: SelectorIter<'a, 'i, Impl>) -> Self { let mut result = AncestorIter(inner); result.skip_until_ancestor(); result } /// Skips a sequence of simple selectors and all subsequent sequences until /// a non-pseudo-element ancestor combinator is reached. fn skip_until_ancestor(&mut self) { loop { while self.0.next().is_some() {} // If this is ever changed to stop at the "pseudo-element" // combinator, we will need to fix the way we compute hashes for // revalidation selectors. if self .0 .next_sequence() .map_or(true, |x| matches!(x, Combinator::Child | Combinator::Descendant)) { break; } } } } impl<'a, 'i, Impl: SelectorImpl<'i>> Iterator for AncestorIter<'a, 'i, Impl> { type Item = &'a Component<'i, Impl>; fn next(&mut self) -> Option { // Grab the next simple selector in the sequence if available. let next = self.0.next(); if next.is_some() { return next; } // See if there are more sequences. If so, skip any non-ancestor sequences. if let Some(combinator) = self.0.next_sequence() { if !matches!(combinator, Combinator::Child | Combinator::Descendant) { self.skip_until_ancestor(); } } self.0.next() } } #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum Combinator { Child, // > Descendant, // space NextSibling, // + LaterSibling, // ~ /// A dummy combinator we use to the left of pseudo-elements. /// /// 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, /// Non-standard Vue >>> combinator. /// https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors DeepDescendant, /// Non-standard /deep/ combinator. /// Appeared in early versions of the css-scoping-1 specification: /// https://www.w3.org/TR/2014/WD-css-scoping-1-20140403/#deep-combinator /// And still supported as an alias for >>> by Vue. Deep, } 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 | Combinator::SlotAssignment ) } /// Returns true if this combinator is a pseudo-element combinator. #[inline] pub fn is_pseudo_element(&self) -> bool { matches!(*self, Combinator::PseudoElement) } /// Returns true if this combinator is a next- or later-sibling combinator. #[inline] pub fn is_sibling(&self) -> bool { matches!(*self, Combinator::NextSibling | Combinator::LaterSibling) } #[inline] pub fn is_tree_combinator(&self) -> bool { matches!( *self, Combinator::Child | Combinator::Descendant | Combinator::NextSibling | Combinator::LaterSibling ) } } /// An enum for the different types of :nth- pseudoclasses #[derive(Copy, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum NthType { Child, LastChild, OnlyChild, OfType, LastOfType, OnlyOfType, Col, LastCol, } impl NthType { pub fn is_only(self) -> bool { self == Self::OnlyChild || self == Self::OnlyOfType } pub fn is_of_type(self) -> bool { self == Self::OfType || self == Self::LastOfType || self == Self::OnlyOfType } pub fn is_from_end(self) -> bool { self == Self::LastChild || self == Self::LastOfType || self == Self::LastCol } pub fn allows_of_selector(self) -> bool { self == Self::Child || self == Self::LastChild } } /// The properties that comprise an :nth- pseudoclass as of Selectors 3 (e.g., /// nth-child(An+B)). /// https://www.w3.org/TR/selectors-3/#nth-child-pseudo #[derive(Copy, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub struct NthSelectorData { pub ty: NthType, pub is_function: bool, pub a: i32, pub b: i32, } impl NthSelectorData { /// Returns selector data for :only-{child,of-type} #[inline] pub const fn only(of_type: bool) -> Self { Self { ty: if of_type { NthType::OnlyOfType } else { NthType::OnlyChild }, is_function: false, a: 0, b: 1, } } /// Returns selector data for :first-{child,of-type} #[inline] pub const fn first(of_type: bool) -> Self { Self { ty: if of_type { NthType::OfType } else { NthType::Child }, is_function: false, a: 0, b: 1, } } /// Returns selector data for :last-{child,of-type} #[inline] pub const fn last(of_type: bool) -> Self { Self { ty: if of_type { NthType::LastOfType } else { NthType::LastChild }, is_function: false, a: 0, b: 1, } } #[inline] pub fn is_function(&self) -> bool { self.a != 0 || self.b != 1 } /// Writes the beginning of the selector. #[inline] fn write_start(&self, dest: &mut W, is_function: bool) -> fmt::Result { dest.write_str(match self.ty { NthType::Child if is_function => ":nth-child(", NthType::Child => ":first-child", NthType::LastChild if is_function => ":nth-last-child(", NthType::LastChild => ":last-child", NthType::OfType if is_function => ":nth-of-type(", NthType::OfType => ":first-of-type", NthType::LastOfType if is_function => ":nth-last-of-type(", NthType::LastOfType => ":last-of-type", NthType::OnlyChild => ":only-child", NthType::OnlyOfType => ":only-of-type", NthType::Col => ":nth-col(", NthType::LastCol => ":nth-last-col(", }) } /// Serialize (part of the CSS Syntax spec, but currently only used here). /// #[inline] fn write_affine(&self, dest: &mut W) -> fmt::Result { match (self.a, self.b) { (0, 0) => dest.write_char('0'), (1, 0) => dest.write_char('n'), (-1, 0) => dest.write_str("-n"), (_, 0) => write!(dest, "{}n", self.a), (2, 1) => dest.write_str("odd"), (0, _) => write!(dest, "{}", self.b), (1, _) => write!(dest, "n{:+}", self.b), (-1, _) => write!(dest, "-n{:+}", self.b), (_, _) => write!(dest, "{}n{:+}", self.a, self.b), } } } /// The properties that comprise an :nth- pseudoclass as of Selectors 4 (e.g., /// nth-child(An+B [of S]?)). /// https://www.w3.org/TR/selectors-4/#nth-child-pseudo #[derive(Clone, PartialEq, Eq, Hash)] pub struct NthOfSelectorData<'i, Impl: SelectorImpl<'i>>(NthSelectorData, Box<[Selector<'i, Impl>]>); #[cfg(feature = "into_owned")] impl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for NthOfSelectorData<'i, Impl> where Impl: static_self::IntoOwned<'any, Owned = NewSel>, NewSel: SelectorImpl<'any>, Component<'i, Impl>: static_self::IntoOwned<'any, Owned = Component<'any, NewSel>>, { type Owned = NthOfSelectorData<'any, NewSel>; fn into_owned(self) -> Self::Owned { NthOfSelectorData(self.0, self.1.into_owned()) } } impl<'i, Impl: SelectorImpl<'i>> NthOfSelectorData<'i, Impl> { /// Returns selector data for :nth-{,last-}{child,of-type}(An+B [of S]) #[inline] pub fn new(nth_data: NthSelectorData, selectors: Box<[Selector<'i, Impl>]>) -> Self { Self(nth_data, selectors) } /// Returns the An+B part of the selector #[inline] pub fn nth_data(&self) -> &NthSelectorData { &self.0 } /// Returns the selector list part of the selector #[inline] pub fn selectors(&self) -> &[Selector<'i, Impl>] { &*self.1 } pub fn clone_selectors(&self) -> Box<[Selector<'i, Impl>]> { self.1.clone() } } /// A CSS simple selector or combinator. We store both in the same enum for /// optimal packing and cache performance, see [1]. /// /// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1357973 #[derive(Clone, PartialEq, Eq, Hash)] pub enum Component<'i, Impl: SelectorImpl<'i>> { Combinator(Combinator), ExplicitAnyNamespace, ExplicitNoNamespace, DefaultNamespace(Impl::NamespaceUrl), Namespace(Impl::NamespacePrefix, Impl::NamespaceUrl), ExplicitUniversalType, LocalName(LocalName<'i, Impl>), ID(Impl::Identifier), Class(Impl::Identifier), AttributeInNoNamespaceExists { local_name: Impl::LocalName, local_name_lower: Impl::LocalName, }, // Used only when local_name is already lowercase. AttributeInNoNamespace { local_name: Impl::LocalName, operator: AttrSelectorOperator, value: Impl::AttrValue, case_sensitivity: ParsedCaseSensitivity, never_matches: bool, }, // Use a Box in the less common cases with more data to keep size_of::() small. AttributeOther(Box>), /// Pseudo-classes Negation(Box<[Selector<'i, Impl>]>), Root, Empty, Scope, Nth(NthSelectorData), NthOf(NthOfSelectorData<'i, Impl>), NonTSPseudoClass(Impl::NonTSPseudoClass), /// 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<'i, Impl>), /// The `::part` pseudo-element. /// https://drafts.csswg.org/css-shadow-parts/#part Part(Box<[Impl::Identifier]>), /// 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. /// /// See https://github.com/w3c/csswg-drafts/issues/2158 Host(Option>), /// The `:where` pseudo-class. /// /// https://drafts.csswg.org/selectors/#zero-matches /// /// The inner argument is conceptually a SelectorList, but we move the /// selectors to the heap to keep Component small. Where(Box<[Selector<'i, Impl>]>), /// The `:is` pseudo-class. /// /// https://drafts.csswg.org/selectors/#matches-pseudo /// /// Same comment as above re. the argument. Is(Box<[Selector<'i, Impl>]>), Any(Impl::VendorPrefix, Box<[Selector<'i, Impl>]>), /// The `:has` pseudo-class. /// /// https://www.w3.org/TR/selectors/#relational Has(Box<[Selector<'i, Impl>]>), /// An implementation-dependent pseudo-element selector. PseudoElement(Impl::PseudoElement), /// A nesting selector: /// /// https://drafts.csswg.org/css-nesting-1/#nest-selector /// /// NOTE: This is a lightningcss addition. Nesting, } #[cfg(feature = "into_owned")] impl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for Component<'i, Impl> where Impl: static_self::IntoOwned<'any, Owned = NewSel>, NewSel: SelectorImpl<'any>, Impl::NamespaceUrl: static_self::IntoOwned<'any, Owned = NewSel::NamespaceUrl>, Impl::NamespacePrefix: static_self::IntoOwned<'any, Owned = NewSel::NamespacePrefix>, Impl::Identifier: static_self::IntoOwned<'any, Owned = NewSel::Identifier>, Impl::LocalName: static_self::IntoOwned<'any, Owned = NewSel::LocalName>, Impl::AttrValue: static_self::IntoOwned<'any, Owned = NewSel::AttrValue>, Impl::NonTSPseudoClass: static_self::IntoOwned<'any, Owned = NewSel::NonTSPseudoClass>, Impl::PseudoElement: static_self::IntoOwned<'any, Owned = NewSel::PseudoElement>, Impl::VendorPrefix: static_self::IntoOwned<'any, Owned = NewSel::VendorPrefix>, { type Owned = Component<'any, NewSel>; fn into_owned(self) -> Self::Owned { match self { Component::Combinator(c) => Component::Combinator(c.into_owned()), Component::ExplicitAnyNamespace => Component::ExplicitAnyNamespace, Component::ExplicitNoNamespace => Component::ExplicitNoNamespace, Component::DefaultNamespace(c) => Component::DefaultNamespace(c.into_owned()), Component::Namespace(a, b) => Component::Namespace(a.into_owned(), b.into_owned()), Component::ExplicitUniversalType => Component::ExplicitUniversalType, Component::LocalName(c) => Component::LocalName(c.into_owned()), Component::ID(c) => Component::ID(c.into_owned()), Component::Class(c) => Component::Class(c.into_owned()), Component::AttributeInNoNamespaceExists { local_name, local_name_lower, } => Component::AttributeInNoNamespaceExists { local_name: local_name.into_owned(), local_name_lower: local_name_lower.into_owned(), }, Component::AttributeInNoNamespace { local_name, operator, value, case_sensitivity, never_matches, } => { let value = value.into_owned(); Component::AttributeInNoNamespace { local_name: local_name.into_owned(), operator, value, case_sensitivity, never_matches, } } Component::AttributeOther(c) => Component::AttributeOther(c.into_owned()), Component::Negation(c) => Component::Negation(c.into_owned()), Component::Root => Component::Root, Component::Empty => Component::Empty, Component::Scope => Component::Scope, Component::Nth(c) => Component::Nth(c.into_owned()), Component::NthOf(c) => Component::NthOf(c.into_owned()), Component::NonTSPseudoClass(c) => Component::NonTSPseudoClass(c.into_owned()), Component::Slotted(c) => Component::Slotted(c.into_owned()), Component::Part(c) => Component::Part(c.into_owned()), Component::Host(c) => Component::Host(c.into_owned()), Component::Where(c) => Component::Where(c.into_owned()), Component::Is(c) => Component::Is(c.into_owned()), Component::Any(a, b) => Component::Any(a.into_owned(), b.into_owned()), Component::Has(c) => Component::Has(c.into_owned()), Component::PseudoElement(c) => Component::PseudoElement(c.into_owned()), Component::Nesting => Component::Nesting, } } } impl<'i, Impl: SelectorImpl<'i>> Component<'i, Impl> { /// Returns true if this is a combinator. pub fn is_combinator(&self) -> bool { matches!(*self, Component::Combinator(_)) } /// Returns the value as a combinator if applicable, None otherwise. pub fn as_combinator(&self) -> Option { match *self { Component::Combinator(c) => Some(c), _ => None, } } /// Whether this component is valid after a pseudo-element. Only intended /// for sanity-checking. pub fn maybe_allowed_after_pseudo_element(&self) -> bool { match *self { Component::NonTSPseudoClass(..) => true, Component::Negation(ref selectors) | Component::Is(ref selectors) | Component::Where(ref selectors) => { selectors .iter() .all(|selector| selector.iter_raw_match_order().all(|c| c.maybe_allowed_after_pseudo_element())) } _ => false, } } /// Whether a given selector should match for stateless pseudo-elements. /// /// This is a bit subtle: Only selectors that return true in /// `maybe_allowed_after_pseudo_element` should end up here, and /// `NonTSPseudoClass` never matches (as it is a stateless pseudo after /// all). fn matches_for_stateless_pseudo_element(&self) -> bool { debug_assert!( self.maybe_allowed_after_pseudo_element(), "Someone messed up pseudo-element parsing: {:?}", *self ); match *self { Component::Negation(ref selectors) => !selectors.iter().all(|selector| { selector .iter_raw_match_order() .all(|c| c.matches_for_stateless_pseudo_element()) }), Component::Is(ref selectors) | Component::Where(ref selectors) => selectors.iter().any(|selector| { selector .iter_raw_match_order() .all(|c| c.matches_for_stateless_pseudo_element()) }), _ => false, } } pub fn visit(&self, visitor: &mut V) -> bool where V: SelectorVisitor<'i, Impl = Impl>, { use self::Component::*; if !visitor.visit_simple_selector(self) { return false; } match *self { Slotted(ref selector) => { if !selector.visit(visitor) { return false; } } Host(Some(ref selector)) => { if !selector.visit(visitor) { return false; } } AttributeInNoNamespaceExists { ref local_name, ref local_name_lower, } => { if !visitor.visit_attribute_selector( &NamespaceConstraint::Specific(&namespace_empty_string::()), local_name, local_name_lower, ) { return false; } } AttributeInNoNamespace { ref local_name, never_matches, .. } if !never_matches => { if !visitor.visit_attribute_selector( &NamespaceConstraint::Specific(&namespace_empty_string::()), local_name, local_name, ) { return false; } } AttributeOther(ref attr_selector) if !attr_selector.never_matches => { let empty_string; let namespace = match attr_selector.namespace() { Some(ns) => ns, None => { empty_string = crate::parser::namespace_empty_string::(); NamespaceConstraint::Specific(&empty_string) } }; if !visitor.visit_attribute_selector( &namespace, &attr_selector.local_name, &attr_selector.local_name_lower, ) { return false; } } NonTSPseudoClass(ref pseudo_class) => { if !pseudo_class.visit(visitor) { return false; } } Negation(ref list) | Is(ref list) | Where(ref list) => { if !visitor.visit_selector_list(&list) { return false; } } NthOf(ref nth_of_data) => { if !visitor.visit_selector_list(nth_of_data.selectors()) { return false; } } _ => {} } true } } #[derive(Clone, Eq, PartialEq, Hash)] pub struct LocalName<'i, Impl: SelectorImpl<'i>> { pub name: Impl::LocalName, pub lower_name: Impl::LocalName, } #[cfg(feature = "into_owned")] impl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for LocalName<'i, Impl> where Impl: static_self::IntoOwned<'any, Owned = NewSel>, NewSel: SelectorImpl<'any>, Impl::LocalName: static_self::IntoOwned<'any, Owned = NewSel::LocalName>, { type Owned = LocalName<'any, NewSel>; fn into_owned(self) -> Self::Owned { LocalName { name: self.name.into_owned(), lower_name: self.lower_name.into_owned(), } } } impl<'i, Impl: SelectorImpl<'i>> Debug for Selector<'i, Impl> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("Selector(")?; self.to_css(f)?; write!(f, ", specificity = 0x{:x})", self.specificity()) } } impl<'i, Impl: SelectorImpl<'i>> Debug for Component<'i, Impl> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) } } impl<'i, Impl: SelectorImpl<'i>> Debug for AttrSelectorWithOptionalNamespace<'i, Impl> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) } } impl<'i, Impl: SelectorImpl<'i>> Debug for LocalName<'i, Impl> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) } } #[cfg(feature = "serde")] impl<'i, Impl: SelectorImpl<'i>> serde::Serialize for LocalName<'i, Impl> where Impl::LocalName: serde::Serialize, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.name.serialize(serializer) } } #[cfg(feature = "serde")] impl<'i, 'de: 'i, Impl: SelectorImpl<'i>> serde::Deserialize<'de> for LocalName<'i, Impl> where Impl::LocalName: serde::Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let name = Impl::LocalName::deserialize(deserializer)?; let lower_name = to_ascii_lowercase(name.as_ref().to_string().into()).into(); Ok(LocalName { name, lower_name }) } } fn serialize_selector_list<'a, 'i: 'a, Impl, I, W>(iter: I, dest: &mut W) -> fmt::Result where Impl: SelectorImpl<'i>, I: Iterator>, W: fmt::Write, { let mut first = true; for selector in iter { if !first { dest.write_str(", ")?; } first = false; selector.to_css(dest)?; } Ok(()) } impl<'i, Impl: SelectorImpl<'i>> ToCss for SelectorList<'i, Impl> { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, { serialize_selector_list(self.0.iter(), dest) } } impl<'i, Impl: SelectorImpl<'i>> fmt::Display for SelectorList<'i, Impl> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { Impl::to_css(self, f) } } impl<'i, Impl: SelectorImpl<'i>> ToCss for Selector<'i, Impl> { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, { // Compound selectors invert the order of their contents, so we need to // undo that during serialization. // // This two-iterator strategy involves walking over the selector twice. // We could do something more clever, but selector serialization probably // isn't hot enough to justify it, and the stringification likely // dominates anyway. // // NB: A parse-order iterator is a Rev<>, which doesn't expose as_slice(), // which we need for |split|. So we split by combinators on a match-order // sequence and then reverse. let mut combinators = self.iter_raw_match_order().rev().filter_map(|x| x.as_combinator()); let compound_selectors = self.iter_raw_match_order().as_slice().split(|x| x.is_combinator()).rev(); let mut combinators_exhausted = false; for compound in compound_selectors { debug_assert!(!combinators_exhausted); // https://drafts.csswg.org/cssom/#serializing-selectors if compound.is_empty() { continue; } // 1. If there is only one simple selector in the compound selectors // which is a universal selector, append the result of // serializing the universal selector to s. // // Check if `!compound.empty()` first--this can happen if we have // something like `... > ::before`, because we store `>` and `::` // both as combinators internally. // // If we are in this case, after we have serialized the universal // selector, we skip Step 2 and continue with the algorithm. let (can_elide_namespace, first_non_namespace) = match compound[0] { Component::ExplicitAnyNamespace | Component::ExplicitNoNamespace | Component::Namespace(..) => (false, 1), Component::DefaultNamespace(..) => (true, 1), _ => (true, 0), }; let mut perform_step_2 = true; let next_combinator = combinators.next(); if first_non_namespace == compound.len() - 1 { match (next_combinator, &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, just like // Combinator::SlotAssignment, don't exist in the // spec. (Some(Combinator::PseudoElement), _) | (Some(Combinator::SlotAssignment), _) => (), (_, &Component::ExplicitUniversalType) => { // Iterate over everything so we serialize the namespace // too. for simple in compound.iter() { simple.to_css(dest)?; } // Skip step 2, which is an "otherwise". perform_step_2 = false; } _ => (), } } // 2. Otherwise, for each simple selector in the compound selectors // that is not a universal selector of which the namespace prefix // maps to a namespace that is not the default namespace // serialize the simple selector and append the result to s. // // See https://github.com/w3c/csswg-drafts/issues/1606, which is // proposing to change this to match up with the behavior asserted // in cssom/serialize-namespaced-type-selectors.html, which the // following code tries to match. if perform_step_2 { for simple in compound.iter() { if let Component::ExplicitUniversalType = *simple { // Can't have a namespace followed by a pseudo-element // selector followed by a universal selector in the same // compound selector, so we don't have to worry about the // real namespace being in a different `compound`. if can_elide_namespace { continue; } } simple.to_css(dest)?; } } // 3. If this is not the last part of the chain of the selector // append a single SPACE (U+0020), followed by the combinator // ">", "+", "~", ">>", "||", as appropriate, followed by another // single SPACE (U+0020) if the combinator was not whitespace, to // s. match next_combinator { Some(c) => c.to_css(dest)?, None => combinators_exhausted = true, }; // 4. If this is the last part of the chain of the selector and // there is a pseudo-element, append "::" followed by the name of // the pseudo-element, to s. // // (we handle this above) } Ok(()) } } impl ToCss for Combinator { fn to_css(&self, dest: &mut W) -> fmt::Result 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::DeepDescendant => dest.write_str(" >>> "), Combinator::Deep => dest.write_str(" /deep/ "), Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => Ok(()), } } } impl<'i, Impl: SelectorImpl<'i>> ToCss for Component<'i, Impl> { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, { use self::Component::*; 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_names) => { dest.write_str("::part(")?; for (i, name) in part_names.iter().enumerate() { if i != 0 { dest.write_char(' ')?; } name.to_css(dest)?; } dest.write_char(')') } PseudoElement(ref p) => p.to_css(dest), ID(ref s) => { dest.write_char('#')?; s.to_css(dest) } Class(ref s) => { dest.write_char('.')?; s.to_css(dest) } LocalName(ref s) => s.to_css(dest), ExplicitUniversalType => dest.write_char('*'), DefaultNamespace(_) => Ok(()), ExplicitNoNamespace => dest.write_char('|'), ExplicitAnyNamespace => dest.write_str("*|"), Namespace(ref prefix, _) => { prefix.to_css(dest)?; dest.write_char('|') } AttributeInNoNamespaceExists { ref local_name, .. } => { dest.write_char('[')?; local_name.to_css(dest)?; dest.write_char(']') } AttributeInNoNamespace { ref local_name, operator, ref value, case_sensitivity, .. } => { dest.write_char('[')?; local_name.to_css(dest)?; operator.to_css(dest)?; value.to_css(dest)?; match case_sensitivity { ParsedCaseSensitivity::CaseSensitive | ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {} ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?, } dest.write_char(']') } AttributeOther(ref attr_selector) => attr_selector.to_css(dest), // Pseudo-classes Root => dest.write_str(":root"), Empty => dest.write_str(":empty"), Scope => dest.write_str(":scope"), Host(ref selector) => { dest.write_str(":host")?; if let Some(ref selector) = *selector { dest.write_char('(')?; selector.to_css(dest)?; dest.write_char(')')?; } Ok(()) } Nth(ref nth_data) => { nth_data.write_start(dest, nth_data.is_function())?; if nth_data.is_function() { nth_data.write_affine(dest)?; dest.write_char(')')?; } Ok(()) } NthOf(ref nth_of_data) => { let nth_data = nth_of_data.nth_data(); nth_data.write_start(dest, true)?; debug_assert!( nth_data.is_function, "A selector must be a function to hold An+B notation" ); nth_data.write_affine(dest)?; debug_assert!( matches!(nth_data.ty, NthType::Child | NthType::LastChild), "Only :nth-child or :nth-last-child can be of a selector list" ); debug_assert!( !nth_of_data.selectors().is_empty(), "The selector list should not be empty" ); dest.write_str(" of ")?; serialize_selector_list(nth_of_data.selectors().iter(), dest)?; dest.write_char(')') } Is(ref list) | Where(ref list) | Negation(ref list) | Has(ref list) | Any(_, ref list) => { match *self { Where(..) => dest.write_str(":where(")?, Is(..) => dest.write_str(":is(")?, Negation(..) => dest.write_str(":not(")?, Has(..) => dest.write_str(":has(")?, Any(ref prefix, _) => { dest.write_char(':')?; prefix.to_css(dest)?; dest.write_str("any(")?; } _ => unreachable!(), } serialize_selector_list(list.iter(), dest)?; dest.write_str(")") } NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest), Nesting => dest.write_char('&'), } } } impl<'i, Impl: SelectorImpl<'i>> ToCss for AttrSelectorWithOptionalNamespace<'i, Impl> { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, { dest.write_char('[')?; match self.namespace { Some(NamespaceConstraint::Specific((ref prefix, _))) => { prefix.to_css(dest)?; dest.write_char('|')? } Some(NamespaceConstraint::Any) => dest.write_str("*|")?, None => {} } self.local_name.to_css(dest)?; match self.operation { ParsedAttrSelectorOperation::Exists => {} ParsedAttrSelectorOperation::WithValue { operator, case_sensitivity, ref expected_value, } => { operator.to_css(dest)?; expected_value.to_css(dest)?; match case_sensitivity { ParsedCaseSensitivity::CaseSensitive | ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {} ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?, } } } dest.write_char(']') } } impl<'i, Impl: SelectorImpl<'i>> ToCss for LocalName<'i, Impl> { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, { self.name.to_css(dest) } } /// Build up a Selector. /// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ; /// /// `Err` means invalid selector. fn parse_selector<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, state: &mut SelectorParsingState, nesting_requirement: NestingRequirement, ) -> Result, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, { if nesting_requirement == NestingRequirement::Prefixed { let state = input.state(); if !input.expect_delim('&').is_ok() { return Err(input.new_custom_error(SelectorParseErrorKind::MissingNestingPrefix)); } input.reset(&state); } let mut builder = SelectorBuilder::default(); 'outer_loop: loop { // Parse a sequence of simple selectors. let empty = parse_compound_selector(parser, state, input, &mut builder)?; if empty { return Err(input.new_custom_error(if builder.has_combinators() { SelectorParseErrorKind::DanglingCombinator } else { SelectorParseErrorKind::EmptySelector })); } if state.intersects(SelectorParsingState::AFTER_PSEUDO) { // Input should be exhausted here. let source_location = input.current_source_location(); if let Ok(next) = input.next() { let next = next.clone(); return Err( source_location.new_custom_error(SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(next)), ); } break; } // Parse a combinator. let combinator; let mut any_whitespace = false; loop { let before_this_token = input.state(); match input.next_including_whitespace() { Err(_e) => break 'outer_loop, Ok(&Token::WhiteSpace(_)) => any_whitespace = true, Ok(&Token::Delim('>')) => { if parser.deep_combinator_enabled() && input .try_parse(|input| { input.expect_delim('>')?; input.expect_delim('>') }) .is_ok() { combinator = Combinator::DeepDescendant; } else { combinator = Combinator::Child; } break; } Ok(&Token::Delim('+')) => { combinator = Combinator::NextSibling; break; } Ok(&Token::Delim('~')) => { combinator = Combinator::LaterSibling; break; } Ok(&Token::Delim('/')) if parser.deep_combinator_enabled() => { if input .try_parse(|input| { input.expect_ident_matching("deep")?; input.expect_delim('/') }) .is_ok() { combinator = Combinator::Deep; break; } else { break 'outer_loop; } } Ok(_) => { input.reset(&before_this_token); if any_whitespace { combinator = Combinator::Descendant; break; } else { break 'outer_loop; } } } } if !state.allows_combinators() { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); } builder.push_combinator(combinator); } if !state.contains(SelectorParsingState::AFTER_NESTING) { match nesting_requirement { NestingRequirement::Implicit => { builder.add_nesting_prefix(); } NestingRequirement::Contained | NestingRequirement::Prefixed => { return Err(input.new_custom_error(SelectorParseErrorKind::MissingNestingSelector)); } _ => {} } } let has_pseudo_element = state .intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT | SelectorParsingState::AFTER_UNKNOWN_PSEUDO_ELEMENT); let slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED); let part = state.intersects(SelectorParsingState::AFTER_PART); let (spec, components) = builder.build(has_pseudo_element, slotted, part); Ok(Selector(spec, components)) } impl<'i, Impl: SelectorImpl<'i>> Selector<'i, Impl> { /// Parse a selector, without any pseudo-element. #[inline] pub fn parse<'t, P>(parser: &P, input: &mut CssParser<'i, 't>) -> Result> where P: Parser<'i, Impl = Impl>, { parse_selector( parser, input, &mut SelectorParsingState::empty(), NestingRequirement::None, ) } } fn parse_relative_selector<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, state: &mut SelectorParsingState, mut nesting_requirement: NestingRequirement, ) -> Result, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, { // https://www.w3.org/TR/selectors-4/#parse-relative-selector let s = input.state(); let combinator = match input.next()? { Token::Delim('>') => Some(Combinator::Child), Token::Delim('+') => Some(Combinator::NextSibling), Token::Delim('~') => Some(Combinator::LaterSibling), _ => { input.reset(&s); None } }; let scope = if nesting_requirement == NestingRequirement::Implicit { Component::Nesting } else { Component::Scope }; if combinator.is_some() { nesting_requirement = NestingRequirement::None; } let mut selector = parse_selector(parser, input, state, nesting_requirement)?; if let Some(combinator) = combinator { // https://www.w3.org/TR/selectors/#absolutizing selector.1.push(Component::Combinator(combinator)); selector.1.push(scope); } Ok(selector) } /// * `Err(())`: Invalid selector, abort /// * `Ok(false)`: Not a type selector, could be something else. `input` was not consumed. /// * `Ok(true)`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`) fn parse_type_selector<'i, 't, P, Impl, S>( parser: &P, input: &mut CssParser<'i, 't>, state: SelectorParsingState, sink: &mut S, ) -> Result> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, S: Push>, { match parse_qualified_name(parser, input, /* in_attr_selector = */ false) { Err(ParseError { kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput), .. }) | Ok(OptionalQName::None(_)) => Ok(false), Ok(OptionalQName::Some(namespace, local_name)) => { if state.intersects(SelectorParsingState::AFTER_PSEUDO) { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); } match namespace { QNamePrefix::ImplicitAnyNamespace => {} QNamePrefix::ImplicitDefaultNamespace(url) => sink.push(Component::DefaultNamespace(url)), QNamePrefix::ExplicitNamespace(prefix, url) => sink.push(match parser.default_namespace() { Some(ref default_url) if url == *default_url => Component::DefaultNamespace(url), _ => Component::Namespace(prefix, url), }), QNamePrefix::ExplicitNoNamespace => sink.push(Component::ExplicitNoNamespace), QNamePrefix::ExplicitAnyNamespace => { // Element type selectors that have no namespace // component (no namespace separator) represent elements // without regard to the element's namespace (equivalent // to "*|") unless a default namespace has been declared // for namespaced selectors (e.g. in CSS, in the style // sheet). If a default namespace has been declared, // such selectors will represent only elements in the // default namespace. // -- Selectors § 6.1.1 // So we'll have this act the same as the // QNamePrefix::ImplicitAnyNamespace case. // For lightning css this logic was removed, should be handled when matching. sink.push(Component::ExplicitAnyNamespace) } QNamePrefix::ImplicitNoNamespace => { unreachable!() // Not returned with in_attr_selector = false } } match local_name { Some(name) => sink.push(Component::LocalName(LocalName { lower_name: to_ascii_lowercase(name.clone()).into(), name: name.into(), })), None => sink.push(Component::ExplicitUniversalType), } Ok(true) } Err(e) => Err(e), } } #[derive(Debug)] enum SimpleSelectorParseResult<'i, Impl: SelectorImpl<'i>> { SimpleSelector(Component<'i, Impl>), PseudoElement(Impl::PseudoElement), SlottedPseudo(Selector<'i, Impl>), PartPseudo(Box<[Impl::Identifier]>), } #[derive(Debug)] enum QNamePrefix<'i, Impl: SelectorImpl<'i>> { 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` ExplicitAnyNamespace, // `*|foo` ExplicitNamespace(Impl::NamespacePrefix, Impl::NamespaceUrl), // `prefix|foo` } enum OptionalQName<'i, Impl: SelectorImpl<'i>> { Some(QNamePrefix<'i, Impl>, Option>), None(Token<'i>), } /// * `Err(())`: Invalid selector, abort /// * `Ok(None(token))`: Not a simple selector, could be something else. `input` was not consumed, /// but the token is still returned. /// * `Ok(Some(namespace, local_name))`: `None` for the local name means a `*` universal selector fn parse_qualified_name<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, in_attr_selector: bool, ) -> Result, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, { let default_namespace = |local_name| { let namespace = match parser.default_namespace() { Some(url) => QNamePrefix::ImplicitDefaultNamespace(url), None => QNamePrefix::ImplicitAnyNamespace, }; Ok(OptionalQName::Some(namespace, local_name)) }; let explicit_namespace = |input: &mut CssParser<'i, 't>, namespace| { let location = input.current_source_location(); match input.next_including_whitespace() { Ok(&Token::Delim('*')) if !in_attr_selector => Ok(OptionalQName::Some(namespace, None)), Ok(&Token::Ident(ref local_name)) => Ok(OptionalQName::Some(namespace, Some(local_name.clone()))), Ok(t) if in_attr_selector => { let e = SelectorParseErrorKind::InvalidQualNameInAttr(t.clone()); Err(location.new_custom_error(e)) } Ok(t) => Err(location.new_custom_error(SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t.clone()))), Err(e) => Err(e.into()), } }; let start = input.state(); // FIXME: remove clone() when lifetimes are non-lexical match input.next_including_whitespace().map(|t| t.clone()) { Ok(Token::Ident(value)) => { let after_ident = input.state(); match input.next_including_whitespace() { Ok(&Token::Delim('|')) => { let prefix = value.clone().into(); let result = parser.namespace_for_prefix(&prefix); let url = result.ok_or( after_ident .source_location() .new_custom_error(SelectorParseErrorKind::ExpectedNamespace(value)), )?; explicit_namespace(input, QNamePrefix::ExplicitNamespace(prefix, url)) } _ => { input.reset(&after_ident); if in_attr_selector { Ok(OptionalQName::Some(QNamePrefix::ImplicitNoNamespace, Some(value))) } else { default_namespace(Some(value)) } } } } Ok(Token::Delim('*')) => { let after_star = input.state(); // FIXME: remove clone() when lifetimes are non-lexical match input.next_including_whitespace().map(|t| t.clone()) { Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitAnyNamespace), result => { input.reset(&after_star); if in_attr_selector { match result { Ok(t) => Err( after_star .source_location() .new_custom_error(SelectorParseErrorKind::ExpectedBarInAttr(t)), ), Err(e) => Err(e.into()), } } else { default_namespace(None) } } } } Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitNoNamespace), Ok(t) => { input.reset(&start); Ok(OptionalQName::None(t)) } Err(e) => { input.reset(&start); Err(e.into()) } } } fn parse_attribute_selector<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, ) -> Result, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, { let namespace; let local_name; input.skip_whitespace(); match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? { OptionalQName::None(t) => { return Err(input.new_custom_error(SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t))); } OptionalQName::Some(_, None) => unreachable!(), OptionalQName::Some(ns, Some(ln)) => { local_name = ln; namespace = match ns { QNamePrefix::ImplicitNoNamespace | QNamePrefix::ExplicitNoNamespace => None, QNamePrefix::ExplicitNamespace(prefix, url) => Some(NamespaceConstraint::Specific((prefix, url))), QNamePrefix::ExplicitAnyNamespace => Some(NamespaceConstraint::Any), QNamePrefix::ImplicitAnyNamespace | QNamePrefix::ImplicitDefaultNamespace(_) => { unreachable!() // Not returned with in_attr_selector = true } } } } let location = input.current_source_location(); let operator = match input.next() { // [foo] Err(_) => { let local_name_lower = to_ascii_lowercase(local_name.clone()).into(); let local_name = local_name.into(); if let Some(namespace) = namespace { return Ok(Component::AttributeOther(Box::new(AttrSelectorWithOptionalNamespace { namespace: Some(namespace), local_name, local_name_lower, operation: ParsedAttrSelectorOperation::Exists, never_matches: false, }))); } else { return Ok(Component::AttributeInNoNamespaceExists { local_name, local_name_lower, }); } } // [foo=bar] Ok(&Token::Delim('=')) => AttrSelectorOperator::Equal, // [foo~=bar] Ok(&Token::IncludeMatch) => AttrSelectorOperator::Includes, // [foo|=bar] Ok(&Token::DashMatch) => AttrSelectorOperator::DashMatch, // [foo^=bar] Ok(&Token::PrefixMatch) => AttrSelectorOperator::Prefix, // [foo*=bar] Ok(&Token::SubstringMatch) => AttrSelectorOperator::Substring, // [foo$=bar] Ok(&Token::SuffixMatch) => AttrSelectorOperator::Suffix, Ok(t) => { return Err( location.new_custom_error(SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t.clone())), ); } }; let value = match input.expect_ident_or_string() { Ok(t) => t.clone(), Err(BasicParseError { kind: BasicParseErrorKind::UnexpectedToken(t), location, }) => return Err(location.new_custom_error(SelectorParseErrorKind::BadValueInAttr(t))), Err(e) => return Err(e.into()), }; let never_matches = match operator { AttrSelectorOperator::Equal | AttrSelectorOperator::DashMatch => false, AttrSelectorOperator::Includes => value.is_empty() || value.contains(SELECTOR_WHITESPACE), AttrSelectorOperator::Prefix | AttrSelectorOperator::Substring | AttrSelectorOperator::Suffix => { value.is_empty() } }; let attribute_flags = parse_attribute_flags(input)?; let value = value.into(); let case_sensitivity; // copied from to_ascii_lowercase function, so we can know whether it is already lower case. let (local_name_lower_cow, local_name_is_ascii_lowercase) = if let Some(first_uppercase) = local_name.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { let mut string = local_name.to_string(); string[first_uppercase..].make_ascii_lowercase(); (string.into(), false) } else { (local_name.clone(), true) }; case_sensitivity = attribute_flags.to_case_sensitivity(local_name_lower_cow.as_ref(), namespace.is_some()); let local_name_lower = local_name_lower_cow.into(); let local_name = local_name.into(); if namespace.is_some() || !local_name_is_ascii_lowercase { Ok(Component::AttributeOther(Box::new(AttrSelectorWithOptionalNamespace { namespace, local_name, local_name_lower, never_matches, operation: ParsedAttrSelectorOperation::WithValue { operator, case_sensitivity, expected_value: value, }, }))) } else { Ok(Component::AttributeInNoNamespace { local_name, operator, value, case_sensitivity, never_matches, }) } } /// An attribute selector can have 's' or 'i' as flags, or no flags at all. enum AttributeFlags { // Matching should be case-sensitive ('s' flag). CaseSensitive, // Matching should be case-insensitive ('i' flag). AsciiCaseInsensitive, // No flags. Matching behavior depends on the name of the attribute. CaseSensitivityDependsOnName, } impl AttributeFlags { fn to_case_sensitivity(self, local_name: &str, have_namespace: bool) -> ParsedCaseSensitivity { match self { AttributeFlags::CaseSensitive => ParsedCaseSensitivity::ExplicitCaseSensitive, AttributeFlags::AsciiCaseInsensitive => ParsedCaseSensitivity::AsciiCaseInsensitive, AttributeFlags::CaseSensitivityDependsOnName => { if !have_namespace && include!(concat!(env!("OUT_DIR"), "/ascii_case_insensitive_html_attributes.rs")).contains(local_name) { ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument } else { ParsedCaseSensitivity::CaseSensitive } } } } } fn parse_attribute_flags<'i, 't>(input: &mut CssParser<'i, 't>) -> Result> { let location = input.current_source_location(); let token = match input.next() { Ok(t) => t, Err(..) => { // Selectors spec says language-defined; HTML says it depends on the // exact attribute name. return Ok(AttributeFlags::CaseSensitivityDependsOnName); } }; let ident = match *token { Token::Ident(ref i) => i, ref other => return Err(location.new_basic_unexpected_token_error(other.clone())), }; Ok(match_ignore_ascii_case! { ident, "i" => AttributeFlags::AsciiCaseInsensitive, "s" => AttributeFlags::CaseSensitive, _ => return Err(location.new_basic_unexpected_token_error(token.clone())), }) } /// Level 3: Parse **one** simple_selector. (Though we might insert a second /// implied "|*" type selector.) fn parse_negation<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, state: &mut SelectorParsingState, ) -> Result, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, { let mut child_state = *state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS; let list = SelectorList::parse_with_state( parser, input, &mut child_state, ParseErrorRecovery::DiscardList, NestingRequirement::None, )?; if child_state.contains(SelectorParsingState::AFTER_NESTING) { state.insert(SelectorParsingState::AFTER_NESTING) } Ok(Component::Negation(list.0.into_vec().into_boxed_slice())) } /// simple_selector_sequence /// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]* /// | [ HASH | class | attrib | pseudo | negation ]+ /// /// `Err(())` means invalid selector. /// `Ok(true)` is an empty selector fn parse_compound_selector<'i, 't, P, Impl>( parser: &P, state: &mut SelectorParsingState, input: &mut CssParser<'i, 't>, builder: &mut SelectorBuilder<'i, Impl>, ) -> Result> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, { input.skip_whitespace(); let mut empty = true; if parser.is_nesting_allowed() && input.try_parse(|input| input.expect_delim('&')).is_ok() { state.insert(SelectorParsingState::AFTER_NESTING); builder.push_simple_selector(Component::Nesting); empty = false; } if parse_type_selector(parser, input, *state, builder)? { empty = false; } loop { let result = match parse_one_simple_selector(parser, input, state)? { None => break, Some(result) => result, }; if empty { if let Some(url) = parser.default_namespace() { // If there was no explicit type selector, but there is a // default namespace, there is an implicit "|*" type // selector. Except for :host() or :not() / :is() / :where(), // where we ignore it. // // https://drafts.csswg.org/css-scoping/#host-element-in-tree: // // When considered within its own shadow trees, the shadow // host is featureless. Only the :host, :host(), and // :host-context() pseudo-classes are allowed to match it. // // https://drafts.csswg.org/selectors-4/#featureless: // // A featureless element does not match any selector at all, // except those it is explicitly defined to match. If a // given selector is allowed to match a featureless element, // it must do so while ignoring the default namespace. // // https://drafts.csswg.org/selectors-4/#matches // // Default namespace declarations do not affect the compound // selector representing the subject of any selector within // a :is() pseudo-class, unless that compound selector // contains an explicit universal selector or type selector. // // (Similar quotes for :where() / :not()) // let ignore_default_ns = state.intersects(SelectorParsingState::SKIP_DEFAULT_NAMESPACE) || matches!(result, SimpleSelectorParseResult::SimpleSelector(Component::Host(..))); if !ignore_default_ns { builder.push_simple_selector(Component::DefaultNamespace(url)); } } } empty = false; match result { SimpleSelectorParseResult::SimpleSelector(s) => { builder.push_simple_selector(s); } SimpleSelectorParseResult::PartPseudo(part_names) => { state.insert(SelectorParsingState::AFTER_PART); builder.push_combinator(Combinator::Part); builder.push_simple_selector(Component::Part(part_names)); } SimpleSelectorParseResult::SlottedPseudo(selector) => { state.insert(SelectorParsingState::AFTER_SLOTTED); builder.push_combinator(Combinator::SlotAssignment); builder.push_simple_selector(Component::Slotted(selector)); } SimpleSelectorParseResult::PseudoElement(p) => { if !p.is_unknown() { state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT); builder.push_combinator(Combinator::PseudoElement); } else { state.insert(SelectorParsingState::AFTER_UNKNOWN_PSEUDO_ELEMENT); } if !p.accepts_state_pseudo_classes() { state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT); } if p.is_webkit_scrollbar() { state.insert(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR); } if p.is_view_transition() { state.insert(SelectorParsingState::AFTER_VIEW_TRANSITION); } builder.push_simple_selector(Component::PseudoElement(p)); } } } Ok(empty) } fn parse_is_or_where<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, state: &mut SelectorParsingState, component: impl FnOnce(Box<[Selector<'i, Impl>]>) -> Component<'i, Impl>, ) -> Result, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, { debug_assert!(parser.parse_is_and_where()); // https://drafts.csswg.org/selectors/#matches-pseudo: // // Pseudo-elements cannot be represented by the matches-any // pseudo-class; they are not valid within :is(). // let mut child_state = *state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS; let inner = SelectorList::parse_with_state( parser, input, &mut child_state, parser.is_and_where_error_recovery(), NestingRequirement::None, )?; if child_state.contains(SelectorParsingState::AFTER_NESTING) { state.insert(SelectorParsingState::AFTER_NESTING) } Ok(component(inner.0.into_vec().into_boxed_slice())) } fn parse_has<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, state: &mut SelectorParsingState, ) -> Result, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, { let mut child_state = *state; let inner = SelectorList::parse_relative_with_state( parser, input, &mut child_state, parser.is_and_where_error_recovery(), NestingRequirement::None, )?; if child_state.contains(SelectorParsingState::AFTER_NESTING) { state.insert(SelectorParsingState::AFTER_NESTING) } Ok(Component::Has(inner.0.into_vec().into_boxed_slice())) } fn parse_functional_pseudo_class<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, name: CowRcStr<'i>, state: &mut SelectorParsingState, ) -> Result, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, { match_ignore_ascii_case! { &name, "nth-child" => return parse_nth_pseudo_class(parser, input, *state, NthType::Child), "nth-of-type" => return parse_nth_pseudo_class(parser, input, *state, NthType::OfType), "nth-last-child" => return parse_nth_pseudo_class(parser, input, *state, NthType::LastChild), "nth-last-of-type" => return parse_nth_pseudo_class(parser, input, *state, NthType::LastOfType), "nth-col" => return parse_nth_pseudo_class(parser, input, *state, NthType::Col), "nth-last-col" => return parse_nth_pseudo_class(parser, input, *state, NthType::LastCol), "is" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Is), "where" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Where), "has" => return parse_has(parser, input, state), "host" => { if !state.allows_tree_structural_pseudo_classes() { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); } return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input, state)?))); }, "not" => { return parse_negation(parser, input, state) }, _ => {} } if let Some(prefix) = parser.parse_any_prefix(&name) { return parse_is_or_where(parser, input, state, |selectors| Component::Any(prefix, selectors)); } if !state.allows_custom_functional_pseudo_classes() { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); } P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass) } fn parse_nth_pseudo_class<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, state: SelectorParsingState, ty: NthType, ) -> Result, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, { if !state.allows_tree_structural_pseudo_classes() { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); } let (a, b) = parse_nth(input)?; let nth_data = NthSelectorData { ty, is_function: true, a, b, }; if !ty.allows_of_selector() { return Ok(Component::Nth(nth_data)); } // Try to parse "of ". if input.try_parse(|i| i.expect_ident_matching("of")).is_err() { return Ok(Component::Nth(nth_data)); } // Whitespace between "of" and the selector list is optional // https://github.com/w3c/csswg-drafts/issues/8285 let mut child_state = state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS; let selectors = SelectorList::parse_with_state( parser, input, &mut child_state, ParseErrorRecovery::IgnoreInvalidSelector, NestingRequirement::None, )?; Ok(Component::NthOf(NthOfSelectorData::new( nth_data, selectors.0.into_vec().into_boxed_slice(), ))) } /// 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). 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, _ => false, } } /// Parse a simple selector other than a type selector. /// /// * `Err(())`: Invalid selector, abort /// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed. /// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element fn parse_one_simple_selector<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, state: &mut SelectorParsingState, ) -> Result>, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, { let start = input.state(); let token_location = input.current_source_location(); 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(token_location.new_custom_error( SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::IDHash(id)), )); } let id = Component::ID(id.into()); SimpleSelectorParseResult::SimpleSelector(id) } Token::Delim('.') => { if state.intersects(SelectorParsingState::AFTER_PSEUDO) { return Err(token_location.new_custom_error( SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::Delim('.')), )); } let location = input.current_source_location(); let class = match *input.next_including_whitespace()? { Token::Ident(ref class) => class.clone(), ref t => { let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone()); return Err(location.new_custom_error(e)); } }; let class = Component::Class(class.into()); SimpleSelectorParseResult::SimpleSelector(class) } Token::SquareBracketBlock => { if state.intersects(SelectorParsingState::AFTER_PSEUDO) { return Err(token_location.new_custom_error( SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::SquareBracketBlock), )); } let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?; SimpleSelectorParseResult::SimpleSelector(attr) } 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()), t => (true, t), }; let (name, is_functional) = match next_token { Token::Ident(name) => (name, false), Token::Function(name) => (name, true), t => { let e = SelectorParseErrorKind::PseudoElementExpectedIdent(t); return Err(input.new_custom_error(e)); } }; let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name); if is_pseudo_element { if !state.allows_pseudos() { 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 names = input.parse_nested_block(|input| { let mut result = Vec::with_capacity(1); result.push(input.expect_ident_cloned()?.into()); while !input.is_exhausted() { result.push(input.expect_ident_cloned()?.into()); } Ok(result.into_boxed_slice()) })?; return Ok(Some(SimpleSelectorParseResult::PartPseudo(names))); } 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, state))?; return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector))); } input.parse_nested_block(|input| P::parse_functional_pseudo_element(parser, name, input))? } else { P::parse_pseudo_element(parser, location, name)? }; 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, state))? } else { parse_simple_pseudo_class(parser, location, name, *state)? }; SimpleSelectorParseResult::SimpleSelector(pseudo_class) } } Token::Delim('&') if parser.is_nesting_allowed() => { *state |= SelectorParsingState::AFTER_NESTING; SimpleSelectorParseResult::SimpleSelector(Component::Nesting) } _ => { input.reset(&start); return Ok(None); } })) } fn parse_simple_pseudo_class<'i, P, Impl>( parser: &P, location: SourceLocation, name: CowRcStr<'i>, state: SelectorParsingState, ) -> Result, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl<'i>, { 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::Nth(NthSelectorData::first(/* of_type = */ false))), "last-child" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ false))), "only-child" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ false))), "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::Nth(NthSelectorData::first(/* of_type = */ true))), "last-of-type" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ true))), "only-of-type" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ true))), _ => {}, } } // The view-transition pseudo elements accept the :only-child pseudo class. // https://w3c.github.io/csswg-drafts/css-view-transitions-1/#pseudo-root if state.intersects(SelectorParsingState::AFTER_VIEW_TRANSITION) { match_ignore_ascii_case! { &name, "only-child" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ false))), _ => {} } } let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name)?; if state.intersects(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR) { if !pseudo_class.is_valid_after_webkit_scrollbar() { return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassAfterWebKitScrollbar)); } } else if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) { if !pseudo_class.is_user_action_state() { return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassAfterPseudoElement)); } } else if !pseudo_class.is_valid_before_webkit_scrollbar() { return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassBeforeWebKitScrollbar)); } Ok(Component::NonTSPseudoClass(pseudo_class)) } // NB: pub module in order to access the DummyParser #[cfg(test)] pub mod tests { use super::*; use crate::builder::SelectorFlags; use crate::parser; use cssparser::{serialize_identifier, serialize_string, Parser as CssParser, ParserInput, ToCss}; use std::collections::HashMap; use std::fmt; #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum PseudoClass { Hover, Active, Lang(String), } #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum PseudoElement { Before, After, } impl<'i> parser::PseudoElement<'i> for PseudoElement { type Impl = DummySelectorImpl; fn accepts_state_pseudo_classes(&self) -> bool { true } fn valid_after_slotted(&self) -> bool { true } } impl<'i> parser::NonTSPseudoClass<'i> for PseudoClass { type Impl = DummySelectorImpl; #[inline] 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 { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, { match *self { PseudoClass::Hover => dest.write_str(":hover"), PseudoClass::Active => dest.write_str(":active"), PseudoClass::Lang(ref lang) => { dest.write_str(":lang(")?; serialize_identifier(lang, dest)?; dest.write_char(')') } } } } impl ToCss for PseudoElement { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, { match *self { PseudoElement::Before => dest.write_str("::before"), PseudoElement::After => dest.write_str("::after"), } } } #[derive(Clone, Debug, PartialEq)] pub struct DummySelectorImpl; #[derive(Default)] pub struct DummyParser { default_ns: Option, ns_prefixes: HashMap, } impl DummyParser { fn default_with_namespace(default_ns: DummyAtom) -> DummyParser { DummyParser { default_ns: Some(default_ns), ns_prefixes: Default::default(), } } } impl<'i> SelectorImpl<'i> for DummySelectorImpl { type ExtraMatchingData = (); type AttrValue = DummyAttrValue; type Identifier = DummyAtom; type LocalName = DummyAtom; type NamespaceUrl = DummyAtom; type NamespacePrefix = DummyAtom; type BorrowedLocalName = DummyAtom; type BorrowedNamespaceUrl = DummyAtom; type NonTSPseudoClass = PseudoClass; type PseudoElement = PseudoElement; type VendorPrefix = u8; } #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] pub struct DummyAttrValue(String); impl ToCss for DummyAttrValue { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, { serialize_string(&self.0, dest) } } impl AsRef for DummyAttrValue { fn as_ref(&self) -> &str { self.0.as_ref() } } impl<'a> From<&'a str> for DummyAttrValue { fn from(string: &'a str) -> Self { Self(string.into()) } } impl<'a> From> for DummyAttrValue { fn from(string: std::borrow::Cow<'a, str>) -> Self { Self(string.to_string()) } } impl<'a> From> for DummyAttrValue { fn from(string: CowRcStr<'a>) -> Self { Self(string.to_string()) } } #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] pub struct DummyAtom(String); impl ToCss for DummyAtom { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, { serialize_identifier(&self.0, dest) } } impl From for DummyAtom { fn from(string: String) -> Self { DummyAtom(string) } } impl<'a> From<&'a str> for DummyAtom { fn from(string: &'a str) -> Self { DummyAtom(string.into()) } } impl<'a> From> for DummyAtom { fn from(string: CowRcStr<'a>) -> Self { DummyAtom(string.to_string()) } } impl AsRef for DummyAtom { fn as_ref(&self) -> &str { self.0.as_ref() } } impl<'a> From> for DummyAtom { fn from(string: std::borrow::Cow<'a, str>) -> Self { Self(string.to_string()) } } impl<'i> Parser<'i> for DummyParser { type Impl = DummySelectorImpl; type Error = SelectorParseErrorKind<'i>; fn parse_slotted(&self) -> bool { true } fn parse_is_and_where(&self) -> bool { true } fn is_and_where_error_recovery(&self) -> ParseErrorRecovery { ParseErrorRecovery::DiscardList } fn parse_part(&self) -> bool { true } fn parse_non_ts_pseudo_class( &self, location: SourceLocation, name: CowRcStr<'i>, ) -> Result> { match_ignore_ascii_case! { &name, "hover" => return Ok(PseudoClass::Hover), "active" => return Ok(PseudoClass::Active), _ => {} } Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name))) } fn parse_non_ts_functional_pseudo_class<'t>( &self, name: CowRcStr<'i>, parser: &mut CssParser<'i, 't>, ) -> Result> { match_ignore_ascii_case! { &name, "lang" => { let lang = parser.expect_ident_or_string()?.as_ref().to_owned(); return Ok(PseudoClass::Lang(lang)); }, _ => {} } Err(parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name))) } fn parse_pseudo_element( &self, location: SourceLocation, name: CowRcStr<'i>, ) -> Result> { match_ignore_ascii_case! { &name, "before" => return Ok(PseudoElement::Before), "after" => return Ok(PseudoElement::After), _ => {} } Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name))) } fn default_namespace(&self) -> Option { self.default_ns.clone() } fn namespace_for_prefix(&self, prefix: &DummyAtom) -> Option { self.ns_prefixes.get(prefix).cloned() } } fn parse<'i>(input: &'i str) -> Result, SelectorParseError<'i>> { parse_ns(input, &DummyParser::default()) } // fn parse_expected<'i, 'a>( // input: &'i str, // expected: Option<&'a str>, // ) -> Result, SelectorParseError<'i>> { // parse_ns_expected(input, &DummyParser::default(), expected) // } fn parse_ns<'i>( input: &'i str, parser: &DummyParser, ) -> Result, SelectorParseError<'i>> { parse_ns_expected(input, parser, None) } fn parse_ns_expected<'i, 'a>( input: &'i str, parser: &DummyParser, expected: Option<&'a str>, ) -> Result, SelectorParseError<'i>> { let mut parser_input = ParserInput::new(input); let result = SelectorList::parse( parser, &mut CssParser::new(&mut parser_input), ParseErrorRecovery::DiscardList, NestingRequirement::None, ); if let Ok(ref selectors) = result { assert_eq!(selectors.0.len(), 1); // We can't assume that the serialized parsed selector will equal // the input; for example, if there is no default namespace, '*|foo' // should serialize to 'foo'. assert_eq!( selectors.0[0].to_css_string(), match expected { Some(x) => x, None => input, } ); } result } fn specificity(a: u32, b: u32, c: u32) -> u32 { a << 20 | b << 10 | c } #[test] fn test_empty() { let mut input = ParserInput::new(":empty"); let list = SelectorList::parse( &DummyParser::default(), &mut CssParser::new(&mut input), ParseErrorRecovery::DiscardList, NestingRequirement::None, ); assert!(list.is_ok()); } const MATHML: &str = "http://www.w3.org/1998/Math/MathML"; const SVG: &str = "http://www.w3.org/2000/svg"; #[test] fn test_parsing() { 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 { name: DummyAtom::from("EeÉ"), lower_name: DummyAtom::from("eeÉ"), })], specificity(0, 0, 1), Default::default(), )])) ); assert_eq!( parse("|e"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::ExplicitNoNamespace, Component::LocalName(LocalName { name: DummyAtom::from("e"), lower_name: DummyAtom::from("e"), }), ], specificity(0, 0, 1), Default::default(), )])) ); // When the default namespace is not set, *| should be elided. // https://github.com/servo/servo/pull/17537 // assert_eq!( // parse_expected("*|e", Some("e")), // Ok(SelectorList::from_vec(vec![Selector::from_vec( // vec![Component::LocalName(LocalName { // name: DummyAtom::from("e"), // lower_name: DummyAtom::from("e"), // })], // specificity(0, 0, 1), // Default::default(), // )])) // ); // When the default namespace is set, *| should _not_ be elided (as foo // is no longer equivalent to *|foo--the former is only for foo in the // default namespace). // https://github.com/servo/servo/issues/16020 // assert_eq!( // parse_ns( // "*|e", // &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")) // ), // Ok(SelectorList::from_vec(vec![Selector::from_vec( // vec![ // Component::ExplicitAnyNamespace, // Component::LocalName(LocalName { // name: DummyAtom::from("e"), // lower_name: DummyAtom::from("e"), // }), // ], // specificity(0, 0, 1), // Default::default(), // )])) // ); assert_eq!( parse("*"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::ExplicitUniversalType], specificity(0, 0, 0), Default::default(), )])) ); assert_eq!( parse("|*"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::ExplicitNoNamespace, Component::ExplicitUniversalType,], specificity(0, 0, 0), Default::default(), )])) ); // assert_eq!( // parse_expected("*|*", Some("*")), // Ok(SelectorList::from_vec(vec![Selector::from_vec( // vec![Component::ExplicitUniversalType], // specificity(0, 0, 0), // Default::default(), // )])) // ); assert_eq!( parse_ns( "*|*", &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")) ), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::ExplicitAnyNamespace, Component::ExplicitUniversalType,], specificity(0, 0, 0), Default::default(), )])) ); assert_eq!( parse(".foo:lang(en-US)"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::Class(DummyAtom::from("foo")), Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())), ], specificity(0, 2, 0), Default::default(), )])) ); assert_eq!( parse("#bar"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::ID(DummyAtom::from("bar"))], specificity(1, 0, 0), Default::default(), )])) ); assert_eq!( parse("e.foo#bar"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::LocalName(LocalName { name: DummyAtom::from("e"), lower_name: DummyAtom::from("e"), }), Component::Class(DummyAtom::from("foo")), Component::ID(DummyAtom::from("bar")), ], specificity(1, 1, 1), Default::default(), )])) ); assert_eq!( parse("e.foo #bar"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::LocalName(LocalName { name: DummyAtom::from("e"), lower_name: DummyAtom::from("e"), }), Component::Class(DummyAtom::from("foo")), Component::Combinator(Combinator::Descendant), Component::ID(DummyAtom::from("bar")), ], specificity(1, 1, 1), Default::default(), )])) ); // Default namespace does not apply to attribute selectors // https://github.com/mozilla/servo/pull/1652 let mut parser = DummyParser::default(); assert_eq!( parse_ns("[Foo]", &parser), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::AttributeInNoNamespaceExists { local_name: DummyAtom::from("Foo"), local_name_lower: DummyAtom::from("foo"), }], specificity(0, 1, 0), Default::default(), )])) ); assert!(parse_ns("svg|circle", &parser).is_err()); parser.ns_prefixes.insert(DummyAtom("svg".into()), DummyAtom(SVG.into())); assert_eq!( parse_ns("svg|circle", &parser), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::Namespace(DummyAtom("svg".into()), SVG.into()), Component::LocalName(LocalName { name: DummyAtom::from("circle"), lower_name: DummyAtom::from("circle"), }), ], specificity(0, 0, 1), Default::default(), )])) ); assert_eq!( parse_ns("svg|*", &parser), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::Namespace(DummyAtom("svg".into()), SVG.into()), Component::ExplicitUniversalType, ], specificity(0, 0, 0), Default::default(), )])) ); // Default namespace does not apply to attribute selectors // https://github.com/mozilla/servo/pull/1652 // but it does apply to implicit type selectors // https://github.com/servo/rust-selectors/pull/82 parser.default_ns = Some(MATHML.into()); assert_eq!( parse_ns("[Foo]", &parser), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::DefaultNamespace(MATHML.into()), Component::AttributeInNoNamespaceExists { local_name: DummyAtom::from("Foo"), local_name_lower: DummyAtom::from("foo"), }, ], specificity(0, 1, 0), Default::default(), )])) ); // Default namespace does apply to type selectors assert_eq!( parse_ns("e", &parser), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::DefaultNamespace(MATHML.into()), Component::LocalName(LocalName { name: DummyAtom::from("e"), lower_name: DummyAtom::from("e"), }), ], specificity(0, 0, 1), Default::default(), )])) ); assert_eq!( parse_ns("*", &parser), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::DefaultNamespace(MATHML.into()), Component::ExplicitUniversalType, ], specificity(0, 0, 0), Default::default(), )])) ); assert_eq!( parse_ns("*|*", &parser), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::ExplicitAnyNamespace, Component::ExplicitUniversalType,], specificity(0, 0, 0), Default::default(), )])) ); // Default namespace applies to universal and type selectors inside :not and :matches, // but not otherwise. assert_eq!( parse_ns(":not(.cl)", &parser), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::DefaultNamespace(MATHML.into()), Component::Negation( vec![Selector::from_vec( vec![Component::Class(DummyAtom::from("cl"))], specificity(0, 1, 0), Default::default(), )] .into_boxed_slice() ), ], specificity(0, 1, 0), Default::default(), )])) ); assert_eq!( parse_ns(":not(*)", &parser), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::DefaultNamespace(MATHML.into()), Component::Negation( vec![Selector::from_vec( vec![ Component::DefaultNamespace(MATHML.into()), Component::ExplicitUniversalType, ], specificity(0, 0, 0), Default::default(), )] .into_boxed_slice(), ), ], specificity(0, 0, 0), Default::default(), )])) ); assert_eq!( parse_ns(":not(e)", &parser), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::DefaultNamespace(MATHML.into()), Component::Negation( vec![Selector::from_vec( vec![ Component::DefaultNamespace(MATHML.into()), Component::LocalName(LocalName { name: DummyAtom::from("e"), lower_name: DummyAtom::from("e"), }), ], specificity(0, 0, 1), Default::default(), ),] .into_boxed_slice() ), ], specificity(0, 0, 1), Default::default(), )])) ); assert_eq!( parse("[attr|=\"foo\"]"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::AttributeInNoNamespace { local_name: DummyAtom::from("attr"), operator: AttrSelectorOperator::DashMatch, value: DummyAttrValue::from("foo"), never_matches: false, case_sensitivity: ParsedCaseSensitivity::CaseSensitive, }], specificity(0, 1, 0), Default::default(), )])) ); // https://github.com/mozilla/servo/issues/1723 assert_eq!( parse("::before"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::Combinator(Combinator::PseudoElement), Component::PseudoElement(PseudoElement::Before), ], specificity(0, 0, 1), SelectorFlags::HAS_PSEUDO, )])) ); assert_eq!( parse("::before:hover"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::Combinator(Combinator::PseudoElement), Component::PseudoElement(PseudoElement::Before), Component::NonTSPseudoClass(PseudoClass::Hover), ], specificity(0, 1, 1), SelectorFlags::HAS_PSEUDO, )])) ); assert_eq!( parse("::before:hover:hover"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::Combinator(Combinator::PseudoElement), Component::PseudoElement(PseudoElement::Before), Component::NonTSPseudoClass(PseudoClass::Hover), Component::NonTSPseudoClass(PseudoClass::Hover), ], specificity(0, 2, 1), SelectorFlags::HAS_PSEUDO, )])) ); 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_ok()); // https://github.com/servo/servo/issues/15335 assert!(parse(":: before").is_err()); assert_eq!( parse("div ::after"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::LocalName(LocalName { name: DummyAtom::from("div"), lower_name: DummyAtom::from("div"), }), Component::Combinator(Combinator::Descendant), Component::Combinator(Combinator::PseudoElement), Component::PseudoElement(PseudoElement::After), ], specificity(0, 0, 2), SelectorFlags::HAS_PSEUDO, )])) ); assert_eq!( parse("#d1 > .ok"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![ Component::ID(DummyAtom::from("d1")), Component::Combinator(Combinator::Child), Component::Class(DummyAtom::from("ok")), ], (1 << 20) + (1 << 10) + (0 << 0), Default::default(), )])) ); parser.default_ns = None; assert!(parse(":not(#provel.old)").is_ok()); assert!(parse(":not(#provel > old)").is_ok()); assert!(parse("table[rules]:not([rules=\"none\"]):not([rules=\"\"])").is_ok()); // https://github.com/servo/servo/issues/16017 assert_eq!( parse_ns(":not(*)", &parser), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::Negation( vec![Selector::from_vec( vec![Component::ExplicitUniversalType], specificity(0, 0, 0), Default::default(), )] .into_boxed_slice() )], specificity(0, 0, 0), Default::default(), )])) ); assert_eq!( parse_ns(":not(|*)", &parser), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::Negation( vec![Selector::from_vec( vec![Component::ExplicitNoNamespace, Component::ExplicitUniversalType,], specificity(0, 0, 0), Default::default(), )] .into_boxed_slice(), )], specificity(0, 0, 0), Default::default(), )])) ); // *| should be elided if there is no default namespace. // https://github.com/servo/servo/pull/17537 // assert_eq!( // parse_ns_expected(":not(*|*)", &parser, Some(":not(*)")), // Ok(SelectorList::from_vec(vec![Selector::from_vec( // vec![Component::Negation( // vec![Selector::from_vec( // vec![Component::ExplicitUniversalType], // specificity(0, 0, 0), // Default::default() // )] // .into_boxed_slice() // )], // specificity(0, 0, 0), // Default::default(), // )])) // ); 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()); assert!(parse("::part(foo bar)").is_ok()); 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()); assert!(parse("foo:where()").is_err()); assert!(parse("foo:where(div, foo, .bar baz)").is_ok()); assert!(parse("foo:where(::before)").is_err()); } #[test] fn test_pseudo_iter() { let selector = &parse("q::before").unwrap().0[0]; assert!(!selector.is_universal()); let mut iter = selector.iter(); assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before))); assert_eq!(iter.next(), None); let combinator = iter.next_sequence(); assert_eq!(combinator, Some(Combinator::PseudoElement)); assert!(matches!(iter.next(), Some(&Component::LocalName(..)))); assert_eq!(iter.next(), None); assert_eq!(iter.next_sequence(), None); } #[test] fn test_universal() { let selector = &parse_ns( "*|*::before", &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")), ) .unwrap() .0[0]; assert!(selector.is_universal()); } #[test] fn test_empty_pseudo_iter() { let selector = &parse("::before").unwrap().0[0]; assert!(selector.is_universal()); let mut iter = selector.iter(); assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before))); assert_eq!(iter.next(), None); assert_eq!(iter.next_sequence(), Some(Combinator::PseudoElement)); assert_eq!(iter.next(), None); assert_eq!(iter.next_sequence(), None); } struct TestVisitor { seen: Vec, } impl<'i> SelectorVisitor<'i> for TestVisitor { type Impl = DummySelectorImpl; fn visit_simple_selector(&mut self, s: &Component) -> bool { let mut dest = String::new(); s.to_css(&mut dest).unwrap(); self.seen.push(dest); true } } #[test] fn visitor() { let mut test_visitor = TestVisitor { seen: vec![] }; parse(":not(:hover) ~ label").unwrap().0[0].visit(&mut test_visitor); assert!(test_visitor.seen.contains(&":hover".into())); let mut test_visitor = TestVisitor { seen: vec![] }; parse("::before:hover").unwrap().0[0].visit(&mut test_visitor); assert!(test_visitor.seen.contains(&":hover".into())); } }