/* 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 cssparser::{Token, Parser as CssParser, parse_nth, ToCss, serialize_identifier, CssStringWriter}; use std::ascii::AsciiExt; use std::borrow::{Borrow, Cow}; use std::cmp; use std::fmt::{self, Display, Debug, Write}; use std::hash::Hash; use std::ops::Add; use std::sync::Arc; use tree::SELECTOR_WHITESPACE; macro_rules! with_all_bounds { ( [ $( $InSelector: tt )* ] [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ] ) => { fn from_cow_str(cow: Cow) -> T where T: $($FromStr)* { match cow { Cow::Borrowed(s) => T::from(s), Cow::Owned(s) => T::from(s), } } fn from_ascii_lowercase(s: &str) -> T where T: $($FromStr)* { 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(); T::from(string) } else { T::from(s) } } /// This trait allows to define the parser implementation in regards /// of pseudo-classes/elements pub trait SelectorImpl: Sized { type AttrValue: $($InSelector)*; type Identifier: $($InSelector)*; type ClassName: $($InSelector)*; type LocalName: $($InSelector)* + Borrow; type NamespaceUrl: $($CommonBounds)* + Default + Borrow; type NamespacePrefix: $($InSelector)* + Default; type BorrowedNamespaceUrl: ?Sized + Eq; type BorrowedLocalName: ?Sized + Eq + Hash; /// non tree-structural pseudo-classes /// (see: https://drafts.csswg.org/selectors/#structural-pseudos) type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss; /// pseudo-elements type PseudoElement: $($CommonBounds)* + Sized + ToCss; /// Declares if the following "attribute exists" selector is considered /// "common" enough to be shareable. If that's not the case, when matching /// over an element, the relation /// AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE would be set. fn attr_exists_selector_is_shareable(_attr_selector: &AttrSelector) -> bool { false } /// Declares if the following "equals" attribute selector is considered /// "common" enough to be shareable. fn attr_equals_selector_is_shareable(_attr_selector: &AttrSelector, _value: &Self::AttrValue) -> bool { false } } } } macro_rules! with_bounds { ( [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ]) => { with_all_bounds! { [$($CommonBounds)* + $($FromStr)* + Display] [$($CommonBounds)*] [$($FromStr)*] } } } with_bounds! { [Clone + Eq + Hash] [From + for<'a> From<&'a str>] } pub trait Parser { type Impl: SelectorImpl; /// This function can return an "Err" pseudo-element in order to support CSS2.1 /// pseudo-elements. fn parse_non_ts_pseudo_class(&self, _name: Cow) -> Result<::NonTSPseudoClass, ()> { Err(()) } fn parse_non_ts_functional_pseudo_class (&self, _name: Cow, _arguments: &mut CssParser) -> Result<::NonTSPseudoClass, ()> { Err(()) } fn parse_pseudo_element(&self, _name: Cow) -> Result<::PseudoElement, ()> { Err(()) } fn default_namespace(&self) -> Option<::NamespaceUrl> { None } fn namespace_for_prefix(&self, _prefix: &::NamespacePrefix) -> Option<::NamespaceUrl> { None } } #[derive(PartialEq, Clone, Debug)] pub struct SelectorList(pub Vec>); impl SelectorList { /// 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

(parser: &P, input: &mut CssParser) -> Result where P: Parser { input.parse_comma_separated(|input| parse_selector(parser, input)) .map(SelectorList) } } #[derive(PartialEq, Clone)] pub struct Selector { pub complex_selector: Arc>, pub pseudo_element: Option, pub specificity: u32, } fn affects_sibling(simple_selector: &SimpleSelector) -> bool { match *simple_selector { SimpleSelector::Negation(ref negated) => { negated.iter().any(|ref selector| selector.affects_siblings()) } SimpleSelector::FirstChild | SimpleSelector::LastChild | SimpleSelector::OnlyChild | SimpleSelector::NthChild(..) | SimpleSelector::NthLastChild(..) | SimpleSelector::NthOfType(..) | SimpleSelector::NthLastOfType(..) | SimpleSelector::FirstOfType | SimpleSelector::LastOfType | SimpleSelector::OnlyOfType => true, _ => false, } } fn matches_non_common_style_affecting_attribute(simple_selector: &SimpleSelector) -> bool { match *simple_selector { SimpleSelector::Negation(ref negated) => { negated.iter().any(|ref selector| selector.matches_non_common_style_affecting_attribute()) } SimpleSelector::AttrEqual(ref attr, ref val, _) => { !Impl::attr_equals_selector_is_shareable(attr, val) } SimpleSelector::AttrExists(ref attr) => { !Impl::attr_exists_selector_is_shareable(attr) } SimpleSelector::AttrIncludes(..) | SimpleSelector::AttrDashMatch(..) | SimpleSelector::AttrPrefixMatch(..) | SimpleSelector::AttrSuffixMatch(..) | SimpleSelector::AttrSubstringMatch(..) => true, // This deliberately includes Attr*NeverMatch // which never match regardless of element attributes. _ => false, } } impl Selector { /// Whether this selector, if matching on a set of siblings, could affect /// other sibling's style. pub fn affects_siblings(&self) -> bool { self.complex_selector.affects_siblings() } pub fn matches_non_common_style_affecting_attribute(&self) -> bool { self.complex_selector.matches_non_common_style_affecting_attribute() } } impl ComplexSelector { /// Whether this complex selector, if matching on a set of siblings, /// could affect other sibling's style. pub fn affects_siblings(&self) -> bool { match self.next { Some((_, Combinator::NextSibling)) | Some((_, Combinator::LaterSibling)) => return true, _ => {}, } match self.compound_selector.last() { Some(ref selector) => affects_sibling(selector), None => false, } } pub fn matches_non_common_style_affecting_attribute(&self) -> bool { match self.compound_selector.last() { Some(ref selector) => matches_non_common_style_affecting_attribute(selector), None => false, } } } #[derive(Clone, Eq, Hash, PartialEq)] pub struct ComplexSelector { pub compound_selector: Vec>, pub next: Option<(Arc>, Combinator)>, // c.next is left of c } #[derive(Eq, PartialEq, Clone, Copy, Debug, Hash)] pub enum Combinator { Child, // > Descendant, // space NextSibling, // + LaterSibling, // ~ } #[derive(Eq, PartialEq, Clone, Hash)] pub enum SimpleSelector { ID(Impl::Identifier), Class(Impl::ClassName), LocalName(LocalName), Namespace(Namespace), // Attribute selectors AttrExists(AttrSelector), // [foo] AttrEqual(AttrSelector, Impl::AttrValue, CaseSensitivity), // [foo=bar] AttrIncludes(AttrSelector, Impl::AttrValue), // [foo~=bar] AttrDashMatch(AttrSelector, Impl::AttrValue), // [foo|=bar] AttrPrefixMatch(AttrSelector, Impl::AttrValue), // [foo^=bar] AttrSubstringMatch(AttrSelector, Impl::AttrValue), // [foo*=bar] AttrSuffixMatch(AttrSelector, Impl::AttrValue), // [foo$=bar] AttrIncludesNeverMatch(AttrSelector, Impl::AttrValue), // empty value or with whitespace AttrPrefixNeverMatch(AttrSelector, Impl::AttrValue), // empty value AttrSubstringNeverMatch(AttrSelector, Impl::AttrValue), // empty value AttrSuffixNeverMatch(AttrSelector, Impl::AttrValue), // empty value // Pseudo-classes Negation(Vec>>), FirstChild, LastChild, OnlyChild, Root, Empty, NthChild(i32, i32), NthLastChild(i32, i32), NthOfType(i32, i32), NthLastOfType(i32, i32), FirstOfType, LastOfType, OnlyOfType, NonTSPseudoClass(Impl::NonTSPseudoClass), // ... } #[derive(Eq, PartialEq, Clone, Hash, Copy, Debug)] pub enum CaseSensitivity { CaseSensitive, // Selectors spec says language-defined, but HTML says sensitive. CaseInsensitive, } #[derive(Eq, PartialEq, Clone, Hash)] pub struct LocalName { pub name: Impl::LocalName, pub lower_name: Impl::LocalName, } #[derive(Eq, PartialEq, Clone, Hash)] pub struct AttrSelector { pub name: Impl::LocalName, pub lower_name: Impl::LocalName, pub namespace: NamespaceConstraint, } #[derive(Eq, PartialEq, Clone, Hash, Debug)] pub enum NamespaceConstraint { Any, Specific(Namespace), } /// FIXME(SimonSapin): should Hash only hash the URL? What is it used for? #[derive(Eq, PartialEq, Clone, Hash)] pub struct Namespace { pub prefix: Option, pub url: Impl::NamespaceUrl, } impl Default for Namespace { fn default() -> Self { Namespace { prefix: None, url: Impl::NamespaceUrl::default(), // empty string } } } impl Debug for Selector { 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 Debug for ComplexSelector { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) } } impl Debug for SimpleSelector { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) } } impl Debug for AttrSelector { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) } } impl Debug for Namespace { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) } } impl Debug for LocalName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) } } impl ToCss for SelectorList { fn to_css(&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 ToCss for Selector { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { self.complex_selector.to_css(dest)?; if let Some(ref pseudo) = self.pseudo_element { pseudo.to_css(dest)?; } Ok(()) } } impl ToCss for ComplexSelector { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { if let Some((ref next, ref combinator)) = self.next { next.to_css(dest)?; combinator.to_css(dest)?; } for simple in &self.compound_selector { simple.to_css(dest)?; } Ok(()) } } impl ToCss for Combinator { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { Combinator::Child => dest.write_str(" > "), Combinator::Descendant => dest.write_str(" "), Combinator::NextSibling => dest.write_str(" + "), Combinator::LaterSibling => dest.write_str(" ~ "), } } } impl ToCss for SimpleSelector { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { use self::SimpleSelector::*; match *self { 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), Namespace(ref ns) => ns.to_css(dest), // Attribute selectors AttrExists(ref a) => { dest.write_char('[')?; a.to_css(dest)?; dest.write_char(']') } AttrEqual(ref a, ref v, case) => { attr_selector_to_css(a, " = ", v, match case { CaseSensitivity::CaseSensitive => None, CaseSensitivity::CaseInsensitive => Some(" i"), }, dest) } AttrDashMatch(ref a, ref v) => attr_selector_to_css(a, " |= ", v, None, dest), AttrIncludesNeverMatch(ref a, ref v) | AttrIncludes(ref a, ref v) => attr_selector_to_css(a, " ~= ", v, None, dest), AttrPrefixNeverMatch(ref a, ref v) | AttrPrefixMatch(ref a, ref v) => attr_selector_to_css(a, " ^= ", v, None, dest), AttrSubstringNeverMatch(ref a, ref v) | AttrSubstringMatch(ref a, ref v) => attr_selector_to_css(a, " *= ", v, None, dest), AttrSuffixNeverMatch(ref a, ref v) | AttrSuffixMatch(ref a, ref v) => attr_selector_to_css(a, " $= ", v, None, dest), // Pseudo-classes Negation(ref args) => { dest.write_str(":not(")?; let mut args = args.iter(); let first = args.next().unwrap(); first.to_css(dest)?; for arg in args { dest.write_str(", ")?; arg.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"), FirstOfType => dest.write_str(":first-of-type"), LastOfType => dest.write_str(":last-of-type"), OnlyOfType => dest.write_str(":only-of-type"), NthChild(a, b) => write!(dest, ":nth-child({}n{:+})", a, b), NthLastChild(a, b) => write!(dest, ":nth-last-child({}n{:+})", a, b), NthOfType(a, b) => write!(dest, ":nth-of-type({}n{:+})", a, b), NthLastOfType(a, b) => write!(dest, ":nth-last-of-type({}n{:+})", a, b), NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest), } } } impl ToCss for AttrSelector { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { if let NamespaceConstraint::Specific(ref ns) = self.namespace { ns.to_css(dest)?; } display_to_css_identifier(&self.name, dest) } } impl ToCss for Namespace { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { if let Some(ref prefix) = self.prefix { display_to_css_identifier(prefix, dest)?; dest.write_char('|')?; } Ok(()) } } impl ToCss for LocalName { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { display_to_css_identifier(&self.name, dest) } } fn attr_selector_to_css(attr: &AttrSelector, operator: &str, value: &Impl::AttrValue, modifier: Option<&str>, dest: &mut W) -> fmt::Result where Impl: SelectorImpl, W: fmt::Write { dest.write_char('[')?; attr.to_css(dest)?; dest.write_str(operator)?; dest.write_char('"')?; write!(CssStringWriter::new(dest), "{}", value)?; dest.write_char('"')?; if let Some(m) = modifier { dest.write_str(m)?; } dest.write_char(']') } /// Serialize the output of Display as a CSS identifier fn display_to_css_identifier(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) } const MAX_10BIT: u32 = (1u32 << 10) - 1; #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] struct Specificity { id_selectors: u32, class_like_selectors: u32, element_selectors: u32, } impl Add for Specificity { type Output = Specificity; fn add(self, rhs: Specificity) -> Specificity { Specificity { id_selectors: self.id_selectors + rhs.id_selectors, class_like_selectors: self.class_like_selectors + rhs.class_like_selectors, element_selectors: self.element_selectors + rhs.element_selectors, } } } impl Default for Specificity { fn default() -> Specificity { Specificity { id_selectors: 0, class_like_selectors: 0, element_selectors: 0, } } } impl From for Specificity { fn from(value: u32) -> Specificity { assert!(value <= MAX_10BIT << 20 | MAX_10BIT << 10 | MAX_10BIT); Specificity { id_selectors: value >> 20, class_like_selectors: (value >> 10) & MAX_10BIT, element_selectors: value & MAX_10BIT, } } } impl From for u32 { fn from(specificity: Specificity) -> u32 { cmp::min(specificity.id_selectors, MAX_10BIT) << 20 | cmp::min(specificity.class_like_selectors, MAX_10BIT) << 10 | cmp::min(specificity.element_selectors, MAX_10BIT) } } fn specificity(complex_selector: &ComplexSelector, pseudo_element: Option<&Impl::PseudoElement>) -> u32 where Impl: SelectorImpl { let mut specificity = complex_selector_specificity(complex_selector); if pseudo_element.is_some() { specificity.element_selectors += 1; } specificity.into() } fn complex_selector_specificity(mut selector: &ComplexSelector) -> Specificity where Impl: SelectorImpl { fn compound_selector_specificity(compound_selector: &[SimpleSelector], specificity: &mut Specificity) where Impl: SelectorImpl { for simple_selector in compound_selector.iter() { match *simple_selector { SimpleSelector::LocalName(..) => specificity.element_selectors += 1, SimpleSelector::ID(..) => specificity.id_selectors += 1, SimpleSelector::Class(..) | SimpleSelector::AttrExists(..) | SimpleSelector::AttrEqual(..) | SimpleSelector::AttrIncludes(..) | SimpleSelector::AttrDashMatch(..) | SimpleSelector::AttrPrefixMatch(..) | SimpleSelector::AttrSubstringMatch(..) | SimpleSelector::AttrSuffixMatch(..) | SimpleSelector::AttrIncludesNeverMatch(..) | SimpleSelector::AttrPrefixNeverMatch(..) | SimpleSelector::AttrSubstringNeverMatch(..) | SimpleSelector::AttrSuffixNeverMatch(..) | SimpleSelector::FirstChild | SimpleSelector::LastChild | SimpleSelector::OnlyChild | SimpleSelector::Root | SimpleSelector::Empty | SimpleSelector::NthChild(..) | SimpleSelector::NthLastChild(..) | SimpleSelector::NthOfType(..) | SimpleSelector::NthLastOfType(..) | SimpleSelector::FirstOfType | SimpleSelector::LastOfType | SimpleSelector::OnlyOfType | SimpleSelector::NonTSPseudoClass(..) => specificity.class_like_selectors += 1, SimpleSelector::Namespace(..) => (), SimpleSelector::Negation(ref negated) => { let max = negated.iter().map(|s| complex_selector_specificity(&s)) .max().unwrap(); *specificity = *specificity + max; } } } } let mut specificity = Default::default(); compound_selector_specificity(&selector.compound_selector, &mut specificity); loop { match selector.next { None => break, Some((ref next_selector, _)) => { selector = &**next_selector; compound_selector_specificity(&selector.compound_selector, &mut specificity) } } } specificity } /// Build up a Selector. /// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ; /// /// `Err` means invalid selector. fn parse_selector(parser: &P, input: &mut CssParser) -> Result, ()> where P: Parser, Impl: SelectorImpl { let (complex, pseudo_element) = parse_complex_selector_and_pseudo_element(parser, input)?; Ok(Selector { specificity: specificity(&complex, pseudo_element.as_ref()), complex_selector: Arc::new(complex), pseudo_element: pseudo_element, }) } fn parse_complex_selector_and_pseudo_element( parser: &P, input: &mut CssParser) -> Result<(ComplexSelector, Option), ()> where P: Parser, Impl: SelectorImpl { let (first, mut pseudo_element) = parse_compound_selector(parser, input)?; let mut complex = ComplexSelector { compound_selector: first, next: None }; 'outer_loop: while pseudo_element.is_none() { let combinator; let mut any_whitespace = false; loop { let position = input.position(); match input.next_including_whitespace() { Err(()) => 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(position); if any_whitespace { combinator = Combinator::Descendant; break } else { break 'outer_loop } } } } let (compound_selector, pseudo) = parse_compound_selector(parser, input)?; complex = ComplexSelector { compound_selector: compound_selector, next: Some((Arc::new(complex), combinator)) }; pseudo_element = pseudo; } Ok((complex, pseudo_element)) } fn parse_complex_selector( parser: &P, input: &mut CssParser) -> Result, ()> where P: Parser, Impl: SelectorImpl { let (complex, pseudo_element) = parse_complex_selector_and_pseudo_element(parser, input)?; if pseudo_element.is_some() { return Err(()) } Ok(complex) } /// * `Err(())`: Invalid selector, abort /// * `Ok(None)`: Not a type selector, could be something else. `input` was not consumed. /// * `Ok(Some(vec))`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`) fn parse_type_selector(parser: &P, input: &mut CssParser) -> Result>>, ()> where P: Parser, Impl: SelectorImpl { match parse_qualified_name(parser, input, /* in_attr_selector = */ false)? { None => Ok(None), Some((namespace, local_name)) => { let mut compound_selector = vec!(); match namespace { NamespaceConstraint::Specific(ns) => { compound_selector.push(SimpleSelector::Namespace(ns)) }, NamespaceConstraint::Any => (), } match local_name { Some(name) => { compound_selector.push(SimpleSelector::LocalName(LocalName { lower_name: from_ascii_lowercase(&name), name: from_cow_str(name), })) } None => (), } Ok(Some(compound_selector)) } } } #[derive(Debug)] enum SimpleSelectorParseResult { SimpleSelector(SimpleSelector), PseudoElement(Impl::PseudoElement), } /// * `Err(())`: Invalid selector, abort /// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed. /// * `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, Option>)>, ()> where P: Parser, Impl: SelectorImpl { let default_namespace = |local_name| { let namespace = match parser.default_namespace() { Some(url) => NamespaceConstraint::Specific(Namespace { prefix: None, url: url }), None => NamespaceConstraint::Any, }; Ok(Some((namespace, local_name))) }; let explicit_namespace = |input: &mut CssParser<'i, 't>, namespace| { match input.next_including_whitespace() { Ok(Token::Delim('*')) if !in_attr_selector => { Ok(Some((namespace, None))) }, Ok(Token::Ident(local_name)) => { Ok(Some((namespace, Some(local_name)))) }, _ => Err(()), } }; let position = input.position(); match input.next_including_whitespace() { Ok(Token::Ident(value)) => { let position = input.position(); match input.next_including_whitespace() { Ok(Token::Delim('|')) => { let prefix = from_cow_str(value); let result = parser.namespace_for_prefix(&prefix); let url = result.ok_or(())?; explicit_namespace(input, NamespaceConstraint::Specific(Namespace { prefix: Some(prefix), url: url })) }, _ => { input.reset(position); if in_attr_selector { Ok(Some((NamespaceConstraint::Specific(Default::default()), Some(value)))) } else { default_namespace(Some(value)) } } } }, Ok(Token::Delim('*')) => { let position = input.position(); match input.next_including_whitespace() { Ok(Token::Delim('|')) => explicit_namespace(input, NamespaceConstraint::Any), _ => { input.reset(position); if in_attr_selector { Err(()) } else { default_namespace(None) } }, } }, Ok(Token::Delim('|')) => { explicit_namespace(input, NamespaceConstraint::Specific(Default::default())) } _ => { input.reset(position); Ok(None) } } } fn parse_attribute_selector(parser: &P, input: &mut CssParser) -> Result, ()> where P: Parser, Impl: SelectorImpl { let attr = match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? { None => return Err(()), Some((_, None)) => unreachable!(), Some((namespace, Some(local_name))) => AttrSelector { namespace: namespace, lower_name: from_ascii_lowercase(&local_name), name: from_cow_str(local_name), }, }; match input.next() { // [foo] Err(()) => Ok(SimpleSelector::AttrExists(attr)), // [foo=bar] Ok(Token::Delim('=')) => { let value = input.expect_ident_or_string()?; let flags = parse_attribute_flags(input)?; Ok(SimpleSelector::AttrEqual(attr, from_cow_str(value), flags)) } // [foo~=bar] Ok(Token::IncludeMatch) => { let value = input.expect_ident_or_string()?; if value.is_empty() || value.contains(SELECTOR_WHITESPACE) { Ok(SimpleSelector::AttrIncludesNeverMatch(attr, from_cow_str(value))) } else { Ok(SimpleSelector::AttrIncludes(attr, from_cow_str(value))) } } // [foo|=bar] Ok(Token::DashMatch) => { let value = input.expect_ident_or_string()?; Ok(SimpleSelector::AttrDashMatch(attr, from_cow_str(value))) } // [foo^=bar] Ok(Token::PrefixMatch) => { let value = input.expect_ident_or_string()?; if value.is_empty() { Ok(SimpleSelector::AttrPrefixNeverMatch(attr, from_cow_str(value))) } else { Ok(SimpleSelector::AttrPrefixMatch(attr, from_cow_str(value))) } } // [foo*=bar] Ok(Token::SubstringMatch) => { let value = input.expect_ident_or_string()?; if value.is_empty() { Ok(SimpleSelector::AttrSubstringNeverMatch(attr, from_cow_str(value))) } else { Ok(SimpleSelector::AttrSubstringMatch(attr, from_cow_str(value))) } } // [foo$=bar] Ok(Token::SuffixMatch) => { let value = input.expect_ident_or_string()?; if value.is_empty() { Ok(SimpleSelector::AttrSuffixNeverMatch(attr, from_cow_str(value))) } else { Ok(SimpleSelector::AttrSuffixMatch(attr, from_cow_str(value))) } } _ => Err(()) } } fn parse_attribute_flags(input: &mut CssParser) -> Result { match input.next() { Err(()) => Ok(CaseSensitivity::CaseSensitive), Ok(Token::Ident(ref value)) if value.eq_ignore_ascii_case("i") => { Ok(CaseSensitivity::CaseInsensitive) } _ => Err(()) } } /// Level 3: Parse **one** simple_selector. (Though we might insert a second /// implied "|*" type selector.) fn parse_negation(parser: &P, input: &mut CssParser) -> Result, ()> where P: Parser, Impl: SelectorImpl { input.parse_comma_separated(|input| parse_complex_selector(parser, input).map(Arc::new)) .map(SimpleSelector::Negation) } /// simple_selector_sequence /// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]* /// | [ HASH | class | attrib | pseudo | negation ]+ /// /// `Err(())` means invalid selector fn parse_compound_selector( parser: &P, input: &mut CssParser) -> Result<(Vec>, Option), ()> where P: Parser, Impl: SelectorImpl { // Consume any leading whitespace. loop { let position = input.position(); if !matches!(input.next_including_whitespace(), Ok(Token::WhiteSpace(_))) { input.reset(position); break } } let mut empty = true; let mut compound_selector = match parse_type_selector(parser, input)? { None => { match parser.default_namespace() { // If there was no explicit type selector, but there is a // default namespace, there is an implicit "|*" type // selector. Some(url) => vec![SimpleSelector::Namespace(Namespace { prefix: None, url: url })], None => vec![], } } Some(s) => { empty = false; s } }; let mut pseudo_element = None; loop { match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? { None => break, Some(SimpleSelectorParseResult::SimpleSelector(s)) => { compound_selector.push(s); empty = false } Some(SimpleSelectorParseResult::PseudoElement(p)) => { pseudo_element = Some(p); empty = false; break } } } if empty { // An empty selector is invalid. Err(()) } else { Ok((compound_selector, pseudo_element)) } } fn parse_functional_pseudo_class(parser: &P, input: &mut CssParser, name: Cow, inside_negation: bool) -> Result, ()> where P: Parser, Impl: SelectorImpl { match_ignore_ascii_case! { &name, "nth-child" => return parse_nth_pseudo_class(input, SimpleSelector::NthChild), "nth-of-type" => return parse_nth_pseudo_class(input, SimpleSelector::NthOfType), "nth-last-child" => return parse_nth_pseudo_class(input, SimpleSelector::NthLastChild), "nth-last-of-type" => return parse_nth_pseudo_class(input, SimpleSelector::NthLastOfType), "not" => { if inside_negation { return Err(()) } else { return parse_negation(parser, input) } }, _ => {} } P::parse_non_ts_functional_pseudo_class(parser, name, input) .map(SimpleSelector::NonTSPseudoClass) } fn parse_nth_pseudo_class(input: &mut CssParser, selector: F) -> Result, ()> where Impl: SelectorImpl, F: FnOnce(i32, i32) -> SimpleSelector { let (a, b) = parse_nth(input)?; Ok(selector(a, b)) } /// 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(parser: &P, input: &mut CssParser, inside_negation: bool) -> Result>, ()> where P: Parser, Impl: SelectorImpl { let start_position = input.position(); match input.next_including_whitespace() { Ok(Token::IDHash(id)) => { let id = SimpleSelector::ID(from_cow_str(id)); Ok(Some(SimpleSelectorParseResult::SimpleSelector(id))) } Ok(Token::Delim('.')) => { match input.next_including_whitespace() { Ok(Token::Ident(class)) => { let class = SimpleSelector::Class(from_cow_str(class)); Ok(Some(SimpleSelectorParseResult::SimpleSelector(class))) } _ => Err(()), } } Ok(Token::SquareBracketBlock) => { let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?; Ok(Some(SimpleSelectorParseResult::SimpleSelector(attr))) } Ok(Token::Colon) => { match input.next_including_whitespace() { Ok(Token::Ident(name)) => { // Supported CSS 2.1 pseudo-elements only. // ** Do not add to this list! ** if name.eq_ignore_ascii_case("before") || name.eq_ignore_ascii_case("after") || name.eq_ignore_ascii_case("first-line") || name.eq_ignore_ascii_case("first-letter") { let pseudo_element = P::parse_pseudo_element(parser, name)?; Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo_element))) } else { let pseudo_class = parse_simple_pseudo_class(parser, name)?; Ok(Some(SimpleSelectorParseResult::SimpleSelector(pseudo_class))) } } Ok(Token::Function(name)) => { let pseudo = input.parse_nested_block(|input| { parse_functional_pseudo_class(parser, input, name, inside_negation) })?; Ok(Some(SimpleSelectorParseResult::SimpleSelector(pseudo))) } Ok(Token::Colon) => { match input.next_including_whitespace() { Ok(Token::Ident(name)) => { let pseudo = P::parse_pseudo_element(parser, name)?; Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo))) } _ => Err(()) } } _ => Err(()) } } _ => { input.reset(start_position); Ok(None) } } } fn parse_simple_pseudo_class(parser: &P, name: Cow) -> Result, ()> where P: Parser, Impl: SelectorImpl { (match_ignore_ascii_case! { &name, "first-child" => Ok(SimpleSelector::FirstChild), "last-child" => Ok(SimpleSelector::LastChild), "only-child" => Ok(SimpleSelector::OnlyChild), "root" => Ok(SimpleSelector::Root), "empty" => Ok(SimpleSelector::Empty), "first-of-type" => Ok(SimpleSelector::FirstOfType), "last-of-type" => Ok(SimpleSelector::LastOfType), "only-of-type" => Ok(SimpleSelector::OnlyOfType), _ => Err(()) }).or_else(|()| { P::parse_non_ts_pseudo_class(parser, name).map(|pc| SimpleSelector::NonTSPseudoClass(pc)) }) } // NB: pub module in order to access the DummyParser #[cfg(test)] pub mod tests { use cssparser::{Parser as CssParser, ToCss, serialize_identifier}; use std::borrow::Cow; use std::collections::HashMap; use std::fmt; use std::sync::Arc; use super::*; #[derive(PartialEq, Clone, Debug, Hash, Eq)] pub enum PseudoClass { Hover, Lang(String), } #[derive(Eq, PartialEq, Clone, Debug, Hash)] pub enum PseudoElement { Before, After, } impl ToCss for PseudoClass { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { PseudoClass::Hover => dest.write_str(":hover"), PseudoClass::Lang(ref lang) => { dest.write_str(":lang(")?; serialize_identifier(lang, dest)?; dest.write_char(')') } } } } impl ToCss for PseudoElement { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { PseudoElement::Before => dest.write_str("::before"), PseudoElement::After => dest.write_str("::after"), } } } #[derive(PartialEq, Debug)] pub struct DummySelectorImpl; #[derive(Default)] pub struct DummyParser { default_ns: Option, ns_prefixes: HashMap, } impl SelectorImpl for DummySelectorImpl { type AttrValue = String; type Identifier = String; type ClassName = String; type LocalName = String; type NamespaceUrl = String; type NamespacePrefix = String; type BorrowedLocalName = str; type BorrowedNamespaceUrl = str; type NonTSPseudoClass = PseudoClass; type PseudoElement = PseudoElement; } impl Parser for DummyParser { type Impl = DummySelectorImpl; fn parse_non_ts_pseudo_class(&self, name: Cow) -> Result { match_ignore_ascii_case! { &name, "hover" => Ok(PseudoClass::Hover), _ => Err(()) } } fn parse_non_ts_functional_pseudo_class(&self, name: Cow, parser: &mut CssParser) -> Result { match_ignore_ascii_case! { &name, "lang" => Ok(PseudoClass::Lang(try!(parser.expect_ident_or_string()).into_owned())), _ => Err(()) } } fn parse_pseudo_element(&self, name: Cow) -> Result { match_ignore_ascii_case! { &name, "before" => Ok(PseudoElement::Before), "after" => Ok(PseudoElement::After), _ => Err(()) } } fn default_namespace(&self) -> Option { self.default_ns.clone() } fn namespace_for_prefix(&self, prefix: &str) -> Option { self.ns_prefixes.get(prefix).cloned() } } fn parse(input: &str) -> Result, ()> { parse_ns(input, &DummyParser::default()) } fn parse_ns(input: &str, parser: &DummyParser) -> Result, ()> { let result = SelectorList::parse(parser, &mut CssParser::new(input)); if let Ok(ref selectors) = result { assert_eq!(selectors.0.len(), 1); assert_eq!(selectors.0[0].to_css_string(), input); } result } fn specificity(a: u32, b: u32, c: u32) -> u32 { a << 20 | b << 10 | c } #[test] fn test_empty() { let list = SelectorList::parse(&DummyParser::default(), &mut CssParser::new(":empty")); 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_eq!(parse(""), Err(())) ; assert_eq!(parse(":lang(4)"), Err(())) ; assert_eq!(parse(":lang(en US)"), Err(())) ; assert_eq!(parse("EeÉ"), Ok(SelectorList(vec!(Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec!(SimpleSelector::LocalName(LocalName { name: String::from("EeÉ"), lower_name: String::from("eeÉ") })), next: None, }), pseudo_element: None, specificity: specificity(0, 0, 1), })))); assert_eq!(parse(".foo:lang(en-US)"), Ok(SelectorList(vec!(Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec![ SimpleSelector::Class(String::from("foo")), SimpleSelector::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())) ], next: None, }), pseudo_element: None, specificity: specificity(0, 2, 0), })))); assert_eq!(parse("#bar"), Ok(SelectorList(vec!(Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec!(SimpleSelector::ID(String::from("bar"))), next: None, }), pseudo_element: None, specificity: specificity(1, 0, 0), })))); assert_eq!(parse("e.foo#bar"), Ok(SelectorList(vec!(Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec!(SimpleSelector::LocalName(LocalName { name: String::from("e"), lower_name: String::from("e") }), SimpleSelector::Class(String::from("foo")), SimpleSelector::ID(String::from("bar"))), next: None, }), pseudo_element: None, specificity: specificity(1, 1, 1), })))); assert_eq!(parse("e.foo #bar"), Ok(SelectorList(vec!(Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec!(SimpleSelector::ID(String::from("bar"))), next: Some((Arc::new(ComplexSelector { compound_selector: vec!(SimpleSelector::LocalName(LocalName { name: String::from("e"), lower_name: String::from("e") }), SimpleSelector::Class(String::from("foo"))), next: None, }), Combinator::Descendant)), }), pseudo_element: None, specificity: 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(vec!(Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec!(SimpleSelector::AttrExists(AttrSelector { name: String::from("Foo"), lower_name: String::from("foo"), namespace: NamespaceConstraint::Specific(Namespace { prefix: None, url: "".into(), }), })), next: None, }), pseudo_element: None, specificity: specificity(0, 1, 0), })))); assert_eq!(parse_ns("svg|circle", &parser), Err(())); parser.ns_prefixes.insert("svg".into(), SVG.into()); assert_eq!(parse_ns("svg|circle", &parser), Ok(SelectorList(vec![Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec![ SimpleSelector::Namespace(Namespace { prefix: Some("svg".into()), url: SVG.into(), }), SimpleSelector::LocalName(LocalName { name: String::from("circle"), lower_name: String::from("circle") }) ], next: None, }), pseudo_element: None, specificity: specificity(0, 0, 1), }]))); // 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(vec!(Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec![ SimpleSelector::Namespace(Namespace { prefix: None, url: MATHML.into(), }), SimpleSelector::AttrExists(AttrSelector { name: String::from("Foo"), lower_name: String::from("foo"), namespace: NamespaceConstraint::Specific(Namespace { prefix: None, url: "".into(), }), }), ], next: None, }), pseudo_element: None, specificity: specificity(0, 1, 0), })))); // Default namespace does apply to type selectors assert_eq!(parse_ns("e", &parser), Ok(SelectorList(vec!(Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec!( SimpleSelector::Namespace(Namespace { prefix: None, url: MATHML.into(), }), SimpleSelector::LocalName(LocalName { name: String::from("e"), lower_name: String::from("e") }), ), next: None, }), pseudo_element: None, specificity: specificity(0, 0, 1), })))); assert_eq!(parse("[attr |= \"foo\"]"), Ok(SelectorList(vec![Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec![ SimpleSelector::AttrDashMatch(AttrSelector { name: String::from("attr"), lower_name: String::from("attr"), namespace: NamespaceConstraint::Specific(Namespace { prefix: None, url: "".into(), }), }, "foo".to_owned()) ], next: None, }), pseudo_element: None, specificity: specificity(0, 1, 0), }]))); // https://github.com/mozilla/servo/issues/1723 assert_eq!(parse("::before"), Ok(SelectorList(vec!(Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec!(), next: None, }), pseudo_element: Some(PseudoElement::Before), specificity: specificity(0, 0, 1), })))); // https://github.com/servo/servo/issues/15335 assert_eq!(parse(":: before"), Err(())); assert_eq!(parse("div ::after"), Ok(SelectorList(vec!(Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec!(), next: Some((Arc::new(ComplexSelector { compound_selector: vec!(SimpleSelector::LocalName(LocalName { name: String::from("div"), lower_name: String::from("div") })), next: None, }), Combinator::Descendant)), }), pseudo_element: Some(PseudoElement::After), specificity: specificity(0, 0, 2), })))); assert_eq!(parse("#d1 > .ok"), Ok(SelectorList(vec![Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec![ SimpleSelector::Class(String::from("ok")), ], next: Some((Arc::new(ComplexSelector { compound_selector: vec![ SimpleSelector::ID(String::from("d1")), ], next: None, }), Combinator::Child)), }), pseudo_element: None, specificity: (1 << 20) + (1 << 10) + (0 << 0), }]))); assert_eq!(parse(":not(.babybel, #provel.old)"), Ok(SelectorList(vec!(Selector { complex_selector: Arc::new(ComplexSelector { compound_selector: vec!(SimpleSelector::Negation( vec!( Arc::new(ComplexSelector { compound_selector: vec!(SimpleSelector::Class(String::from("babybel"))), next: None }), Arc::new(ComplexSelector { compound_selector: vec!( SimpleSelector::ID(String::from("provel")), SimpleSelector::Class(String::from("old")), ), next: None }), ) )), next: None, }), pseudo_element: None, specificity: specificity(1, 1, 0), })))); } }