diff options
Diffstat (limited to 'components/selectors/parser.rs')
-rw-r--r-- | components/selectors/parser.rs | 4140 |
1 files changed, 0 insertions, 4140 deletions
diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs deleted file mode 100644 index 2c44da8018e..00000000000 --- a/components/selectors/parser.rs +++ /dev/null @@ -1,4140 +0,0 @@ -/* 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, ParsedCaseSensitivity}; -use crate::bloom::BLOOM_HASH_MASK; -use crate::builder::{ - relative_selector_list_specificity_and_flags, selector_list_specificity_and_flags, - SelectorBuilder, SelectorFlags, Specificity, SpecificityAndFlags, -}; -use crate::context::QuirksMode; -use crate::sink::Push; -use crate::visitor::SelectorListKind; -pub use crate::visitor::SelectorVisitor; -use bitflags::bitflags; -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 servo_arc::{HeaderWithLength, ThinArc, UniqueArc}; -use smallvec::SmallVec; -use std::borrow::{Borrow, Cow}; -use std::fmt::{self, Debug}; -use std::iter::Rev; -use std::slice; - -/// A trait that represents a pseudo-element. -pub trait PseudoElement: Sized + ToCss { - /// The `SelectorImpl` this pseudo-element is used for. - type Impl: SelectorImpl; - - /// 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 - } -} - -/// A trait that represents a pseudo-class. -pub trait NonTSPseudoClass: Sized + ToCss { - /// The `SelectorImpl` this pseudo-element is used for. - type Impl: SelectorImpl; - - /// 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 visit<V>(&self, _visitor: &mut V) -> bool - where - V: SelectorVisitor<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(s: &str) -> Cow<str> { - if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { - let mut string = s.to_owned(); - string[first_uppercase..].make_ascii_lowercase(); - string.into() - } else { - s.into() - } -} - -bitflags! { - /// Flags that indicate at which point of parsing a selector are we. - struct SelectorParsingState: u8 { - /// 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 explicitly disallow relative selectors (i.e. `:has()`). - const DISALLOW_RELATIVE_SELECTOR = 1 << 7; - } -} - -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, - NonCompoundSelector, - NonPseudoElementAfterSlotted, - InvalidPseudoElementAfterSlotted, - InvalidPseudoElementInsideWhere, - InvalidState, - UnexpectedTokenInAttributeSelector(Token<'i>), - PseudoElementExpectedColon(Token<'i>), - PseudoElementExpectedIdent(Token<'i>), - NoIdentForPseudo(Token<'i>), - UnsupportedPseudoClassOrElement(CowRcStr<'i>), - UnexpectedIdent(CowRcStr<'i>), - ExpectedNamespace(CowRcStr<'i>), - ExpectedBarInAttr(Token<'i>), - BadValueInAttr(Token<'i>), - InvalidQualNameInAttr(Token<'i>), - ExplicitNamespaceUnexpectedToken(Token<'i>), - ClassNeedsIdent(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 - /// <https://github.com/rust-lang/rust/issues/26925> - pub trait SelectorImpl: Clone + Debug + Sized + 'static { - type ExtraMatchingData<'a>: Sized + Default; - type AttrValue: $($InSelector)*; - type Identifier: $($InSelector)*; - type LocalName: $($InSelector)* + Borrow<Self::BorrowedLocalName>; - type NamespaceUrl: $($CommonBounds)* + Default + Borrow<Self::BorrowedNamespaceUrl>; - 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<Impl = Self>; - - /// pseudo-elements - type PseudoElement: $($CommonBounds)* + PseudoElement<Impl = Self>; - - /// Whether attribute hashes should be collected for filtering - /// purposes. - fn should_collect_attr_hash(_name: &Self::LocalName) -> bool { - false - } - } - } -} - -macro_rules! with_bounds { - ( [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ]) => { - with_all_bounds! { - [$($CommonBounds)* + $($FromStr)* + ToCss] - [$($CommonBounds)*] - [$($FromStr)*] - } - } -} - -with_bounds! { - [Clone + Eq] - [for<'a> From<&'a str>] -} - -pub trait Parser<'i> { - type Impl: SelectorImpl; - type Error: 'i + From<SelectorParseErrorKind<'i>>; - - /// 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 selector list of nth-child() or nth-last-child(). - fn parse_nth_child_of(&self) -> bool { - false - } - - /// Whether to parse the `:where` pseudo-class. - fn parse_is_and_where(&self) -> bool { - false - } - - /// Whether to parse the :has pseudo-class. - fn parse_has(&self) -> bool { - false - } - - /// Whether to parse the '&' delimiter as a parent selector. - fn parse_parent_selector(&self) -> bool { - false - } - - /// Whether the given function name is an alias for the `:is()` function. - fn is_is_alias(&self, _name: &str) -> bool { - false - } - - /// Whether to parse the `:host` pseudo-class. - fn parse_host(&self) -> bool { - false - } - - /// Whether to allow forgiving selector-list parsing. - fn allow_forgiving_selectors(&self) -> bool { - true - } - - /// 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<<Self::Impl as SelectorImpl>::NonTSPseudoClass, ParseError<'i, Self::Error>> { - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_non_ts_functional_pseudo_class<'t>( - &self, - name: CowRcStr<'i>, - arguments: &mut CssParser<'i, 't>, - ) -> Result<<Self::Impl as SelectorImpl>::NonTSPseudoClass, ParseError<'i, Self::Error>> { - Err( - arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_pseudo_element( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<<Self::Impl as SelectorImpl>::PseudoElement, ParseError<'i, Self::Error>> { - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_functional_pseudo_element<'t>( - &self, - name: CowRcStr<'i>, - arguments: &mut CssParser<'i, 't>, - ) -> Result<<Self::Impl as SelectorImpl>::PseudoElement, ParseError<'i, Self::Error>> { - Err( - arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn default_namespace(&self) -> Option<<Self::Impl as SelectorImpl>::NamespaceUrl> { - None - } - - fn namespace_for_prefix( - &self, - _prefix: &<Self::Impl as SelectorImpl>::NamespacePrefix, - ) -> Option<<Self::Impl as SelectorImpl>::NamespaceUrl> { - None - } -} - -#[derive(Clone, Debug, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct SelectorList<Impl: SelectorImpl>( - #[shmem(field_bound)] pub SmallVec<[Selector<Impl>; 1]>, -); - -/// Whether or not we're using forgiving parsing mode -enum ForgivingParsing { - /// Discard the entire selector list upon encountering any invalid selector. - /// This is the default behavior for almost all of CSS. - No, - /// Ignore invalid selectors, potentially creating an empty selector list. - /// - /// This is the error recovery mode of :is() and :where() - Yes, -} - -/// Flag indicating if we're parsing relative selectors. -#[derive(Copy, Clone, PartialEq)] -enum ParseRelative { - /// Expect selectors to start with a combinator, assuming descendant combinator if not present. - Yes, - /// Treat as parse error if any selector begins with a combinator. - No, -} - -impl<Impl: SelectorImpl> SelectorList<Impl> { - /// Returns a selector list with a single `&` - pub fn ampersand() -> Self { - Self(smallvec::smallvec![Selector::ampersand()]) - } - - /// Parse a comma-separated list of Selectors. - /// <https://drafts.csswg.org/selectors/#grouping> - /// - /// Return the Selectors or Err if there is an invalid selector. - pub fn parse<'i, 't, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - ) -> Result<Self, ParseError<'i, P::Error>> - where - P: Parser<'i, Impl = Impl>, - { - Self::parse_with_state( - parser, - input, - SelectorParsingState::empty(), - ForgivingParsing::No, - ParseRelative::No, - ) - } - - #[inline] - fn parse_with_state<'i, 't, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, - recovery: ForgivingParsing, - parse_relative: ParseRelative, - ) -> Result<Self, ParseError<'i, P::Error>> - where - P: Parser<'i, Impl = Impl>, - { - let mut values = SmallVec::new(); - loop { - let selector = input.parse_until_before(Delimiter::Comma, |i| { - parse_selector(parser, i, state, parse_relative) - }); - - let was_ok = selector.is_ok(); - match selector { - Ok(selector) => values.push(selector), - Err(err) => match recovery { - ForgivingParsing::No => return Err(err), - ForgivingParsing::Yes => { - if !parser.allow_forgiving_selectors() { - return Err(err); - } - }, - }, - } - - 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 SelectorList from a Vec of selectors. Used in tests. - #[allow(dead_code)] - pub(crate) fn from_vec(v: Vec<Selector<Impl>>) -> Self { - SelectorList(SmallVec::from_vec(v)) - } -} - -/// 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: SelectorParsingState, -) -> Result<Selector<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - parse_selector( - parser, - input, - state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS, - ParseRelative::No, - ) -} - -/// 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<Impl: SelectorImpl>( - iter: SelectorIter<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::AttributeInNoNamespace { ref local_name, .. } - if Impl::should_collect_attr_hash(local_name) => - { - // AttributeInNoNamespace is only used when local_name == - // local_name_lower. - local_name.precomputed_hash() - }, - Component::AttributeInNoNamespaceExists { - ref local_name, - ref local_name_lower, - .. - } => { - // 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 local_name != local_name_lower || !Impl::should_collect_attr_hash(local_name) { - continue; - } - local_name.precomputed_hash() - }, - Component::AttributeOther(ref selector) => { - if selector.local_name != selector.local_name_lower || - !Impl::should_collect_attr_hash(&selector.local_name) - { - continue; - } - selector.local_name.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<Impl: SelectorImpl>(selector: &Selector<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) - } -} - -#[inline] -pub fn namespace_empty_string<Impl: SelectorImpl>() -> 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, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct Selector<Impl: SelectorImpl>( - #[shmem(field_bound)] ThinArc<SpecificityAndFlags, Component<Impl>>, -); - -impl<Impl: SelectorImpl> Selector<Impl> { - /// See Arc::mark_as_intentionally_leaked - pub fn mark_as_intentionally_leaked(&self) { - self.0.with_arc(|a| a.mark_as_intentionally_leaked()) - } - - fn ampersand() -> Self { - Self(ThinArc::from_header_and_iter( - SpecificityAndFlags { - specificity: 0, - flags: SelectorFlags::HAS_PARENT, - }, - std::iter::once(Component::ParentSelector), - )) - } - - #[inline] - pub fn specificity(&self) -> u32 { - self.0.header.header.specificity() - } - - #[inline] - fn flags(&self) -> SelectorFlags { - self.0.header.header.flags - } - - #[inline] - pub fn has_pseudo_element(&self) -> bool { - self.0.header.header.has_pseudo_element() - } - - #[inline] - pub fn has_parent_selector(&self) -> bool { - self.0.header.header.has_parent_selector() - } - - #[inline] - pub fn is_slotted(&self) -> bool { - self.0.header.header.is_slotted() - } - - #[inline] - pub fn is_part(&self) -> bool { - self.0.header.header.is_part() - } - - #[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(..) - ) - }) - } - - /// 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<Impl> { - SelectorIter { - iter: self.iter_raw_match_order(), - next_combinator: None, - } - } - - /// Same as `iter()`, but skips `RelativeSelectorAnchor` and its associated combinator. - #[inline] - pub fn iter_skip_relative_selector_anchor(&self) -> SelectorIter<Impl> { - if cfg!(debug_assertions) { - let mut selector_iter = self.iter_raw_parse_order_from(0); - assert!( - matches!( - selector_iter.next().unwrap(), - Component::RelativeSelectorAnchor - ), - "Relative selector does not start with RelativeSelectorAnchor" - ); - assert!( - selector_iter.next().unwrap().is_combinator(), - "Relative combinator does not exist" - ); - } - - SelectorIter { - iter: self.0.slice[..self.len() - 2].iter(), - 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<Impl> { - let iter = self.0.slice[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.0.slice[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<Component<Impl>> { - self.0.slice.iter() - } - - /// 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.0.slice[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<slice::Iter<Component<Impl>>> { - self.0.slice[..self.len() - offset].iter().rev() - } - - /// Creates a Selector from a vec of Components, specified in parse order. Used in tests. - #[allow(dead_code)] - pub(crate) fn from_vec( - vec: Vec<Component<Impl>>, - 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 }; - Selector(builder.build_with_specificity_and_flags(spec)) - } - - pub fn replace_parent_selector(&self, parent: &[Selector<Impl>]) -> Self { - // FIXME(emilio): Shouldn't allow replacing if parent has a pseudo-element selector - // or what not. - let flags = self.flags() - SelectorFlags::HAS_PARENT; - let mut specificity = Specificity::from(self.specificity()); - let parent_specificity = - Specificity::from(selector_list_specificity_and_flags(parent.iter()).specificity()); - - // The specificity at this point will be wrong, we replace it by the correct one after the - // fact. - let specificity_and_flags = SpecificityAndFlags { - specificity: self.specificity(), - flags, - }; - - fn replace_parent_on_selector_list<Impl: SelectorImpl>( - orig: &[Selector<Impl>], - parent: &[Selector<Impl>], - specificity: &mut Specificity, - with_specificity: bool, - ) -> Vec<Selector<Impl>> { - let mut any = false; - - let result = orig - .iter() - .map(|s| { - if !s.has_parent_selector() { - return s.clone(); - } - any = true; - s.replace_parent_selector(parent) - }) - .collect(); - - if !any || !with_specificity { - return result; - } - - *specificity += Specificity::from( - selector_list_specificity_and_flags(result.iter()).specificity - - selector_list_specificity_and_flags(orig.iter()).specificity, - ); - result - } - - fn replace_parent_on_relative_selector_list<Impl: SelectorImpl>( - orig: &[RelativeSelector<Impl>], - parent: &[Selector<Impl>], - specificity: &mut Specificity, - ) -> Vec<RelativeSelector<Impl>> { - let mut any = false; - - let result = orig - .iter() - .map(|s| { - if !s.selector.has_parent_selector() { - return s.clone(); - } - any = true; - RelativeSelector { - match_hint: s.match_hint, - selector: s.selector.replace_parent_selector(parent), - } - }) - .collect(); - - if !any { - return result; - } - - *specificity += Specificity::from( - relative_selector_list_specificity_and_flags(&result).specificity - - relative_selector_list_specificity_and_flags(orig).specificity, - ); - result - } - - fn replace_parent_on_selector<Impl: SelectorImpl>( - orig: &Selector<Impl>, - parent: &[Selector<Impl>], - specificity: &mut Specificity, - ) -> Selector<Impl> { - if !orig.has_parent_selector() { - return orig.clone(); - } - let new_selector = orig.replace_parent_selector(parent); - *specificity += Specificity::from(new_selector.specificity() - orig.specificity()); - new_selector - } - - let mut items = if !self.has_parent_selector() { - // Implicit `&` plus descendant combinator. - let iter = self.iter_raw_match_order(); - let len = iter.len() + 2; - specificity += parent_specificity; - let iter = iter - .cloned() - .chain(std::iter::once(Component::Combinator( - Combinator::Descendant, - ))) - .chain(std::iter::once(Component::Is( - parent.to_vec().into_boxed_slice(), - ))); - let header = HeaderWithLength::new(specificity_and_flags, len); - UniqueArc::from_header_and_iter_with_size(header, iter, len) - } else { - let iter = self.iter_raw_match_order().map(|component| { - use self::Component::*; - match *component { - LocalName(..) | - ID(..) | - Class(..) | - AttributeInNoNamespaceExists { .. } | - AttributeInNoNamespace { .. } | - AttributeOther(..) | - ExplicitUniversalType | - ExplicitAnyNamespace | - ExplicitNoNamespace | - DefaultNamespace(..) | - Namespace(..) | - Root | - Empty | - Scope | - Nth(..) | - NonTSPseudoClass(..) | - PseudoElement(..) | - Combinator(..) | - Host(None) | - Part(..) | - RelativeSelectorAnchor => component.clone(), - ParentSelector => { - specificity += parent_specificity; - Is(parent.to_vec().into_boxed_slice()) - }, - Negation(ref selectors) => { - Negation( - replace_parent_on_selector_list( - selectors, - parent, - &mut specificity, - /* with_specificity = */ true, - ) - .into_boxed_slice(), - ) - }, - Is(ref selectors) => { - Is(replace_parent_on_selector_list( - selectors, - parent, - &mut specificity, - /* with_specificity = */ true, - ) - .into_boxed_slice()) - }, - Where(ref selectors) => { - Where( - replace_parent_on_selector_list( - selectors, - parent, - &mut specificity, - /* with_specificity = */ false, - ) - .into_boxed_slice(), - ) - }, - Has(ref selectors) => Has(replace_parent_on_relative_selector_list( - selectors, - parent, - &mut specificity, - ) - .into_boxed_slice()), - - Host(Some(ref selector)) => Host(Some(replace_parent_on_selector( - selector, - parent, - &mut specificity, - ))), - NthOf(ref data) => { - let selectors = replace_parent_on_selector_list( - data.selectors(), - parent, - &mut specificity, - /* with_specificity = */ true, - ); - NthOf(NthOfSelectorData::new( - data.nth_data(), - selectors.into_iter(), - )) - }, - Slotted(ref selector) => Slotted(replace_parent_on_selector( - selector, - parent, - &mut specificity, - )), - } - }); - let header = HeaderWithLength::new(specificity_and_flags, iter.len()); - UniqueArc::from_header_and_iter(header, iter) - }; - items.header_mut().specificity = specificity.into(); - Selector(items.shareable_thin()) - } - - /// Returns count of simple selectors and combinators in the Selector. - #[inline] - pub fn len(&self) -> usize { - self.0.slice.len() - } - - /// Returns the address on the heap of the ThinArc for memory reporting. - pub fn thin_arc_heap_ptr(&self) -> *const ::std::os::raw::c_void { - self.0.heap_ptr() - } - - /// 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<V>(&self, visitor: &mut V) -> bool - where - V: SelectorVisitor<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 - } -} - -#[derive(Clone)] -pub struct SelectorIter<'a, Impl: 'a + SelectorImpl> { - iter: slice::Iter<'a, Component<Impl>>, - next_combinator: Option<Combinator>, -} - -impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, 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<Combinator> { - 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| component.is_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<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, Impl: SelectorImpl> Iterator for SelectorIter<'a, Impl> { - type Item = &'a Component<Impl>; - - #[inline] - fn next(&mut self) -> Option<Self::Item> { - 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, Impl: SelectorImpl> fmt::Debug for SelectorIter<'a, 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 combinators in a selector. Does not traverse selectors within psuedoclasses. -struct CombinatorIter<'a, Impl: 'a + SelectorImpl>(SelectorIter<'a, Impl>); -impl<'a, Impl: 'a + SelectorImpl> CombinatorIter<'a, Impl> { - fn new(inner: SelectorIter<'a, Impl>) -> Self { - let mut result = CombinatorIter(inner); - result.consume_non_combinators(); - result - } - - fn consume_non_combinators(&mut self) { - while self.0.next().is_some() {} - } -} - -impl<'a, Impl: SelectorImpl> Iterator for CombinatorIter<'a, Impl> { - type Item = Combinator; - fn next(&mut self) -> Option<Self::Item> { - let result = self.0.next_sequence(); - self.consume_non_combinators(); - result - } -} - -/// An iterator over all simple selectors belonging to ancestors. -struct AncestorIter<'a, Impl: 'a + SelectorImpl>(SelectorIter<'a, Impl>); -impl<'a, Impl: 'a + SelectorImpl> AncestorIter<'a, 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, 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, Impl: SelectorImpl> Iterator for AncestorIter<'a, Impl> { - type Item = &'a Component<Impl>; - fn next(&mut self) -> Option<Self::Item> { - // 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, ToShmem)] -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, -} - -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) - } -} - -/// An enum for the different types of :nth- pseudoclasses -#[derive(Copy, Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub enum NthType { - Child, - LastChild, - OnlyChild, - OfType, - LastOfType, - OnlyOfType, -} - -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 - } -} - -/// 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, ToShmem)] -#[shmem(no_bounds)] -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, - } - } - - /// Writes the beginning of the selector. - #[inline] - fn write_start<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result { - dest.write_str(match self.ty { - NthType::Child if self.is_function => ":nth-child(", - NthType::Child => ":first-child", - NthType::LastChild if self.is_function => ":nth-last-child(", - NthType::LastChild => ":last-child", - NthType::OfType if self.is_function => ":nth-of-type(", - NthType::OfType => ":first-of-type", - NthType::LastOfType if self.is_function => ":nth-last-of-type(", - NthType::LastOfType => ":last-of-type", - NthType::OnlyChild => ":only-child", - NthType::OnlyOfType => ":only-of-type", - }) - } - - /// Serialize <an+b> (part of the CSS Syntax spec, but currently only used here). - /// <https://drafts.csswg.org/css-syntax-3/#serialize-an-anb-value> - #[inline] - fn write_affine<W: fmt::Write>(&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), - - (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, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct NthOfSelectorData<Impl: SelectorImpl>( - #[shmem(field_bound)] ThinArc<NthSelectorData, Selector<Impl>>, -); - -impl<Impl: SelectorImpl> NthOfSelectorData<Impl> { - /// Returns selector data for :nth-{,last-}{child,of-type}(An+B [of S]) - #[inline] - pub fn new<I>(nth_data: &NthSelectorData, selectors: I) -> Self - where - I: Iterator<Item = Selector<Impl>> + ExactSizeIterator, - { - Self(ThinArc::from_header_and_iter(*nth_data, selectors)) - } - - /// Returns the An+B part of the selector - #[inline] - pub fn nth_data(&self) -> &NthSelectorData { - &self.0.header.header - } - - /// Returns the selector list part of the selector - #[inline] - pub fn selectors(&self) -> &[Selector<Impl>] { - &self.0.slice - } -} - -/// Flag indicating where a given relative selector's match would be contained. -#[derive(Clone, Copy, Eq, PartialEq, ToShmem)] -pub enum RelativeSelectorMatchHint { - /// Within this element's subtree. - InSubtree, - /// Within this element's direct children. - InChild, - /// This element's next sibling. - InNextSibling, - /// Within this element's next sibling's subtree. - InNextSiblingSubtree, - /// Within this element's subsequent siblings. - InSibling, - /// Across this element's subsequent siblings and their subtrees. - InSiblingSubtree, -} - -/// Storage for a relative selector. -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct RelativeSelector<Impl: SelectorImpl> { - /// Match space constraining hint. - pub match_hint: RelativeSelectorMatchHint, - /// The selector. Guaranteed to contain `RelativeSelectorAnchor` and the relative combinator in parse order. - #[shmem(field_bound)] - pub selector: Selector<Impl>, -} - -bitflags! { - /// Composition of combinators in a given selector, not traversing selectors of pseudoclasses. - struct CombinatorComposition: u8 { - const DESCENDANTS = 1 << 0; - const SIBLINGS = 1 << 1; - } -} - -impl CombinatorComposition { - fn for_relative_selector<Impl: SelectorImpl>(inner_selector: &Selector<Impl>) -> Self { - let mut result = CombinatorComposition::empty(); - for combinator in CombinatorIter::new(inner_selector.iter_skip_relative_selector_anchor()) { - match combinator { - Combinator::Descendant | Combinator::Child => { - result.insert(Self::DESCENDANTS); - }, - Combinator::NextSibling | Combinator::LaterSibling => { - result.insert(Self::SIBLINGS); - }, - Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => { - continue - }, - }; - if result.is_all() { - break; - } - } - return result; - } -} - -impl<Impl: SelectorImpl> RelativeSelector<Impl> { - fn from_selector_list(selector_list: SelectorList<Impl>) -> Box<[Self]> { - let vec: Vec<Self> = selector_list - .0 - .into_iter() - .map(|selector| { - // It's more efficient to keep track of all this during the parse time, but that seems like a lot of special - // case handling for what it's worth. - if cfg!(debug_assertions) { - let relative_selector_anchor = selector.iter_raw_parse_order_from(0).next(); - debug_assert!( - relative_selector_anchor.is_some(), - "Relative selector is empty" - ); - debug_assert!( - matches!( - relative_selector_anchor.unwrap(), - Component::RelativeSelectorAnchor - ), - "Relative selector anchor is missing" - ); - } - // Leave a hint for narrowing down the search space when we're matching. - let match_hint = match selector.combinator_at_parse_order(1) { - Combinator::Descendant => RelativeSelectorMatchHint::InSubtree, - Combinator::Child => { - let composition = CombinatorComposition::for_relative_selector(&selector); - if composition.is_empty() || composition == CombinatorComposition::SIBLINGS - { - RelativeSelectorMatchHint::InChild - } else { - // Technically, for any composition that consists of child combinators only, - // the search space is depth-constrained, but it's probably not worth optimizing for. - RelativeSelectorMatchHint::InSubtree - } - }, - Combinator::NextSibling => { - let composition = CombinatorComposition::for_relative_selector(&selector); - if composition.is_empty() { - RelativeSelectorMatchHint::InNextSibling - } else if composition == CombinatorComposition::SIBLINGS { - RelativeSelectorMatchHint::InSibling - } else if composition == CombinatorComposition::DESCENDANTS { - // Match won't cross multiple siblings. - RelativeSelectorMatchHint::InNextSiblingSubtree - } else { - RelativeSelectorMatchHint::InSiblingSubtree - } - }, - Combinator::LaterSibling => { - let composition = CombinatorComposition::for_relative_selector(&selector); - if composition.is_empty() || composition == CombinatorComposition::SIBLINGS - { - RelativeSelectorMatchHint::InSibling - } else { - // Even if the match may not cross multiple siblings, we have to look until - // we find a match anyway. - RelativeSelectorMatchHint::InSiblingSubtree - } - }, - Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => { - debug_assert!(false, "Unexpected relative combinator"); - RelativeSelectorMatchHint::InSubtree - }, - }; - RelativeSelector { - match_hint, - selector, - } - }) - .collect(); - vec.into_boxed_slice() - } -} - -/// 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, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub enum Component<Impl: SelectorImpl> { - LocalName(LocalName<Impl>), - - ID(#[shmem(field_bound)] Impl::Identifier), - Class(#[shmem(field_bound)] Impl::Identifier), - - AttributeInNoNamespaceExists { - #[shmem(field_bound)] - local_name: Impl::LocalName, - local_name_lower: Impl::LocalName, - }, - // Used only when local_name is already lowercase. - AttributeInNoNamespace { - local_name: Impl::LocalName, - operator: AttrSelectorOperator, - #[shmem(field_bound)] - value: Impl::AttrValue, - case_sensitivity: ParsedCaseSensitivity, - }, - // Use a Box in the less common cases with more data to keep size_of::<Component>() small. - AttributeOther(Box<AttrSelectorWithOptionalNamespace<Impl>>), - - ExplicitUniversalType, - ExplicitAnyNamespace, - - ExplicitNoNamespace, - DefaultNamespace(#[shmem(field_bound)] Impl::NamespaceUrl), - Namespace( - #[shmem(field_bound)] Impl::NamespacePrefix, - #[shmem(field_bound)] Impl::NamespaceUrl, - ), - - /// Pseudo-classes - Negation(Box<[Selector<Impl>]>), - Root, - Empty, - Scope, - ParentSelector, - Nth(NthSelectorData), - NthOf(NthOfSelectorData<Impl>), - NonTSPseudoClass(#[shmem(field_bound)] 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<Impl>), - /// The `::part` pseudo-element. - /// https://drafts.csswg.org/css-shadow-parts/#part - Part(#[shmem(field_bound)] 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<Selector<Impl>>), - /// 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<Impl>]>), - /// The `:is` pseudo-class. - /// - /// https://drafts.csswg.org/selectors/#matches-pseudo - /// - /// Same comment as above re. the argument. - Is(Box<[Selector<Impl>]>), - /// The `:has` pseudo-class. - /// - /// https://drafts.csswg.org/selectors/#has-pseudo - /// - /// Same comment as above re. the argument. - Has(Box<[RelativeSelector<Impl>]>), - /// An implementation-dependent pseudo-element selector. - PseudoElement(#[shmem(field_bound)] Impl::PseudoElement), - - Combinator(Combinator), - - /// Used only for relative selectors, which starts with a combinator - /// (With an implied descendant combinator if not specified). - /// - /// https://drafts.csswg.org/selectors-4/#typedef-relative-selector - RelativeSelectorAnchor, -} - -impl<Impl: SelectorImpl> Component<Impl> { - /// Returns true if this is a combinator. - #[inline] - pub fn is_combinator(&self) -> bool { - matches!(*self, Component::Combinator(_)) - } - - /// Returns true if this is a :host() selector. - #[inline] - pub fn is_host(&self) -> bool { - matches!(*self, Component::Host(..)) - } - - /// Returns the value as a combinator if applicable, None otherwise. - pub fn as_combinator(&self) -> Option<Combinator> { - 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<V>(&self, visitor: &mut V) -> bool - where - V: SelectorVisitor<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::<Impl>()), - local_name, - local_name_lower, - ) { - return false; - } - }, - AttributeInNoNamespace { ref local_name, .. } => { - if !visitor.visit_attribute_selector( - &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()), - local_name, - local_name, - ) { - return false; - } - }, - AttributeOther(ref attr_selector) => { - let empty_string; - let namespace = match attr_selector.namespace() { - Some(ns) => ns, - None => { - empty_string = crate::parser::namespace_empty_string::<Impl>(); - 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) => { - let list_kind = SelectorListKind::from_component(self); - debug_assert!(!list_kind.is_empty()); - if !visitor.visit_selector_list(list_kind, &list) { - return false; - } - }, - NthOf(ref nth_of_data) => { - if !visitor.visit_selector_list(SelectorListKind::NTH_OF, nth_of_data.selectors()) { - return false; - } - }, - _ => {}, - } - - true - } -} - -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct LocalName<Impl: SelectorImpl> { - #[shmem(field_bound)] - pub name: Impl::LocalName, - pub lower_name: Impl::LocalName, -} - -impl<Impl: SelectorImpl> Debug for Selector<Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("Selector(")?; - self.to_css(f)?; - write!( - f, - ", specificity = {:#x}, flags = {:?})", - self.specificity(), - self.flags() - ) - } -} - -impl<Impl: SelectorImpl> Debug for Component<Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f) - } -} -impl<Impl: SelectorImpl> Debug for AttrSelectorWithOptionalNamespace<Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f) - } -} -impl<Impl: SelectorImpl> Debug for LocalName<Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f) - } -} - -fn serialize_selector_list<'a, Impl, I, W>(iter: I, dest: &mut W) -> fmt::Result -where - Impl: SelectorImpl, - I: Iterator<Item = &'a Selector<Impl>>, - W: fmt::Write, -{ - let mut first = true; - for selector in iter { - if !first { - dest.write_str(", ")?; - } - first = false; - selector.to_css(dest)?; - } - Ok(()) -} - -impl<Impl: SelectorImpl> ToCss for SelectorList<Impl> { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - serialize_selector_list(self.0.iter(), dest) - } -} - -impl<Impl: SelectorImpl> ToCss for Selector<Impl> { - fn to_css<W>(&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; - } - if let Component::RelativeSelectorAnchor = compound.first().unwrap() { - debug_assert!( - compound.len() == 1, - "RelativeLeft should only be a simple selector" - ); - combinators.next().unwrap().to_css_relative(dest)?; - 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 Combinator { - fn to_css_internal<W>(&self, dest: &mut W, prefix_space: bool) -> fmt::Result - where - W: fmt::Write, - { - if matches!( - *self, - Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment - ) { - return Ok(()); - } - if prefix_space { - dest.write_char(' ')?; - } - match *self { - Combinator::Child => dest.write_str("> "), - Combinator::Descendant => Ok(()), - Combinator::NextSibling => dest.write_str("+ "), - Combinator::LaterSibling => dest.write_str("~ "), - Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => unsafe { - debug_unreachable!("Already handled") - }, - } - } - - fn to_css_relative<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - self.to_css_internal(dest, false) - } -} - -impl ToCss for Combinator { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - self.to_css_internal(dest, true) - } -} - -impl<Impl: SelectorImpl> ToCss for Component<Impl> { - fn to_css<W>(&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)?; - dest.write_char('"')?; - value.to_css(dest)?; - dest.write_char('"')?; - 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"), - ParentSelector => dest.write_char('&'), - 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)?; - 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)?; - 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) => { - match *self { - Where(..) => dest.write_str(":where(")?, - Is(..) => dest.write_str(":is(")?, - Negation(..) => dest.write_str(":not(")?, - _ => unreachable!(), - } - serialize_selector_list(list.iter(), dest)?; - dest.write_str(")") - }, - Has(ref list) => { - dest.write_str(":has(")?; - let mut first = true; - for RelativeSelector { ref selector, .. } in list.iter() { - if !first { - dest.write_str(", ")?; - } - first = false; - selector.to_css(dest)?; - } - dest.write_str(")") - }, - NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest), - RelativeSelectorAnchor => Ok(()), - } - } -} - -impl<Impl: SelectorImpl> ToCss for AttrSelectorWithOptionalNamespace<Impl> { - fn to_css<W>(&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 value, - } => { - operator.to_css(dest)?; - dest.write_char('"')?; - value.to_css(dest)?; - dest.write_char('"')?; - match case_sensitivity { - ParsedCaseSensitivity::CaseSensitive | - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}, - ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, - ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?, - } - }, - } - dest.write_char(']') - } -} - -impl<Impl: SelectorImpl> ToCss for LocalName<Impl> { - fn to_css<W>(&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>, - mut state: SelectorParsingState, - parse_relative: ParseRelative, -) -> Result<Selector<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - let mut builder = SelectorBuilder::default(); - if parse_relative == ParseRelative::Yes { - builder.push_simple_selector(Component::RelativeSelectorAnchor); - // Do we see a combinator? If so, push that. Otherwise, push a descendant combinator. - builder - .push_combinator(parse_combinator::<P, Impl>(input).unwrap_or(Combinator::Descendant)); - } - 'outer_loop: loop { - // Parse a sequence of simple selectors. - let empty = parse_compound_selector(parser, &mut 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) { - debug_assert!(state.intersects( - SelectorParsingState::AFTER_PSEUDO_ELEMENT | - SelectorParsingState::AFTER_SLOTTED | - SelectorParsingState::AFTER_PART - )); - break; - } - - let combinator = if let Ok(c) = parse_combinator::<P, Impl>(input) { - c - } else { - break 'outer_loop; - }; - - if !state.allows_combinators() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - - builder.push_combinator(combinator); - } - return Ok(Selector(builder.build())); -} - -fn parse_combinator<'i, 't, P, Impl>(input: &mut CssParser<'i, 't>) -> Result<Combinator, ()> { - let mut any_whitespace = false; - loop { - let before_this_token = input.state(); - match input.next_including_whitespace() { - Err(_e) => return Err(()), - Ok(&Token::WhiteSpace(_)) => any_whitespace = true, - Ok(&Token::Delim('>')) => { - return Ok(Combinator::Child); - }, - Ok(&Token::Delim('+')) => { - return Ok(Combinator::NextSibling); - }, - Ok(&Token::Delim('~')) => { - return Ok(Combinator::LaterSibling); - }, - Ok(_) => { - input.reset(&before_this_token); - if any_whitespace { - return Ok(Combinator::Descendant); - } else { - return Err(()); - } - }, - } - } -} - -impl<Impl: SelectorImpl> Selector<Impl> { - /// Parse a selector, without any pseudo-element. - #[inline] - pub fn parse<'i, 't, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - ) -> Result<Self, ParseError<'i, P::Error>> - where - P: Parser<'i, Impl = Impl>, - { - parse_selector( - parser, - input, - SelectorParsingState::empty(), - ParseRelative::No, - ) - } -} - -/// * `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<bool, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, - S: Push<Component<Impl>>, -{ - 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 => { - match parser.default_namespace() { - // 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. - None => {}, - Some(_) => 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).as_ref().into(), - name: name.as_ref().into(), - })), - None => sink.push(Component::ExplicitUniversalType), - } - Ok(true) - }, - Err(e) => Err(e), - } -} - -#[derive(Debug)] -enum SimpleSelectorParseResult<Impl: SelectorImpl> { - SimpleSelector(Component<Impl>), - PseudoElement(Impl::PseudoElement), - SlottedPseudo(Selector<Impl>), - PartPseudo(Box<[Impl::Identifier]>), -} - -#[derive(Debug)] -enum QNamePrefix<Impl: SelectorImpl> { - 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> { - Some(QNamePrefix<Impl>, Option<CowRcStr<'i>>), - 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<OptionalQName<'i, Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - 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(); - match input.next_including_whitespace() { - Ok(Token::Ident(value)) => { - let value = value.clone(); - let after_ident = input.state(); - match input.next_including_whitespace() { - Ok(&Token::Delim('|')) => { - let prefix = value.as_ref().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(); - match input.next_including_whitespace() { - Ok(&Token::Delim('|')) => { - explicit_namespace(input, QNamePrefix::ExplicitAnyNamespace) - }, - _ if !in_attr_selector => { - input.reset(&after_star); - default_namespace(None) - }, - result => { - let t = result?; - Err(after_star - .source_location() - .new_custom_error(SelectorParseErrorKind::ExpectedBarInAttr(t.clone()))) - }, - } - }, - Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitNoNamespace), - Ok(t) => { - let t = t.clone(); - 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<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - 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).as_ref().into(); - let local_name = local_name.as_ref().into(); - if let Some(namespace) = namespace { - return Ok(Component::AttributeOther(Box::new( - AttrSelectorWithOptionalNamespace { - namespace: Some(namespace), - local_name, - local_name_lower, - operation: ParsedAttrSelectorOperation::Exists, - }, - ))); - } 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 attribute_flags = parse_attribute_flags(input)?; - let value = value.as_ref().into(); - let local_name_lower; - let local_name_is_ascii_lowercase; - let case_sensitivity; - { - let local_name_lower_cow = to_ascii_lowercase(&local_name); - case_sensitivity = - attribute_flags.to_case_sensitivity(local_name_lower_cow.as_ref(), namespace.is_some()); - local_name_lower = local_name_lower_cow.as_ref().into(); - local_name_is_ascii_lowercase = matches!(local_name_lower_cow, Cow::Borrowed(..)); - } - let local_name = local_name.as_ref().into(); - if namespace.is_some() || !local_name_is_ascii_lowercase { - Ok(Component::AttributeOther(Box::new( - AttrSelectorWithOptionalNamespace { - namespace, - local_name, - local_name_lower, - operation: ParsedAttrSelectorOperation::WithValue { - operator, - case_sensitivity, - value, - }, - }, - ))) - } else { - Ok(Component::AttributeInNoNamespace { - local_name, - operator, - value, - case_sensitivity, - }) - } -} - -/// 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<AttributeFlags, BasicParseError<'i>> { - 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 "<defaultns>|*" type selector.) -fn parse_negation<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, -) -> Result<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - let list = SelectorList::parse_with_state( - parser, - input, - state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS, - ForgivingParsing::No, - ParseRelative::No, - )?; - - 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<Impl>, -) -> Result<bool, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - input.skip_whitespace(); - - let mut empty = true; - 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 "<defaultns>|*" 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) => { - state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT); - if !p.accepts_state_pseudo_classes() { - state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT); - } - builder.push_combinator(Combinator::PseudoElement); - builder.push_simple_selector(Component::PseudoElement(p)); - }, - } - } - Ok(empty) -} - -fn parse_is_where<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, - component: impl FnOnce(Box<[Selector<Impl>]>) -> Component<Impl>, -) -> Result<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - 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 inner = SelectorList::parse_with_state( - parser, - input, - state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS, - ForgivingParsing::Yes, - ParseRelative::No, - )?; - Ok(component(inner.0.into_vec().into_boxed_slice())) -} - -fn parse_has<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, -) -> Result<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - debug_assert!(parser.parse_has()); - if state.intersects(SelectorParsingState::DISALLOW_RELATIVE_SELECTOR) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - // Nested `:has()` is disallowed, mark it as such. - // Note: The spec defines ":has-allowed pseudo-element," but there's no - // pseudo-element defined as such at the moment. - // https://w3c.github.io/csswg-drafts/selectors-4/#has-allowed-pseudo-element - let inner = SelectorList::parse_with_state( - parser, - input, - state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS | - SelectorParsingState::DISALLOW_RELATIVE_SELECTOR, - ForgivingParsing::No, - ParseRelative::Yes, - )?; - Ok(Component::Has(RelativeSelector::from_selector_list(inner))) -} - -fn parse_functional_pseudo_class<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - name: CowRcStr<'i>, - state: SelectorParsingState, -) -> Result<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - match_ignore_ascii_case! { &name, - "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), - "is" if parser.parse_is_and_where() => return parse_is_where(parser, input, state, Component::Is), - "where" if parser.parse_is_and_where() => return parse_is_where(parser, input, state, Component::Where), - "has" if parser.parse_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 parser.parse_is_and_where() && parser.is_is_alias(&name) { - return parse_is_where(parser, input, state, Component::Is); - } - - 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<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - 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 !parser.parse_nth_child_of() || ty.is_of_type() { - return Ok(Component::Nth(nth_data)); - } - - // Try to parse "of <selector-list>". - 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 selectors = SelectorList::parse_with_state( - parser, - input, - state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS, - ForgivingParsing::No, - ParseRelative::No, - )?; - Ok(Component::NthOf(NthOfSelectorData::new( - &nth_data, - selectors.0.drain(..), - ))) -} - -/// 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: SelectorParsingState, -) -> Result<Option<SimpleSelectorParseResult<Impl>>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - let start = input.state(); - let token = match input.next_including_whitespace().map(|t| t.clone()) { - Ok(t) => t, - Err(..) => { - input.reset(&start); - return Ok(None); - }, - }; - - Ok(Some(match token { - Token::IDHash(id) => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let id = Component::ID(id.as_ref().into()); - SimpleSelectorParseResult::SimpleSelector(id) - }, - Token::Delim(delim) if delim == '.' || (delim == '&' && parser.parse_parent_selector()) => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let location = input.current_source_location(); - SimpleSelectorParseResult::SimpleSelector(if delim == '&' { - Component::ParentSelector - } else { - let class = match *input.next_including_whitespace()? { - Token::Ident(ref class) => class, - ref t => { - let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone()); - return Err(location.new_custom_error(e)); - }, - }; - Component::Class(class.as_ref().into()) - }) - }, - Token::SquareBracketBlock => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?; - 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()?.as_ref().into()); - while !input.is_exhausted() { - result.push(input.expect_ident()?.as_ref().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) - } - }, - _ => { - input.reset(&start); - return Ok(None); - }, - })) -} - -fn parse_simple_pseudo_class<'i, P, Impl>( - parser: &P, - location: SourceLocation, - name: CowRcStr<'i>, - state: SelectorParsingState, -) -> Result<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - 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))), - _ => {}, - } - } - - let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name)?; - if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) && - !pseudo_class.is_user_action_state() - { - return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - Ok(Component::NonTSPseudoClass(pseudo_class)) -} - -// NB: pub module in order to access the DummyParser -#[cfg(test)] -pub mod tests { - use super::*; - use crate::builder::SelectorFlags; - use crate::parser; - use cssparser::{serialize_identifier, Parser as CssParser, ParserInput, ToCss}; - use std::collections::HashMap; - use std::fmt; - - #[derive(Clone, Debug, Eq, PartialEq)] - pub enum PseudoClass { - Hover, - Active, - Lang(String), - } - - #[derive(Clone, Debug, Eq, PartialEq)] - pub enum PseudoElement { - Before, - After, - Highlight(String), - } - - impl parser::PseudoElement for PseudoElement { - type Impl = DummySelectorImpl; - - fn accepts_state_pseudo_classes(&self) -> bool { - true - } - - fn valid_after_slotted(&self) -> bool { - true - } - } - - impl parser::NonTSPseudoClass 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<W>(&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<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - match *self { - PseudoElement::Before => dest.write_str("::before"), - PseudoElement::After => dest.write_str("::after"), - PseudoElement::Highlight(ref name) => { - dest.write_str("::highlight(")?; - serialize_identifier(&name, dest)?; - dest.write_char(')') - }, - } - } - } - - #[derive(Clone, Debug, PartialEq)] - pub struct DummySelectorImpl; - - #[derive(Default)] - pub struct DummyParser { - default_ns: Option<DummyAtom>, - ns_prefixes: HashMap<DummyAtom, DummyAtom>, - } - - impl DummyParser { - fn default_with_namespace(default_ns: DummyAtom) -> DummyParser { - DummyParser { - default_ns: Some(default_ns), - ns_prefixes: Default::default(), - } - } - } - - impl SelectorImpl for DummySelectorImpl { - type ExtraMatchingData<'a> = std::marker::PhantomData<&'a ()>; - 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; - } - - #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] - pub struct DummyAttrValue(String); - - impl ToCss for DummyAttrValue { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - use std::fmt::Write; - - write!(cssparser::CssStringWriter::new(dest), "{}", &self.0) - } - } - - impl<'a> From<&'a str> for DummyAttrValue { - fn from(string: &'a str) -> Self { - Self(string.into()) - } - } - - #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] - pub struct DummyAtom(String); - - impl ToCss for DummyAtom { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - serialize_identifier(&self.0, dest) - } - } - - impl From<String> 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<'i> Parser<'i> for DummyParser { - type Impl = DummySelectorImpl; - type Error = SelectorParseErrorKind<'i>; - - fn parse_slotted(&self) -> bool { - true - } - - fn parse_nth_child_of(&self) -> bool { - true - } - - fn parse_is_and_where(&self) -> bool { - true - } - - fn parse_has(&self) -> bool { - true - } - - fn parse_parent_selector(&self) -> bool { - true - } - - fn parse_part(&self) -> bool { - true - } - - fn parse_non_ts_pseudo_class( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<PseudoClass, SelectorParseError<'i>> { - match_ignore_ascii_case! { &name, - "hover" => return Ok(PseudoClass::Hover), - "active" => return Ok(PseudoClass::Active), - _ => {} - } - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_non_ts_functional_pseudo_class<'t>( - &self, - name: CowRcStr<'i>, - parser: &mut CssParser<'i, 't>, - ) -> Result<PseudoClass, SelectorParseError<'i>> { - 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::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_pseudo_element( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<PseudoElement, SelectorParseError<'i>> { - match_ignore_ascii_case! { &name, - "before" => return Ok(PseudoElement::Before), - "after" => return Ok(PseudoElement::After), - _ => {} - } - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_functional_pseudo_element<'t>( - &self, - name: CowRcStr<'i>, - parser: &mut CssParser<'i, 't>, - ) -> Result<PseudoElement, SelectorParseError<'i>> { - match_ignore_ascii_case! {&name, - "highlight" => return Ok(PseudoElement::Highlight(parser.expect_ident()?.as_ref().to_owned())), - _ => {} - } - Err( - parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn default_namespace(&self) -> Option<DummyAtom> { - self.default_ns.clone() - } - - fn namespace_for_prefix(&self, prefix: &DummyAtom) -> Option<DummyAtom> { - self.ns_prefixes.get(prefix).cloned() - } - } - - fn parse<'i>( - input: &'i str, - ) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> { - parse_ns(input, &DummyParser::default()) - } - - fn parse_expected<'i, 'a>( - input: &'i str, - expected: Option<&'a str>, - ) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> { - parse_ns_expected(input, &DummyParser::default(), expected) - } - - fn parse_ns<'i>( - input: &'i str, - parser: &DummyParser, - ) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> { - parse_ns_expected(input, parser, None) - } - - fn parse_ns_expected<'i, 'a>( - input: &'i str, - parser: &DummyParser, - expected: Option<&'a str>, - ) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> { - let mut parser_input = ParserInput::new(input); - let result = SelectorList::parse(parser, &mut CssParser::new(&mut parser_input)); - if let Ok(ref selectors) = result { - // 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.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)); - 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"), - 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("::highlight(foo)").is_ok()); - - 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_ok()); - assert!(parse("foo:where(div, foo, .bar baz)").is_ok()); - assert!(parse_expected("foo:where(::before)", Some("foo:where()")).is_ok()); - } - - #[test] - fn parent_selector() { - assert!(parse("foo &").is_ok()); - assert_eq!( - parse("#foo &.bar"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ID(DummyAtom::from("foo")), - Component::Combinator(Combinator::Descendant), - Component::ParentSelector, - Component::Class(DummyAtom::from("bar")), - ], - (1 << 20) + (1 << 10) + (0 << 0), - SelectorFlags::HAS_PARENT - )])) - ); - - let parent = parse(".bar, div .baz").unwrap(); - let child = parse("#foo &.bar").unwrap(); - assert_eq!( - SelectorList::from_vec(vec![child.0[0].replace_parent_selector(&parent.0)]), - parse("#foo :is(.bar, div .baz).bar").unwrap() - ); - - let has_child = parse("#foo:has(&.bar)").unwrap(); - assert_eq!( - SelectorList::from_vec(vec![has_child.0[0].replace_parent_selector(&parent.0)]), - parse("#foo:has(:is(.bar, div .baz).bar)").unwrap() - ); - - let child = parse("#foo").unwrap(); - assert_eq!( - SelectorList::from_vec(vec![child.0[0].replace_parent_selector(&parent.0)]), - parse(":is(.bar, div .baz) #foo").unwrap() - ); - } - - #[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<String>, - } - - impl SelectorVisitor for TestVisitor { - type Impl = DummySelectorImpl; - - fn visit_simple_selector(&mut self, s: &Component<DummySelectorImpl>) -> 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())); - } -} |