forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			2940 lines
		
	
	
	
		
			104 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			2940 lines
		
	
	
	
		
			104 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| /* 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 http://mozilla.org/MPL/2.0/. */
 | ||
| 
 | ||
| use attr::{AttrSelectorOperator, AttrSelectorWithNamespace, ParsedAttrSelectorOperation};
 | ||
| use attr::{NamespaceConstraint, ParsedCaseSensitivity, SELECTOR_WHITESPACE};
 | ||
| use bloom::BLOOM_HASH_MASK;
 | ||
| use builder::{SelectorBuilder, SpecificityAndFlags};
 | ||
| use context::QuirksMode;
 | ||
| use cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind};
 | ||
| use cssparser::{CowRcStr, Delimiter, SourceLocation};
 | ||
| use cssparser::{CssStringWriter, Parser as CssParser, ToCss, Token};
 | ||
| use cssparser::{parse_nth, serialize_identifier};
 | ||
| use precomputed_hash::PrecomputedHash;
 | ||
| use servo_arc::ThinArc;
 | ||
| use sink::Push;
 | ||
| use smallvec::SmallVec;
 | ||
| use std::borrow::{Borrow, Cow};
 | ||
| use std::fmt::{self, Debug, Display, Write};
 | ||
| use std::iter::Rev;
 | ||
| use std::slice;
 | ||
| pub use visitor::{SelectorVisitor, Visit};
 | ||
| 
 | ||
| /// 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 supports_pseudo_class(
 | ||
|         &self,
 | ||
|         _pseudo_class: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
 | ||
|     ) -> 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;
 | ||
| }
 | ||
| 
 | ||
| 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()
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>;
 | ||
| 
 | ||
| #[derive(Clone, Debug, PartialEq)]
 | ||
| pub enum SelectorParseErrorKind<'i> {
 | ||
|     PseudoElementInComplexSelector,
 | ||
|     NoQualifiedNameInAttributeSelector(Token<'i>),
 | ||
|     EmptySelector,
 | ||
|     DanglingCombinator,
 | ||
|     NonSimpleSelectorInNegation,
 | ||
|     NonCompoundSelector,
 | ||
|     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>),
 | ||
|     EmptyNegation,
 | ||
| }
 | ||
| 
 | ||
| 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: Sized + Default + 'static;
 | ||
|             type AttrValue: $($InSelector)*;
 | ||
|             type Identifier: $($InSelector)*;
 | ||
|             type ClassName: $($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>;
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| macro_rules! with_bounds {
 | ||
|     ( [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ]) => {
 | ||
|         with_all_bounds! {
 | ||
|             [$($CommonBounds)* + $($FromStr)* + Display]
 | ||
|             [$($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 the name is a pseudo-element that can be specified with
 | ||
|     /// the single colon syntax in addition to the double-colon syntax.
 | ||
|     fn pseudo_element_allows_single_colon(name: &str) -> bool {
 | ||
|         is_css2_pseudo_element(name)
 | ||
|     }
 | ||
| 
 | ||
|     /// Whether to parse the `::slotted()` pseudo-element.
 | ||
|     fn parse_slotted(&self) -> bool {
 | ||
|         false
 | ||
|     }
 | ||
| 
 | ||
|     /// Whether to parse the `:host` pseudo-class.
 | ||
|     fn parse_host(&self) -> bool {
 | ||
|         false
 | ||
|     }
 | ||
| 
 | ||
|     /// This function can return an "Err" pseudo-element in order to support CSS2.1
 | ||
|     /// pseudo-elements.
 | ||
|     fn parse_non_ts_pseudo_class(
 | ||
|         &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)]
 | ||
| pub struct SelectorList<Impl: SelectorImpl>(pub SmallVec<[Selector<Impl>; 1]>);
 | ||
| 
 | ||
| impl<Impl: SelectorImpl> SelectorList<Impl> {
 | ||
|     /// 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>,
 | ||
|     {
 | ||
|         let mut values = SmallVec::new();
 | ||
|         loop {
 | ||
|             values
 | ||
|                 .push(input
 | ||
|                     .parse_until_before(Delimiter::Comma, |input| parse_selector(parser, input))?);
 | ||
|             match input.next() {
 | ||
|                 Err(_) => return Ok(SelectorList(values)),
 | ||
|                 Ok(&Token::Comma) => continue,
 | ||
|                 Ok(_) => unreachable!(),
 | ||
|             }
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// Creates a SelectorList from a Vec of selectors. Used in tests.
 | ||
|     pub 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>,
 | ||
| ) -> Result<Selector<Impl>, ParseError<'i, P::Error>>
 | ||
| where
 | ||
|     P: Parser<'i, Impl = Impl>,
 | ||
|     Impl: SelectorImpl,
 | ||
| {
 | ||
|     let location = input.current_source_location();
 | ||
|     let selector = Selector::parse(parser, input)?;
 | ||
|     // Ensure they're actually all compound selectors.
 | ||
|     if selector.iter_raw_match_order().any(|s| s.is_combinator()) {
 | ||
|         return Err(location.new_custom_error(SelectorParseErrorKind::NonCompoundSelector));
 | ||
|     }
 | ||
| 
 | ||
|     Ok(selector)
 | ||
| }
 | ||
| 
 | ||
| /// Parse a comma separated list of compound selectors.
 | ||
| pub fn parse_compound_selector_list<'i, 't, P, Impl>(
 | ||
|     parser: &P,
 | ||
|     input: &mut CssParser<'i, 't>,
 | ||
| ) -> Result<Box<[Selector<Impl>]>, ParseError<'i, P::Error>>
 | ||
| where
 | ||
|     P: Parser<'i, Impl = Impl>,
 | ||
|     Impl: SelectorImpl,
 | ||
| {
 | ||
|     input
 | ||
|         .parse_comma_separated(|input| parse_inner_compound_selector(parser, input))
 | ||
|         .map(|selectors| selectors.into_boxed_slice())
 | ||
| }
 | ||
| 
 | ||
| /// 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],
 | ||
| }
 | ||
| 
 | ||
| impl AncestorHashes {
 | ||
|     pub fn new<Impl: SelectorImpl>(selector: &Selector<Impl>, quirks_mode: QuirksMode) -> Self
 | ||
|     where
 | ||
|         Impl::Identifier: PrecomputedHash,
 | ||
|         Impl::ClassName: PrecomputedHash,
 | ||
|         Impl::LocalName: PrecomputedHash,
 | ||
|         Impl::NamespaceUrl: PrecomputedHash,
 | ||
|     {
 | ||
|         Self::from_iter(selector.iter(), quirks_mode)
 | ||
|     }
 | ||
| 
 | ||
|     fn from_iter<Impl: SelectorImpl>(iter: SelectorIter<Impl>, quirks_mode: QuirksMode) -> Self
 | ||
|     where
 | ||
|         Impl::Identifier: PrecomputedHash,
 | ||
|         Impl::ClassName: PrecomputedHash,
 | ||
|         Impl::LocalName: PrecomputedHash,
 | ||
|         Impl::NamespaceUrl: PrecomputedHash,
 | ||
|     {
 | ||
|         // Compute ancestor hashes for the bloom filter.
 | ||
|         let mut hashes = [0u32; 4];
 | ||
|         let mut hash_iter = AncestorIter::new(iter).filter_map(|x| x.ancestor_hash(quirks_mode));
 | ||
|         for i in 0..4 {
 | ||
|             hashes[i] = match hash_iter.next() {
 | ||
|                 Some(x) => x & BLOOM_HASH_MASK,
 | ||
|                 None => break,
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         // Now, pack the fourth hash (if it exists) into the upper byte of each of
 | ||
|         // the other three hashes.
 | ||
|         let fourth = hashes[3];
 | ||
|         if fourth != 0 {
 | ||
|             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)
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl<Impl: SelectorImpl> Visit for Selector<Impl>
 | ||
| where
 | ||
|     Impl::NonTSPseudoClass: Visit<Impl = Impl>,
 | ||
| {
 | ||
|     type Impl = Impl;
 | ||
| 
 | ||
|     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
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl<Impl: SelectorImpl> Visit for Component<Impl>
 | ||
| where
 | ||
|     Impl::NonTSPseudoClass: Visit<Impl = Impl>,
 | ||
| {
 | ||
|     type Impl = Impl;
 | ||
| 
 | ||
|     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;
 | ||
|                 }
 | ||
|             },
 | ||
|             Negation(ref negated) => {
 | ||
|                 for component in negated.iter() {
 | ||
|                     if !component.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,
 | ||
|                 ref local_name_lower,
 | ||
|                 never_matches,
 | ||
|                 ..
 | ||
|             } if !never_matches =>
 | ||
|             {
 | ||
|                 if !visitor.visit_attribute_selector(
 | ||
|                     &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()),
 | ||
|                     local_name,
 | ||
|                     local_name_lower,
 | ||
|                 ) {
 | ||
|                     return false;
 | ||
|                 }
 | ||
|             },
 | ||
|             AttributeOther(ref attr_selector) if !attr_selector.never_matches => {
 | ||
|                 if !visitor.visit_attribute_selector(
 | ||
|                     &attr_selector.namespace(),
 | ||
|                     &attr_selector.local_name,
 | ||
|                     &attr_selector.local_name_lower,
 | ||
|                 ) {
 | ||
|                     return false;
 | ||
|                 }
 | ||
|             },
 | ||
| 
 | ||
|             NonTSPseudoClass(ref pseudo_class) => {
 | ||
|                 if !pseudo_class.visit(visitor) {
 | ||
|                     return false;
 | ||
|                 }
 | ||
|             },
 | ||
|             _ => {},
 | ||
|         }
 | ||
| 
 | ||
|         true
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 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)]
 | ||
| pub struct Selector<Impl: SelectorImpl>(ThinArc<SpecificityAndFlags, Component<Impl>>);
 | ||
| 
 | ||
| impl<Impl: SelectorImpl> Selector<Impl> {
 | ||
|     #[inline]
 | ||
|     pub fn specificity(&self) -> u32 {
 | ||
|         self.0.header.header.specificity()
 | ||
|     }
 | ||
| 
 | ||
|     #[inline]
 | ||
|     pub fn has_pseudo_element(&self) -> bool {
 | ||
|         self.0.header.header.has_pseudo_element()
 | ||
|     }
 | ||
| 
 | ||
|     #[inline]
 | ||
|     pub fn is_slotted(&self) -> bool {
 | ||
|         self.0.header.header.is_slotted()
 | ||
|     }
 | ||
| 
 | ||
|     #[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,
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// 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: 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.
 | ||
|     pub fn from_vec(vec: Vec<Component<Impl>>, specificity_and_flags: u32) -> 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_and_flags);
 | ||
|         Selector(builder.build_with_specificity_and_flags(spec))
 | ||
|     }
 | ||
| 
 | ||
|     /// 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()
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| #[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.all(|component| matches!(*component, Component::Host(..))) &&
 | ||
|             self.next_sequence().is_none()
 | ||
|     }
 | ||
| 
 | ||
|     /// 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 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)]
 | ||
| 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,
 | ||
| }
 | ||
| 
 | ||
| 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)
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /// 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)]
 | ||
| pub enum Component<Impl: SelectorImpl> {
 | ||
|     Combinator(Combinator),
 | ||
| 
 | ||
|     ExplicitAnyNamespace,
 | ||
|     ExplicitNoNamespace,
 | ||
|     DefaultNamespace(Impl::NamespaceUrl),
 | ||
|     Namespace(Impl::NamespacePrefix, Impl::NamespaceUrl),
 | ||
| 
 | ||
|     ExplicitUniversalType,
 | ||
|     LocalName(LocalName<Impl>),
 | ||
| 
 | ||
|     ID(Impl::Identifier),
 | ||
|     Class(Impl::ClassName),
 | ||
| 
 | ||
|     AttributeInNoNamespaceExists {
 | ||
|         local_name: Impl::LocalName,
 | ||
|         local_name_lower: Impl::LocalName,
 | ||
|     },
 | ||
|     AttributeInNoNamespace {
 | ||
|         local_name: Impl::LocalName,
 | ||
|         local_name_lower: Impl::LocalName,
 | ||
|         operator: AttrSelectorOperator,
 | ||
|         value: Impl::AttrValue,
 | ||
|         case_sensitivity: ParsedCaseSensitivity,
 | ||
|         never_matches: bool,
 | ||
|     },
 | ||
|     // Use a Box in the less common cases with more data to keep size_of::<Component>() small.
 | ||
|     AttributeOther(Box<AttrSelectorWithNamespace<Impl>>),
 | ||
| 
 | ||
|     /// Pseudo-classes
 | ||
|     ///
 | ||
|     /// CSS3 Negation only takes a simple simple selector, but we still need to
 | ||
|     /// treat it as a compound selector because it might be a type selector
 | ||
|     /// which we represent as a namespace and a localname.
 | ||
|     ///
 | ||
|     /// Note: if/when we upgrade this to CSS4, which supports combinators, we
 | ||
|     /// need to think about how this should interact with
 | ||
|     /// visit_complex_selector, and what the consumers of those APIs should do
 | ||
|     /// about the presence of combinators in negation.
 | ||
|     Negation(Box<[Component<Impl>]>),
 | ||
|     FirstChild,
 | ||
|     LastChild,
 | ||
|     OnlyChild,
 | ||
|     Root,
 | ||
|     Empty,
 | ||
|     Scope,
 | ||
|     NthChild(i32, i32),
 | ||
|     NthLastChild(i32, i32),
 | ||
|     NthOfType(i32, i32),
 | ||
|     NthLastOfType(i32, i32),
 | ||
|     FirstOfType,
 | ||
|     LastOfType,
 | ||
|     OnlyOfType,
 | ||
|     NonTSPseudoClass(Impl::NonTSPseudoClass),
 | ||
|     /// The ::slotted() pseudo-element (which isn't actually a pseudo-element,
 | ||
|     /// and probably should be a pseudo-class):
 | ||
|     ///
 | ||
|     /// https://drafts.csswg.org/css-scoping/#slotted-pseudo
 | ||
|     ///
 | ||
|     /// The 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 `: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>>),
 | ||
|     PseudoElement(Impl::PseudoElement),
 | ||
| }
 | ||
| 
 | ||
| impl<Impl: SelectorImpl> Component<Impl> {
 | ||
|     /// Compute the ancestor hash to check against the bloom filter.
 | ||
|     fn ancestor_hash(&self, quirks_mode: QuirksMode) -> Option<u32>
 | ||
|     where
 | ||
|         Impl::Identifier: PrecomputedHash,
 | ||
|         Impl::ClassName: PrecomputedHash,
 | ||
|         Impl::LocalName: PrecomputedHash,
 | ||
|         Impl::NamespaceUrl: PrecomputedHash,
 | ||
|     {
 | ||
|         match *self {
 | ||
|             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 {
 | ||
|                     Some(name.precomputed_hash())
 | ||
|                 } else {
 | ||
|                     None
 | ||
|                 }
 | ||
|             },
 | ||
|             Component::DefaultNamespace(ref url) | Component::Namespace(_, ref url) => {
 | ||
|                 Some(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 => {
 | ||
|                 Some(id.precomputed_hash())
 | ||
|             },
 | ||
|             Component::Class(ref class) if quirks_mode != QuirksMode::Quirks => {
 | ||
|                 Some(class.precomputed_hash())
 | ||
|             },
 | ||
|             _ => None,
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// Returns true if this is a combinator.
 | ||
|     pub fn is_combinator(&self) -> bool {
 | ||
|         matches!(*self, Component::Combinator(_))
 | ||
|     }
 | ||
| 
 | ||
|     /// Returns the value as a combinator if applicable, None otherwise.
 | ||
|     pub fn as_combinator(&self) -> Option<Combinator> {
 | ||
|         match *self {
 | ||
|             Component::Combinator(c) => Some(c),
 | ||
|             _ => None,
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| #[derive(Clone, Eq, PartialEq)]
 | ||
| pub struct LocalName<Impl: SelectorImpl> {
 | ||
|     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 = 0x{:x})", self.specificity())
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 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 AttrSelectorWithNamespace<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)
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl<Impl: SelectorImpl> ToCss for SelectorList<Impl> {
 | ||
|     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
 | ||
|     where
 | ||
|         W: fmt::Write,
 | ||
|     {
 | ||
|         let mut iter = self.0.iter();
 | ||
|         let first = iter.next()
 | ||
|             .expect("Empty SelectorList, should contain at least one selector");
 | ||
|         first.to_css(dest)?;
 | ||
|         for selector in iter {
 | ||
|             dest.write_str(", ")?;
 | ||
|             selector.to_css(dest)?;
 | ||
|         }
 | ||
|         Ok(())
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 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;
 | ||
|             }
 | ||
| 
 | ||
|             // 1. If there is only one simple selector in the compound selectors
 | ||
|             //    which is a universal selector, append the result of
 | ||
|             //    serializing the universal selector to s.
 | ||
|             //
 | ||
|             // Check if `!compound.empty()` first--this can happen if we have
 | ||
|             // something like `... > ::before`, because we store `>` and `::`
 | ||
|             // both as combinators internally.
 | ||
|             //
 | ||
|             // If we are in this case, after we have serialized the universal
 | ||
|             // selector, we skip Step 2 and continue with the algorithm.
 | ||
|             let (can_elide_namespace, first_non_namespace) = match compound[0] {
 | ||
|                 Component::ExplicitAnyNamespace |
 | ||
|                 Component::ExplicitNoNamespace |
 | ||
|                 Component::Namespace(..) => (false, 1),
 | ||
|                 Component::DefaultNamespace(..) => (true, 1),
 | ||
|                 _ => (true, 0),
 | ||
|             };
 | ||
|             let mut perform_step_2 = true;
 | ||
|             let next_combinator = combinators.next();
 | ||
|             if first_non_namespace == compound.len() - 1 {
 | ||
|                 match (next_combinator, &compound[first_non_namespace]) {
 | ||
|                     // We have to be careful here, because if there is a
 | ||
|                     // pseudo element "combinator" there isn't really just
 | ||
|                     // the one simple selector. Technically this compound
 | ||
|                     // selector contains the pseudo element selector as well
 | ||
|                     // -- Combinator::PseudoElement, just like
 | ||
|                     // Combinator::SlotAssignment, don't exist in the
 | ||
|                     // spec.
 | ||
|                     (Some(Combinator::PseudoElement), _) |
 | ||
|                     (Some(Combinator::SlotAssignment), _) => (),
 | ||
|                     (_, &Component::ExplicitUniversalType) => {
 | ||
|                         // Iterate over everything so we serialize the namespace
 | ||
|                         // too.
 | ||
|                         for simple in compound.iter() {
 | ||
|                             simple.to_css(dest)?;
 | ||
|                         }
 | ||
|                         // Skip step 2, which is an "otherwise".
 | ||
|                         perform_step_2 = false;
 | ||
|                     },
 | ||
|                     _ => (),
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             // 2. Otherwise, for each simple selector in the compound selectors
 | ||
|             //    that is not a universal selector of which the namespace prefix
 | ||
|             //    maps to a namespace that is not the default namespace
 | ||
|             //    serialize the simple selector and append the result to s.
 | ||
|             //
 | ||
|             // See https://github.com/w3c/csswg-drafts/issues/1606, which is
 | ||
|             // proposing to change this to match up with the behavior asserted
 | ||
|             // in cssom/serialize-namespaced-type-selectors.html, which the
 | ||
|             // following code tries to match.
 | ||
|             if perform_step_2 {
 | ||
|                 for simple in compound.iter() {
 | ||
|                     if let Component::ExplicitUniversalType = *simple {
 | ||
|                         // Can't have a namespace followed by a pseudo-element
 | ||
|                         // selector followed by a universal selector in the same
 | ||
|                         // compound selector, so we don't have to worry about the
 | ||
|                         // real namespace being in a different `compound`.
 | ||
|                         if can_elide_namespace {
 | ||
|                             continue;
 | ||
|                         }
 | ||
|                     }
 | ||
|                     simple.to_css(dest)?;
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             // 3. If this is not the last part of the chain of the selector
 | ||
|             //    append a single SPACE (U+0020), followed by the combinator
 | ||
|             //    ">", "+", "~", ">>", "||", as appropriate, followed by another
 | ||
|             //    single SPACE (U+0020) if the combinator was not whitespace, to
 | ||
|             //    s.
 | ||
|             match next_combinator {
 | ||
|                 Some(c) => c.to_css(dest)?,
 | ||
|                 None => combinators_exhausted = true,
 | ||
|             };
 | ||
| 
 | ||
|             // 4. If this is the last part of the chain of the selector and
 | ||
|             //    there is a pseudo-element, append "::" followed by the name of
 | ||
|             //    the pseudo-element, to s.
 | ||
|             //
 | ||
|             // (we handle this above)
 | ||
|         }
 | ||
| 
 | ||
|         Ok(())
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl ToCss for Combinator {
 | ||
|     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
 | ||
|     where
 | ||
|         W: fmt::Write,
 | ||
|     {
 | ||
|         match *self {
 | ||
|             Combinator::Child => dest.write_str(" > "),
 | ||
|             Combinator::Descendant => dest.write_str(" "),
 | ||
|             Combinator::NextSibling => dest.write_str(" + "),
 | ||
|             Combinator::LaterSibling => dest.write_str(" ~ "),
 | ||
|             Combinator::PseudoElement => Ok(()),
 | ||
|             Combinator::SlotAssignment => Ok(()),
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl<Impl: SelectorImpl> ToCss for Component<Impl> {
 | ||
|     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
 | ||
|     where
 | ||
|         W: fmt::Write,
 | ||
|     {
 | ||
|         use self::Component::*;
 | ||
| 
 | ||
|         /// Serialize <an+b> values (part of the CSS Syntax spec, but currently only used here).
 | ||
|         /// <https://drafts.csswg.org/css-syntax-3/#serialize-an-anb-value>
 | ||
|         fn write_affine<W>(dest: &mut W, a: i32, b: i32) -> fmt::Result
 | ||
|         where
 | ||
|             W: fmt::Write,
 | ||
|         {
 | ||
|             match (a, b) {
 | ||
|                 (0, 0) => dest.write_char('0'),
 | ||
| 
 | ||
|                 (1, 0) => dest.write_char('n'),
 | ||
|                 (-1, 0) => dest.write_str("-n"),
 | ||
|                 (_, 0) => write!(dest, "{}n", a),
 | ||
| 
 | ||
|                 (0, _) => write!(dest, "{}", b),
 | ||
|                 (1, _) => write!(dest, "n{:+}", b),
 | ||
|                 (-1, _) => write!(dest, "-n{:+}", b),
 | ||
|                 (_, _) => write!(dest, "{}n{:+}", a, b),
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         match *self {
 | ||
|             Combinator(ref c) => c.to_css(dest),
 | ||
|             Slotted(ref selector) => {
 | ||
|                 dest.write_str("::slotted(")?;
 | ||
|                 selector.to_css(dest)?;
 | ||
|                 dest.write_char(')')
 | ||
|             },
 | ||
|             PseudoElement(ref p) => p.to_css(dest),
 | ||
|             ID(ref s) => {
 | ||
|                 dest.write_char('#')?;
 | ||
|                 display_to_css_identifier(s, dest)
 | ||
|             },
 | ||
|             Class(ref s) => {
 | ||
|                 dest.write_char('.')?;
 | ||
|                 display_to_css_identifier(s, 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, _) => {
 | ||
|                 display_to_css_identifier(prefix, dest)?;
 | ||
|                 dest.write_char('|')
 | ||
|             },
 | ||
| 
 | ||
|             AttributeInNoNamespaceExists { ref local_name, .. } => {
 | ||
|                 dest.write_char('[')?;
 | ||
|                 display_to_css_identifier(local_name, dest)?;
 | ||
|                 dest.write_char(']')
 | ||
|             },
 | ||
|             AttributeInNoNamespace {
 | ||
|                 ref local_name,
 | ||
|                 operator,
 | ||
|                 ref value,
 | ||
|                 case_sensitivity,
 | ||
|                 ..
 | ||
|             } => {
 | ||
|                 dest.write_char('[')?;
 | ||
|                 display_to_css_identifier(local_name, dest)?;
 | ||
|                 operator.to_css(dest)?;
 | ||
|                 dest.write_char('"')?;
 | ||
|                 write!(CssStringWriter::new(dest), "{}", value)?;
 | ||
|                 dest.write_char('"')?;
 | ||
|                 match case_sensitivity {
 | ||
|                     ParsedCaseSensitivity::CaseSensitive |
 | ||
|                     ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {},
 | ||
|                     ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?,
 | ||
|                 }
 | ||
|                 dest.write_char(']')
 | ||
|             },
 | ||
|             AttributeOther(ref attr_selector) => attr_selector.to_css(dest),
 | ||
| 
 | ||
|             // Pseudo-classes
 | ||
|             Negation(ref arg) => {
 | ||
|                 dest.write_str(":not(")?;
 | ||
|                 for component in arg.iter() {
 | ||
|                     component.to_css(dest)?;
 | ||
|                 }
 | ||
|                 dest.write_str(")")
 | ||
|             },
 | ||
| 
 | ||
|             FirstChild => dest.write_str(":first-child"),
 | ||
|             LastChild => dest.write_str(":last-child"),
 | ||
|             OnlyChild => dest.write_str(":only-child"),
 | ||
|             Root => dest.write_str(":root"),
 | ||
|             Empty => dest.write_str(":empty"),
 | ||
|             Scope => dest.write_str(":scope"),
 | ||
|             Host(ref selector) => {
 | ||
|                 dest.write_str(":host")?;
 | ||
|                 if let Some(ref selector) = *selector {
 | ||
|                     dest.write_char('(')?;
 | ||
|                     selector.to_css(dest)?;
 | ||
|                     dest.write_char(')')?;
 | ||
|                 }
 | ||
|                 Ok(())
 | ||
|             },
 | ||
|             FirstOfType => dest.write_str(":first-of-type"),
 | ||
|             LastOfType => dest.write_str(":last-of-type"),
 | ||
|             OnlyOfType => dest.write_str(":only-of-type"),
 | ||
|             NthChild(a, b) | NthLastChild(a, b) | NthOfType(a, b) | NthLastOfType(a, b) => {
 | ||
|                 match *self {
 | ||
|                     NthChild(_, _) => dest.write_str(":nth-child(")?,
 | ||
|                     NthLastChild(_, _) => dest.write_str(":nth-last-child(")?,
 | ||
|                     NthOfType(_, _) => dest.write_str(":nth-of-type(")?,
 | ||
|                     NthLastOfType(_, _) => dest.write_str(":nth-last-of-type(")?,
 | ||
|                     _ => unreachable!(),
 | ||
|                 }
 | ||
|                 write_affine(dest, a, b)?;
 | ||
|                 dest.write_char(')')
 | ||
|             },
 | ||
|             NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest),
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl<Impl: SelectorImpl> ToCss for AttrSelectorWithNamespace<Impl> {
 | ||
|     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
 | ||
|     where
 | ||
|         W: fmt::Write,
 | ||
|     {
 | ||
|         dest.write_char('[')?;
 | ||
|         match self.namespace {
 | ||
|             NamespaceConstraint::Specific((ref prefix, _)) => {
 | ||
|                 display_to_css_identifier(prefix, dest)?;
 | ||
|                 dest.write_char('|')?
 | ||
|             },
 | ||
|             NamespaceConstraint::Any => dest.write_str("*|")?,
 | ||
|         }
 | ||
|         display_to_css_identifier(&self.local_name, dest)?;
 | ||
|         match self.operation {
 | ||
|             ParsedAttrSelectorOperation::Exists => {},
 | ||
|             ParsedAttrSelectorOperation::WithValue {
 | ||
|                 operator,
 | ||
|                 case_sensitivity,
 | ||
|                 ref expected_value,
 | ||
|             } => {
 | ||
|                 operator.to_css(dest)?;
 | ||
|                 dest.write_char('"')?;
 | ||
|                 write!(CssStringWriter::new(dest), "{}", expected_value)?;
 | ||
|                 dest.write_char('"')?;
 | ||
|                 match case_sensitivity {
 | ||
|                     ParsedCaseSensitivity::CaseSensitive |
 | ||
|                     ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {},
 | ||
|                     ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?,
 | ||
|                 }
 | ||
|             },
 | ||
|         }
 | ||
|         dest.write_char(']')
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl<Impl: SelectorImpl> ToCss for LocalName<Impl> {
 | ||
|     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
 | ||
|     where
 | ||
|         W: fmt::Write,
 | ||
|     {
 | ||
|         display_to_css_identifier(&self.name, dest)
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /// Serialize the output of Display as a CSS identifier
 | ||
| fn display_to_css_identifier<T: Display, W: fmt::Write>(x: &T, dest: &mut W) -> fmt::Result {
 | ||
|     // FIXME(SimonSapin): it is possible to avoid this heap allocation
 | ||
|     // by creating a stream adapter like cssparser::CssStringWriter
 | ||
|     // that holds and writes to `&mut W` and itself implements `fmt::Write`.
 | ||
|     //
 | ||
|     // I haven’t done this yet because it would require somewhat complex and fragile state machine
 | ||
|     // to support in `fmt::Write::write_char` cases that,
 | ||
|     // in `serialize_identifier` (which has the full value as a `&str` slice),
 | ||
|     // can be expressed as
 | ||
|     // `string.starts_with("--")`, `string == "-"`, `string.starts_with("-")`, etc.
 | ||
|     //
 | ||
|     // And I don’t even know if this would be a performance win: jemalloc is good at what it does
 | ||
|     // and the state machine might be slower than `serialize_identifier` as currently written.
 | ||
|     let string = x.to_string();
 | ||
| 
 | ||
|     serialize_identifier(&string, 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>,
 | ||
| ) -> Result<Selector<Impl>, ParseError<'i, P::Error>>
 | ||
| where
 | ||
|     P: Parser<'i, Impl = Impl>,
 | ||
|     Impl: SelectorImpl,
 | ||
| {
 | ||
|     let mut builder = SelectorBuilder::default();
 | ||
| 
 | ||
|     let mut has_pseudo_element;
 | ||
|     let mut slotted;
 | ||
|     'outer_loop: loop {
 | ||
|         // Parse a sequence of simple selectors.
 | ||
|         match parse_compound_selector(parser, input, &mut builder)? {
 | ||
|             Some((has_pseudo, slot)) => {
 | ||
|                 has_pseudo_element = has_pseudo;
 | ||
|                 slotted = slot;
 | ||
|             },
 | ||
|             None => {
 | ||
|                 return Err(input.new_custom_error(if builder.has_combinators() {
 | ||
|                     SelectorParseErrorKind::DanglingCombinator
 | ||
|                 } else {
 | ||
|                     SelectorParseErrorKind::EmptySelector
 | ||
|                 }))
 | ||
|             },
 | ||
|         };
 | ||
| 
 | ||
|         if has_pseudo_element || slotted {
 | ||
|             break;
 | ||
|         }
 | ||
| 
 | ||
|         // Parse a combinator.
 | ||
|         let combinator;
 | ||
|         let mut any_whitespace = false;
 | ||
|         loop {
 | ||
|             let before_this_token = input.state();
 | ||
|             match input.next_including_whitespace() {
 | ||
|                 Err(_e) => break 'outer_loop,
 | ||
|                 Ok(&Token::WhiteSpace(_)) => any_whitespace = true,
 | ||
|                 Ok(&Token::Delim('>')) => {
 | ||
|                     combinator = Combinator::Child;
 | ||
|                     break;
 | ||
|                 },
 | ||
|                 Ok(&Token::Delim('+')) => {
 | ||
|                     combinator = Combinator::NextSibling;
 | ||
|                     break;
 | ||
|                 },
 | ||
|                 Ok(&Token::Delim('~')) => {
 | ||
|                     combinator = Combinator::LaterSibling;
 | ||
|                     break;
 | ||
|                 },
 | ||
|                 Ok(_) => {
 | ||
|                     input.reset(&before_this_token);
 | ||
|                     if any_whitespace {
 | ||
|                         combinator = Combinator::Descendant;
 | ||
|                         break;
 | ||
|                     } else {
 | ||
|                         break 'outer_loop;
 | ||
|                     }
 | ||
|                 },
 | ||
|             }
 | ||
|         }
 | ||
|         builder.push_combinator(combinator);
 | ||
|     }
 | ||
| 
 | ||
|     Ok(Selector(builder.build(has_pseudo_element, slotted)))
 | ||
| }
 | ||
| 
 | ||
| impl<Impl: SelectorImpl> Selector<Impl> {
 | ||
|     /// Parse a selector, without any pseudo-element.
 | ||
|     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>,
 | ||
|     {
 | ||
|         let selector = parse_selector(parser, input)?;
 | ||
|         if selector.has_pseudo_element() {
 | ||
|             let e = SelectorParseErrorKind::PseudoElementInComplexSelector;
 | ||
|             return Err(input.new_custom_error(e));
 | ||
|         }
 | ||
|         Ok(selector)
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /// * `Err(())`: Invalid selector, abort
 | ||
| /// * `Ok(false)`: Not a type selector, could be something else. `input` was not consumed.
 | ||
| /// * `Ok(true)`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`)
 | ||
| fn parse_type_selector<'i, 't, P, Impl, S>(
 | ||
|     parser: &P,
 | ||
|     input: &mut CssParser<'i, 't>,
 | ||
|     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)) => {
 | ||
|             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>),
 | ||
| }
 | ||
| 
 | ||
| #[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();
 | ||
|     // FIXME: remove clone() when lifetimes are non-lexical
 | ||
|     match input.next_including_whitespace().map(|t| t.clone()) {
 | ||
|         Ok(Token::Ident(value)) => {
 | ||
|             let after_ident = input.state();
 | ||
|             match input.next_including_whitespace() {
 | ||
|                 Ok(&Token::Delim('|')) => {
 | ||
|                     let prefix = value.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();
 | ||
|             // FIXME: remove clone() when lifetimes are non-lexical
 | ||
|             match input.next_including_whitespace().map(|t| t.clone()) {
 | ||
|                 Ok(Token::Delim('|')) => {
 | ||
|                     explicit_namespace(input, QNamePrefix::ExplicitAnyNamespace)
 | ||
|                 },
 | ||
|                 result => {
 | ||
|                     input.reset(&after_star);
 | ||
|                     if in_attr_selector {
 | ||
|                         match result {
 | ||
|                             Ok(t) => Err(after_star
 | ||
|                                 .source_location()
 | ||
|                                 .new_custom_error(SelectorParseErrorKind::ExpectedBarInAttr(t))),
 | ||
|                             Err(e) => Err(e.into()),
 | ||
|                         }
 | ||
|                     } else {
 | ||
|                         default_namespace(None)
 | ||
|                     }
 | ||
|                 },
 | ||
|             }
 | ||
|         },
 | ||
|         Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitNoNamespace),
 | ||
|         Ok(t) => {
 | ||
|             input.reset(&start);
 | ||
|             Ok(OptionalQName::None(t))
 | ||
|         },
 | ||
|         Err(e) => {
 | ||
|             input.reset(&start);
 | ||
|             Err(e.into())
 | ||
|         },
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| fn parse_attribute_selector<'i, 't, P, Impl>(
 | ||
|     parser: &P,
 | ||
|     input: &mut CssParser<'i, 't>,
 | ||
| ) -> Result<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(
 | ||
|                     AttrSelectorWithNamespace {
 | ||
|                         namespace: namespace,
 | ||
|                         local_name: local_name,
 | ||
|                         local_name_lower: local_name_lower,
 | ||
|                         operation: ParsedAttrSelectorOperation::Exists,
 | ||
|                         never_matches: false,
 | ||
|                     },
 | ||
|                 )));
 | ||
|             } else {
 | ||
|                 return Ok(Component::AttributeInNoNamespaceExists {
 | ||
|                     local_name: local_name,
 | ||
|                     local_name_lower: local_name_lower,
 | ||
|                 });
 | ||
|             }
 | ||
|         },
 | ||
| 
 | ||
|         // [foo=bar]
 | ||
|         Ok(&Token::Delim('=')) => AttrSelectorOperator::Equal,
 | ||
|         // [foo~=bar]
 | ||
|         Ok(&Token::IncludeMatch) => AttrSelectorOperator::Includes,
 | ||
|         // [foo|=bar]
 | ||
|         Ok(&Token::DashMatch) => AttrSelectorOperator::DashMatch,
 | ||
|         // [foo^=bar]
 | ||
|         Ok(&Token::PrefixMatch) => AttrSelectorOperator::Prefix,
 | ||
|         // [foo*=bar]
 | ||
|         Ok(&Token::SubstringMatch) => AttrSelectorOperator::Substring,
 | ||
|         // [foo$=bar]
 | ||
|         Ok(&Token::SuffixMatch) => AttrSelectorOperator::Suffix,
 | ||
|         Ok(t) => {
 | ||
|             return Err(location.new_custom_error(
 | ||
|                 SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t.clone()),
 | ||
|             ))
 | ||
|         },
 | ||
|     };
 | ||
| 
 | ||
|     let value = match input.expect_ident_or_string() {
 | ||
|         Ok(t) => t.clone(),
 | ||
|         Err(BasicParseError {
 | ||
|             kind: BasicParseErrorKind::UnexpectedToken(t),
 | ||
|             location,
 | ||
|         }) => return Err(location.new_custom_error(SelectorParseErrorKind::BadValueInAttr(t))),
 | ||
|         Err(e) => return Err(e.into()),
 | ||
|     };
 | ||
|     let never_matches = match operator {
 | ||
|         AttrSelectorOperator::Equal | AttrSelectorOperator::DashMatch => false,
 | ||
| 
 | ||
|         AttrSelectorOperator::Includes => value.is_empty() || value.contains(SELECTOR_WHITESPACE),
 | ||
| 
 | ||
|         AttrSelectorOperator::Prefix |
 | ||
|         AttrSelectorOperator::Substring |
 | ||
|         AttrSelectorOperator::Suffix => value.is_empty(),
 | ||
|     };
 | ||
| 
 | ||
|     let mut case_sensitivity = parse_attribute_flags(input)?;
 | ||
| 
 | ||
|     let value = value.as_ref().into();
 | ||
|     let local_name_lower;
 | ||
|     {
 | ||
|         let local_name_lower_cow = to_ascii_lowercase(&local_name);
 | ||
|         if let ParsedCaseSensitivity::CaseSensitive = case_sensitivity {
 | ||
|             if namespace.is_none() &&
 | ||
|                 include!(concat!(
 | ||
|                     env!("OUT_DIR"),
 | ||
|                     "/ascii_case_insensitive_html_attributes.rs"
 | ||
|                 )).contains(&*local_name_lower_cow)
 | ||
|             {
 | ||
|                 case_sensitivity =
 | ||
|                     ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument
 | ||
|             }
 | ||
|         }
 | ||
|         local_name_lower = local_name_lower_cow.as_ref().into();
 | ||
|     }
 | ||
|     let local_name = local_name.as_ref().into();
 | ||
|     if let Some(namespace) = namespace {
 | ||
|         Ok(Component::AttributeOther(Box::new(
 | ||
|             AttrSelectorWithNamespace {
 | ||
|                 namespace: namespace,
 | ||
|                 local_name: local_name,
 | ||
|                 local_name_lower: local_name_lower,
 | ||
|                 never_matches: never_matches,
 | ||
|                 operation: ParsedAttrSelectorOperation::WithValue {
 | ||
|                     operator: operator,
 | ||
|                     case_sensitivity: case_sensitivity,
 | ||
|                     expected_value: value,
 | ||
|                 },
 | ||
|             },
 | ||
|         )))
 | ||
|     } else {
 | ||
|         Ok(Component::AttributeInNoNamespace {
 | ||
|             local_name: local_name,
 | ||
|             local_name_lower: local_name_lower,
 | ||
|             operator: operator,
 | ||
|             value: value,
 | ||
|             case_sensitivity: case_sensitivity,
 | ||
|             never_matches: never_matches,
 | ||
|         })
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| fn parse_attribute_flags<'i, 't>(
 | ||
|     input: &mut CssParser<'i, 't>,
 | ||
| ) -> Result<ParsedCaseSensitivity, BasicParseError<'i>> {
 | ||
|     let location = input.current_source_location();
 | ||
|     match input.next() {
 | ||
|         Err(_) => {
 | ||
|             // Selectors spec says language-defined, but HTML says sensitive.
 | ||
|             Ok(ParsedCaseSensitivity::CaseSensitive)
 | ||
|         },
 | ||
|         Ok(&Token::Ident(ref value)) if value.eq_ignore_ascii_case("i") => {
 | ||
|             Ok(ParsedCaseSensitivity::AsciiCaseInsensitive)
 | ||
|         },
 | ||
|         Ok(t) => Err(location.new_basic_unexpected_token_error(t.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>,
 | ||
| ) -> Result<Component<Impl>, ParseError<'i, P::Error>>
 | ||
| where
 | ||
|     P: Parser<'i, Impl = Impl>,
 | ||
|     Impl: SelectorImpl,
 | ||
| {
 | ||
|     // We use a sequence because a type selector may be represented as two Components.
 | ||
|     let mut sequence = SmallVec::<[Component<Impl>; 2]>::new();
 | ||
| 
 | ||
|     input.skip_whitespace();
 | ||
| 
 | ||
|     // Get exactly one simple selector. The parse logic in the caller will verify
 | ||
|     // that there are no trailing tokens after we're done.
 | ||
|     let is_type_sel = match parse_type_selector(parser, input, &mut sequence) {
 | ||
|         Ok(result) => result,
 | ||
|         Err(ParseError {
 | ||
|             kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput),
 | ||
|             ..
 | ||
|         }) => return Err(input.new_custom_error(SelectorParseErrorKind::EmptyNegation)),
 | ||
|         Err(e) => return Err(e.into()),
 | ||
|     };
 | ||
|     if !is_type_sel {
 | ||
|         match parse_one_simple_selector(parser, input, /* inside_negation = */ true)? {
 | ||
|             Some(SimpleSelectorParseResult::SimpleSelector(s)) => {
 | ||
|                 sequence.push(s);
 | ||
|             },
 | ||
|             None => {
 | ||
|                 return Err(input.new_custom_error(SelectorParseErrorKind::EmptyNegation));
 | ||
|             },
 | ||
|             Some(SimpleSelectorParseResult::PseudoElement(_)) |
 | ||
|             Some(SimpleSelectorParseResult::SlottedPseudo(_)) => {
 | ||
|                 let e = SelectorParseErrorKind::NonSimpleSelectorInNegation;
 | ||
|                 return Err(input.new_custom_error(e));
 | ||
|             },
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     // Success.
 | ||
|     Ok(Component::Negation(sequence.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(None)` is an empty selector
 | ||
| ///
 | ||
| /// The booleans represent whether a pseudo-element has been parsed, and whether
 | ||
| /// ::slotted() has been parsed, respectively.
 | ||
| fn parse_compound_selector<'i, 't, P, Impl>(
 | ||
|     parser: &P,
 | ||
|     input: &mut CssParser<'i, 't>,
 | ||
|     builder: &mut SelectorBuilder<Impl>,
 | ||
| ) -> Result<Option<(bool, bool)>, ParseError<'i, P::Error>>
 | ||
| where
 | ||
|     P: Parser<'i, Impl = Impl>,
 | ||
|     Impl: SelectorImpl,
 | ||
| {
 | ||
|     input.skip_whitespace();
 | ||
| 
 | ||
|     let mut empty = true;
 | ||
|     let mut slot = false;
 | ||
|     if !parse_type_selector(parser, input, builder)? {
 | ||
|         if let Some(url) = parser.default_namespace() {
 | ||
|             // If there was no explicit type selector, but there is a
 | ||
|             // default namespace, there is an implicit "<defaultns>|*" type
 | ||
|             // selector.
 | ||
|             builder.push_simple_selector(Component::DefaultNamespace(url))
 | ||
|         }
 | ||
|     } else {
 | ||
|         empty = false;
 | ||
|     }
 | ||
| 
 | ||
|     let mut pseudo = false;
 | ||
|     loop {
 | ||
|         let parse_result =
 | ||
|             match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? {
 | ||
|                 None => break,
 | ||
|                 Some(result) => result,
 | ||
|             };
 | ||
| 
 | ||
|         match parse_result {
 | ||
|             SimpleSelectorParseResult::SimpleSelector(s) => {
 | ||
|                 builder.push_simple_selector(s);
 | ||
|                 empty = false
 | ||
|             },
 | ||
|             SimpleSelectorParseResult::PseudoElement(p) => {
 | ||
|                 // Try to parse state to its right. There are only 3 allowable
 | ||
|                 // state selectors that can go on pseudo-elements.
 | ||
|                 let mut state_selectors = SmallVec::<[Component<Impl>; 3]>::new();
 | ||
| 
 | ||
|                 loop {
 | ||
|                     let location = input.current_source_location();
 | ||
|                     match input.next_including_whitespace() {
 | ||
|                         Ok(&Token::Colon) => {},
 | ||
|                         Ok(&Token::WhiteSpace(_)) | Err(_) => break,
 | ||
|                         Ok(t) => {
 | ||
|                             let e = SelectorParseErrorKind::PseudoElementExpectedColon(t.clone());
 | ||
|                             return Err(location.new_custom_error(e));
 | ||
|                         },
 | ||
|                     }
 | ||
| 
 | ||
|                     let location = input.current_source_location();
 | ||
|                     // TODO(emilio): Functional pseudo-classes too?
 | ||
|                     // We don't need it for now.
 | ||
|                     let name = match input.next_including_whitespace()? {
 | ||
|                         &Token::Ident(ref name) => name.clone(),
 | ||
|                         t => {
 | ||
|                             return Err(location.new_custom_error(
 | ||
|                                 SelectorParseErrorKind::NoIdentForPseudo(t.clone()),
 | ||
|                             ))
 | ||
|                         },
 | ||
|                     };
 | ||
| 
 | ||
|                     let pseudo_class =
 | ||
|                         P::parse_non_ts_pseudo_class(parser, location, name.clone())?;
 | ||
|                     if !p.supports_pseudo_class(&pseudo_class) {
 | ||
|                         return Err(input.new_custom_error(
 | ||
|                             SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name),
 | ||
|                         ));
 | ||
|                     }
 | ||
|                     state_selectors.push(Component::NonTSPseudoClass(pseudo_class));
 | ||
|                 }
 | ||
| 
 | ||
|                 if !builder.is_empty() {
 | ||
|                     builder.push_combinator(Combinator::PseudoElement);
 | ||
|                 }
 | ||
| 
 | ||
|                 builder.push_simple_selector(Component::PseudoElement(p));
 | ||
|                 for state_selector in state_selectors.drain() {
 | ||
|                     builder.push_simple_selector(state_selector);
 | ||
|                 }
 | ||
| 
 | ||
|                 pseudo = true;
 | ||
|                 empty = false;
 | ||
|                 break;
 | ||
|             },
 | ||
|             SimpleSelectorParseResult::SlottedPseudo(selector) => {
 | ||
|                 empty = false;
 | ||
|                 slot = true;
 | ||
|                 if !builder.is_empty() {
 | ||
|                     builder.push_combinator(Combinator::SlotAssignment);
 | ||
|                 }
 | ||
|                 builder.push_simple_selector(Component::Slotted(selector));
 | ||
|                 // FIXME(emilio): ::slotted() should support ::before and
 | ||
|                 // ::after after it, so we shouldn't break, but we shouldn't
 | ||
|                 // push more type selectors either.
 | ||
|                 break;
 | ||
|             },
 | ||
|         }
 | ||
|     }
 | ||
|     if empty {
 | ||
|         // An empty selector is invalid.
 | ||
|         Ok(None)
 | ||
|     } else {
 | ||
|         Ok(Some((pseudo, slot)))
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| fn parse_functional_pseudo_class<'i, 't, P, Impl>(
 | ||
|     parser: &P,
 | ||
|     input: &mut CssParser<'i, 't>,
 | ||
|     name: CowRcStr<'i>,
 | ||
|     inside_negation: bool,
 | ||
| ) -> Result<Component<Impl>, ParseError<'i, P::Error>>
 | ||
| where
 | ||
|     P: Parser<'i, Impl = Impl>,
 | ||
|     Impl: SelectorImpl,
 | ||
| {
 | ||
|     match_ignore_ascii_case! { &name,
 | ||
|         "nth-child" => return Ok(parse_nth_pseudo_class(input, Component::NthChild)?),
 | ||
|         "nth-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthOfType)?),
 | ||
|         "nth-last-child" => return Ok(parse_nth_pseudo_class(input, Component::NthLastChild)?),
 | ||
|         "nth-last-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthLastOfType)?),
 | ||
|         "host" => return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input)?))),
 | ||
|         "not" => {
 | ||
|             if inside_negation {
 | ||
|                 return Err(input.new_custom_error(
 | ||
|                     SelectorParseErrorKind::UnexpectedIdent("not".into())
 | ||
|                 ));
 | ||
|             }
 | ||
|             return parse_negation(parser, input)
 | ||
|         },
 | ||
|         _ => {}
 | ||
|     }
 | ||
|     P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass)
 | ||
| }
 | ||
| 
 | ||
| fn parse_nth_pseudo_class<'i, 't, Impl, F>(
 | ||
|     input: &mut CssParser<'i, 't>,
 | ||
|     selector: F,
 | ||
| ) -> Result<Component<Impl>, BasicParseError<'i>>
 | ||
| where
 | ||
|     Impl: SelectorImpl,
 | ||
|     F: FnOnce(i32, i32) -> Component<Impl>,
 | ||
| {
 | ||
|     let (a, b) = parse_nth(input)?;
 | ||
|     Ok(selector(a, b))
 | ||
| }
 | ||
| 
 | ||
| /// Returns whether the name corresponds to a CSS2 pseudo-element that
 | ||
| /// can be specified with the single colon syntax (in addition to the
 | ||
| /// double-colon syntax, which can be used for all pseudo-elements).
 | ||
| pub fn is_css2_pseudo_element(name: &str) -> bool {
 | ||
|     // ** 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>,
 | ||
|     inside_negation: bool,
 | ||
| ) -> Result<Option<SimpleSelectorParseResult<Impl>>, ParseError<'i, P::Error>>
 | ||
| where
 | ||
|     P: Parser<'i, Impl = Impl>,
 | ||
|     Impl: SelectorImpl,
 | ||
| {
 | ||
|     let start = input.state();
 | ||
|     // FIXME: remove clone() when lifetimes are non-lexical
 | ||
|     match input.next_including_whitespace().map(|t| t.clone()) {
 | ||
|         Ok(Token::IDHash(id)) => {
 | ||
|             let id = Component::ID(id.as_ref().into());
 | ||
|             Ok(Some(SimpleSelectorParseResult::SimpleSelector(id)))
 | ||
|         },
 | ||
|         Ok(Token::Delim('.')) => {
 | ||
|             let location = input.current_source_location();
 | ||
|             match *input.next_including_whitespace()? {
 | ||
|                 Token::Ident(ref class) => {
 | ||
|                     let class = Component::Class(class.as_ref().into());
 | ||
|                     Ok(Some(SimpleSelectorParseResult::SimpleSelector(class)))
 | ||
|                 },
 | ||
|                 ref t => {
 | ||
|                     let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone());
 | ||
|                     Err(location.new_custom_error(e))
 | ||
|                 },
 | ||
|             }
 | ||
|         },
 | ||
|         Ok(Token::SquareBracketBlock) => {
 | ||
|             let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?;
 | ||
|             Ok(Some(SimpleSelectorParseResult::SimpleSelector(attr)))
 | ||
|         },
 | ||
|         Ok(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 || P::pseudo_element_allows_single_colon(&name);
 | ||
|             if is_pseudo_element {
 | ||
|                 let parse_result = if is_functional {
 | ||
|                     if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") {
 | ||
|                         let selector = input.parse_nested_block(|input| {
 | ||
|                             parse_inner_compound_selector(parser, input)
 | ||
|                         })?;
 | ||
|                         SimpleSelectorParseResult::SlottedPseudo(selector)
 | ||
|                     } else {
 | ||
|                         let selector = input.parse_nested_block(|input| {
 | ||
|                             P::parse_functional_pseudo_element(parser, name, input)
 | ||
|                         })?;
 | ||
|                         SimpleSelectorParseResult::PseudoElement(selector)
 | ||
|                     }
 | ||
|                 } else {
 | ||
|                     SimpleSelectorParseResult::PseudoElement(P::parse_pseudo_element(
 | ||
|                         parser,
 | ||
|                         location,
 | ||
|                         name,
 | ||
|                     )?)
 | ||
|                 };
 | ||
|                 Ok(Some(parse_result))
 | ||
|             } else {
 | ||
|                 let pseudo_class = if is_functional {
 | ||
|                     input.parse_nested_block(|input| {
 | ||
|                         parse_functional_pseudo_class(parser, input, name, inside_negation)
 | ||
|                     })?
 | ||
|                 } else {
 | ||
|                     parse_simple_pseudo_class(parser, location, name)?
 | ||
|                 };
 | ||
|                 Ok(Some(SimpleSelectorParseResult::SimpleSelector(
 | ||
|                     pseudo_class,
 | ||
|                 )))
 | ||
|             }
 | ||
|         },
 | ||
|         _ => {
 | ||
|             input.reset(&start);
 | ||
|             Ok(None)
 | ||
|         },
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| fn parse_simple_pseudo_class<'i, P, Impl>(
 | ||
|     parser: &P,
 | ||
|     location: SourceLocation,
 | ||
|     name: CowRcStr<'i>,
 | ||
| ) -> Result<Component<Impl>, ParseError<'i, P::Error>>
 | ||
| where
 | ||
|     P: Parser<'i, Impl = Impl>,
 | ||
|     Impl: SelectorImpl,
 | ||
| {
 | ||
|     (match_ignore_ascii_case! { &name,
 | ||
|         "first-child" => Ok(Component::FirstChild),
 | ||
|         "last-child"  => Ok(Component::LastChild),
 | ||
|         "only-child"  => Ok(Component::OnlyChild),
 | ||
|         "root" => Ok(Component::Root),
 | ||
|         "empty" => Ok(Component::Empty),
 | ||
|         "scope" => Ok(Component::Scope),
 | ||
|         "host" if P::parse_host(parser) => Ok(Component::Host(None)),
 | ||
|         "first-of-type" => Ok(Component::FirstOfType),
 | ||
|         "last-of-type" => Ok(Component::LastOfType),
 | ||
|         "only-of-type" => Ok(Component::OnlyOfType),
 | ||
|         _ => Err(())
 | ||
|     }).or_else(|()| {
 | ||
|         P::parse_non_ts_pseudo_class(parser, location, name).map(Component::NonTSPseudoClass)
 | ||
|     })
 | ||
| }
 | ||
| 
 | ||
| // NB: pub module in order to access the DummyParser
 | ||
| #[cfg(test)]
 | ||
| pub mod tests {
 | ||
|     use builder::HAS_PSEUDO_BIT;
 | ||
|     use cssparser::{serialize_identifier, Parser as CssParser, ParserInput, ToCss};
 | ||
|     use parser;
 | ||
|     use std::collections::HashMap;
 | ||
|     use std::fmt;
 | ||
|     use super::*;
 | ||
| 
 | ||
|     #[derive(Clone, Debug, Eq, PartialEq)]
 | ||
|     pub enum PseudoClass {
 | ||
|         Hover,
 | ||
|         Active,
 | ||
|         Lang(String),
 | ||
|     }
 | ||
| 
 | ||
|     #[derive(Clone, Debug, Eq, PartialEq)]
 | ||
|     pub enum PseudoElement {
 | ||
|         Before,
 | ||
|         After,
 | ||
|     }
 | ||
| 
 | ||
|     impl parser::PseudoElement for PseudoElement {
 | ||
|         type Impl = DummySelectorImpl;
 | ||
| 
 | ||
|         fn supports_pseudo_class(&self, pc: &PseudoClass) -> bool {
 | ||
|             match *pc {
 | ||
|                 PseudoClass::Hover => true,
 | ||
|                 PseudoClass::Active | PseudoClass::Lang(..) => false,
 | ||
|             }
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     impl parser::NonTSPseudoClass for PseudoClass {
 | ||
|         type Impl = DummySelectorImpl;
 | ||
| 
 | ||
|         #[inline]
 | ||
|         fn is_active_or_hover(&self) -> bool {
 | ||
|             matches!(*self, PseudoClass::Active | PseudoClass::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"),
 | ||
|             }
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     impl Visit for PseudoClass {
 | ||
|         type Impl = DummySelectorImpl;
 | ||
| 
 | ||
|         fn visit<V>(&self, _visitor: &mut V) -> bool
 | ||
|         where
 | ||
|             V: SelectorVisitor<Impl = Self::Impl>,
 | ||
|         {
 | ||
|             true
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     #[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 = ();
 | ||
|         type AttrValue = DummyAtom;
 | ||
|         type Identifier = DummyAtom;
 | ||
|         type ClassName = 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 DummyAtom(String);
 | ||
| 
 | ||
|     impl fmt::Display for DummyAtom {
 | ||
|         fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
 | ||
|             <String as fmt::Display>::fmt(&self.0, fmt)
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     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_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 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 {
 | ||
|             assert_eq!(selectors.0.len(), 1);
 | ||
|             // We can't assume that the serialized parsed selector will equal
 | ||
|             // the input; for example, if there is no default namespace, '*|foo'
 | ||
|             // should serialize to 'foo'.
 | ||
|             assert_eq!(
 | ||
|                 selectors.0[0].to_css_string(),
 | ||
|                 match expected {
 | ||
|                     Some(x) => x,
 | ||
|                     None => input,
 | ||
|                 }
 | ||
|             );
 | ||
|         }
 | ||
|         result
 | ||
|     }
 | ||
| 
 | ||
|     fn specificity(a: u32, b: u32, c: u32) -> u32 {
 | ||
|         a << 20 | b << 10 | c
 | ||
|     }
 | ||
| 
 | ||
|     #[test]
 | ||
|     fn test_empty() {
 | ||
|         let mut input = ParserInput::new(":empty");
 | ||
|         let list = SelectorList::parse(&DummyParser::default(), &mut CssParser::new(&mut input));
 | ||
|         assert!(list.is_ok());
 | ||
|     }
 | ||
| 
 | ||
|     const MATHML: &'static str = "http://www.w3.org/1998/Math/MathML";
 | ||
|     const SVG: &'static 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),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         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),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         // 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),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         // 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),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse("*"),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(vec![Component::ExplicitUniversalType], specificity(0, 0, 0)),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse("|*"),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![
 | ||
|                         Component::ExplicitNoNamespace,
 | ||
|                         Component::ExplicitUniversalType,
 | ||
|                     ],
 | ||
|                     specificity(0, 0, 0),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse_expected("*|*", Some("*")),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(vec![Component::ExplicitUniversalType], specificity(0, 0, 0)),
 | ||
|             ]))
 | ||
|         );
 | ||
|         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),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         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),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse("#bar"),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![Component::ID(DummyAtom::from("bar"))],
 | ||
|                     specificity(1, 0, 0),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         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),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         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 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),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         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),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         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 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 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),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse_ns("*", &parser),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![
 | ||
|                         Component::DefaultNamespace(MATHML.into()),
 | ||
|                         Component::ExplicitUniversalType,
 | ||
|                     ],
 | ||
|                     specificity(0, 0, 0),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse_ns("*|*", &parser),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![
 | ||
|                         Component::ExplicitAnyNamespace,
 | ||
|                         Component::ExplicitUniversalType,
 | ||
|                     ],
 | ||
|                     specificity(0, 0, 0),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         // 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![Component::Class(DummyAtom::from("cl"))].into_boxed_slice(),
 | ||
|                         ),
 | ||
|                     ],
 | ||
|                     specificity(0, 1, 0),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse_ns(":not(*)", &parser),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![
 | ||
|                         Component::DefaultNamespace(MATHML.into()),
 | ||
|                         Component::Negation(
 | ||
|                             vec![
 | ||
|                                 Component::DefaultNamespace(MATHML.into()),
 | ||
|                                 Component::ExplicitUniversalType,
 | ||
|                             ].into_boxed_slice(),
 | ||
|                         ),
 | ||
|                     ],
 | ||
|                     specificity(0, 0, 0),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse_ns(":not(e)", &parser),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![
 | ||
|                         Component::DefaultNamespace(MATHML.into()),
 | ||
|                         Component::Negation(
 | ||
|                             vec![
 | ||
|                                 Component::DefaultNamespace(MATHML.into()),
 | ||
|                                 Component::LocalName(LocalName {
 | ||
|                                     name: DummyAtom::from("e"),
 | ||
|                                     lower_name: DummyAtom::from("e"),
 | ||
|                                 }),
 | ||
|                             ].into_boxed_slice(),
 | ||
|                         ),
 | ||
|                     ],
 | ||
|                     specificity(0, 0, 1),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse("[attr|=\"foo\"]"),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![
 | ||
|                         Component::AttributeInNoNamespace {
 | ||
|                             local_name: DummyAtom::from("attr"),
 | ||
|                             local_name_lower: DummyAtom::from("attr"),
 | ||
|                             operator: AttrSelectorOperator::DashMatch,
 | ||
|                             value: DummyAtom::from("foo"),
 | ||
|                             never_matches: false,
 | ||
|                             case_sensitivity: ParsedCaseSensitivity::CaseSensitive,
 | ||
|                         },
 | ||
|                     ],
 | ||
|                     specificity(0, 1, 0),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         // https://github.com/mozilla/servo/issues/1723
 | ||
|         assert_eq!(
 | ||
|             parse("::before"),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![Component::PseudoElement(PseudoElement::Before)],
 | ||
|                     specificity(0, 0, 1) | HAS_PSEUDO_BIT,
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse("::before:hover"),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![
 | ||
|                         Component::PseudoElement(PseudoElement::Before),
 | ||
|                         Component::NonTSPseudoClass(PseudoClass::Hover),
 | ||
|                     ],
 | ||
|                     specificity(0, 1, 1) | HAS_PSEUDO_BIT,
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse("::before:hover:hover"),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![
 | ||
|                         Component::PseudoElement(PseudoElement::Before),
 | ||
|                         Component::NonTSPseudoClass(PseudoClass::Hover),
 | ||
|                         Component::NonTSPseudoClass(PseudoClass::Hover),
 | ||
|                     ],
 | ||
|                     specificity(0, 2, 1) | HAS_PSEUDO_BIT,
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert!(parse("::before:hover:active").is_err());
 | ||
|         assert!(parse("::before:hover .foo").is_err());
 | ||
|         assert!(parse("::before .foo").is_err());
 | ||
|         assert!(parse("::before ~ bar").is_err());
 | ||
|         assert!(parse("::before:active").is_err());
 | ||
| 
 | ||
|         // 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) | HAS_PSEUDO_BIT,
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         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),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         parser.default_ns = None;
 | ||
|         assert!(parse(":not(#provel.old)").is_err());
 | ||
|         assert!(parse(":not(#provel > old)").is_err());
 | ||
|         assert!(parse("table[rules]:not([rules=\"none\"]):not([rules=\"\"])").is_ok());
 | ||
|         assert_eq!(
 | ||
|             parse(":not(#provel)"),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![
 | ||
|                         Component::Negation(
 | ||
|                             vec![Component::ID(DummyAtom::from("provel"))].into_boxed_slice(),
 | ||
|                         ),
 | ||
|                     ],
 | ||
|                     specificity(1, 0, 0),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse_ns(":not(svg|circle)", &parser),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![
 | ||
|                         Component::Negation(
 | ||
|                             vec![
 | ||
|                                 Component::Namespace(DummyAtom("svg".into()), SVG.into()),
 | ||
|                                 Component::LocalName(LocalName {
 | ||
|                                     name: DummyAtom::from("circle"),
 | ||
|                                     lower_name: DummyAtom::from("circle"),
 | ||
|                                 }),
 | ||
|                             ].into_boxed_slice(),
 | ||
|                         ),
 | ||
|                     ],
 | ||
|                     specificity(0, 0, 1),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         // 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![Component::ExplicitUniversalType].into_boxed_slice(),
 | ||
|                         ),
 | ||
|                     ],
 | ||
|                     specificity(0, 0, 0),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse_ns(":not(|*)", &parser),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![
 | ||
|                         Component::Negation(
 | ||
|                             vec![
 | ||
|                                 Component::ExplicitNoNamespace,
 | ||
|                                 Component::ExplicitUniversalType,
 | ||
|                             ].into_boxed_slice(),
 | ||
|                         ),
 | ||
|                     ],
 | ||
|                     specificity(0, 0, 0),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         // *| 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![Component::ExplicitUniversalType].into_boxed_slice(),
 | ||
|                         ),
 | ||
|                     ],
 | ||
|                     specificity(0, 0, 0),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
|         assert_eq!(
 | ||
|             parse_ns(":not(svg|*)", &parser),
 | ||
|             Ok(SelectorList::from_vec(vec![
 | ||
|                 Selector::from_vec(
 | ||
|                     vec![
 | ||
|                         Component::Negation(
 | ||
|                             vec![
 | ||
|                                 Component::Namespace(DummyAtom("svg".into()), SVG.into()),
 | ||
|                                 Component::ExplicitUniversalType,
 | ||
|                             ].into_boxed_slice(),
 | ||
|                         ),
 | ||
|                     ],
 | ||
|                     specificity(0, 0, 0),
 | ||
|                 ),
 | ||
|             ]))
 | ||
|         );
 | ||
| 
 | ||
|         assert!(parse("::slotted()").is_err());
 | ||
|         assert!(parse("::slotted(div)").is_ok());
 | ||
|         assert!(parse("::slotted(div).foo").is_err());
 | ||
|         assert!(parse("::slotted(div + bar)").is_err());
 | ||
|         assert!(parse("::slotted(div) + foo").is_err());
 | ||
|         assert!(parse("div ::slotted(div)").is_ok());
 | ||
|         assert!(parse("div + slot::slotted(div)").is_ok());
 | ||
|         assert!(parse("div + slot::slotted(div.foo)").is_ok());
 | ||
|         assert!(parse("slot::slotted(div,foo)::first-line").is_err());
 | ||
|         // TODO
 | ||
|         assert!(parse("::slotted(div)::before").is_err());
 | ||
|         assert!(parse("slot::slotted(div,foo)").is_err());
 | ||
|     }
 | ||
| 
 | ||
|     #[test]
 | ||
|     fn test_pseudo_iter() {
 | ||
|         let selector = &parse("q::before").unwrap().0[0];
 | ||
|         assert!(!selector.is_universal());
 | ||
|         let mut iter = selector.iter();
 | ||
|         assert_eq!(
 | ||
|             iter.next(),
 | ||
|             Some(&Component::PseudoElement(PseudoElement::Before))
 | ||
|         );
 | ||
|         assert_eq!(iter.next(), None);
 | ||
|         let combinator = iter.next_sequence();
 | ||
|         assert_eq!(combinator, Some(Combinator::PseudoElement));
 | ||
|         assert!(matches!(iter.next(), Some(&Component::LocalName(..))));
 | ||
|         assert_eq!(iter.next(), None);
 | ||
|         assert_eq!(iter.next_sequence(), None);
 | ||
|     }
 | ||
| 
 | ||
|     #[test]
 | ||
|     fn test_universal() {
 | ||
|         let selector = &parse_ns(
 | ||
|             "*|*::before",
 | ||
|             &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")),
 | ||
|         ).unwrap()
 | ||
|             .0[0];
 | ||
|         assert!(selector.is_universal());
 | ||
|     }
 | ||
| 
 | ||
|     #[test]
 | ||
|     fn test_empty_pseudo_iter() {
 | ||
|         let selector = &parse("::before").unwrap().0[0];
 | ||
|         assert!(selector.is_universal());
 | ||
|         let mut iter = selector.iter();
 | ||
|         assert_eq!(
 | ||
|             iter.next(),
 | ||
|             Some(&Component::PseudoElement(PseudoElement::Before))
 | ||
|         );
 | ||
|         assert_eq!(iter.next(), None);
 | ||
|         assert_eq!(iter.next_sequence(), 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()));
 | ||
|     }
 | ||
| }
 | 
