mirror of
https://github.com/servo/servo.git
synced 2025-06-09 09:03:23 +00:00
1473 lines
54 KiB
Rust
1473 lines
54 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 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<T>(cow: Cow<str>) -> T where T: $($FromStr)* {
|
||
match cow {
|
||
Cow::Borrowed(s) => T::from(s),
|
||
Cow::Owned(s) => T::from(s),
|
||
}
|
||
}
|
||
|
||
fn from_ascii_lowercase<T>(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<Self::BorrowedLocalName>;
|
||
type NamespaceUrl: $($CommonBounds)* + Default + Borrow<Self::BorrowedNamespaceUrl>;
|
||
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<Self>) -> 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<Self>,
|
||
_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<String> + 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<str>)
|
||
-> Result<<Self::Impl as SelectorImpl>::NonTSPseudoClass, ()> {
|
||
Err(())
|
||
}
|
||
|
||
fn parse_non_ts_functional_pseudo_class
|
||
(&self, _name: Cow<str>, _arguments: &mut CssParser)
|
||
-> Result<<Self::Impl as SelectorImpl>::NonTSPseudoClass, ()>
|
||
{
|
||
Err(())
|
||
}
|
||
|
||
fn parse_pseudo_element(&self, _name: Cow<str>)
|
||
-> Result<<Self::Impl as SelectorImpl>::PseudoElement, ()> {
|
||
Err(())
|
||
}
|
||
|
||
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(PartialEq, Clone, Debug)]
|
||
pub struct SelectorList<Impl: SelectorImpl>(pub Vec<Selector<Impl>>);
|
||
|
||
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<P>(parser: &P, input: &mut CssParser) -> Result<Self, ()>
|
||
where P: Parser<Impl=Impl> {
|
||
input.parse_comma_separated(|input| parse_selector(parser, input))
|
||
.map(SelectorList)
|
||
}
|
||
}
|
||
|
||
#[derive(PartialEq, Clone)]
|
||
pub struct Selector<Impl: SelectorImpl> {
|
||
pub complex_selector: Arc<ComplexSelector<Impl>>,
|
||
pub pseudo_element: Option<Impl::PseudoElement>,
|
||
pub specificity: u32,
|
||
}
|
||
|
||
fn affects_sibling<Impl: SelectorImpl>(simple_selector: &SimpleSelector<Impl>) -> 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<Impl: SelectorImpl>(simple_selector: &SimpleSelector<Impl>) -> 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<Impl: SelectorImpl> Selector<Impl> {
|
||
/// 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<Impl: SelectorImpl> ComplexSelector<Impl> {
|
||
/// 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<Impl: SelectorImpl> {
|
||
pub compound_selector: Vec<SimpleSelector<Impl>>,
|
||
pub next: Option<(Arc<ComplexSelector<Impl>>, 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<Impl: SelectorImpl> {
|
||
ID(Impl::Identifier),
|
||
Class(Impl::ClassName),
|
||
LocalName(LocalName<Impl>),
|
||
Namespace(Namespace<Impl>),
|
||
|
||
// Attribute selectors
|
||
AttrExists(AttrSelector<Impl>), // [foo]
|
||
AttrEqual(AttrSelector<Impl>, Impl::AttrValue, CaseSensitivity), // [foo=bar]
|
||
AttrIncludes(AttrSelector<Impl>, Impl::AttrValue), // [foo~=bar]
|
||
AttrDashMatch(AttrSelector<Impl>, Impl::AttrValue), // [foo|=bar]
|
||
AttrPrefixMatch(AttrSelector<Impl>, Impl::AttrValue), // [foo^=bar]
|
||
AttrSubstringMatch(AttrSelector<Impl>, Impl::AttrValue), // [foo*=bar]
|
||
AttrSuffixMatch(AttrSelector<Impl>, Impl::AttrValue), // [foo$=bar]
|
||
|
||
AttrIncludesNeverMatch(AttrSelector<Impl>, Impl::AttrValue), // empty value or with whitespace
|
||
AttrPrefixNeverMatch(AttrSelector<Impl>, Impl::AttrValue), // empty value
|
||
AttrSubstringNeverMatch(AttrSelector<Impl>, Impl::AttrValue), // empty value
|
||
AttrSuffixNeverMatch(AttrSelector<Impl>, Impl::AttrValue), // empty value
|
||
|
||
// Pseudo-classes
|
||
Negation(Vec<Arc<ComplexSelector<Impl>>>),
|
||
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<Impl: SelectorImpl> {
|
||
pub name: Impl::LocalName,
|
||
pub lower_name: Impl::LocalName,
|
||
}
|
||
|
||
#[derive(Eq, PartialEq, Clone, Hash)]
|
||
pub struct AttrSelector<Impl: SelectorImpl> {
|
||
pub name: Impl::LocalName,
|
||
pub lower_name: Impl::LocalName,
|
||
pub namespace: NamespaceConstraint<Impl>,
|
||
}
|
||
|
||
#[derive(Eq, PartialEq, Clone, Hash, Debug)]
|
||
pub enum NamespaceConstraint<Impl: SelectorImpl> {
|
||
Any,
|
||
Specific(Namespace<Impl>),
|
||
}
|
||
|
||
/// FIXME(SimonSapin): should Hash only hash the URL? What is it used for?
|
||
#[derive(Eq, PartialEq, Clone, Hash)]
|
||
pub struct Namespace<Impl: SelectorImpl> {
|
||
pub prefix: Option<Impl::NamespacePrefix>,
|
||
pub url: Impl::NamespaceUrl,
|
||
}
|
||
|
||
impl<Impl: SelectorImpl> Default for Namespace<Impl> {
|
||
fn default() -> Self {
|
||
Namespace {
|
||
prefix: None,
|
||
url: Impl::NamespaceUrl::default(), // empty string
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
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 ComplexSelector<Impl> {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
|
||
}
|
||
impl<Impl: SelectorImpl> Debug for SimpleSelector<Impl> {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
|
||
}
|
||
impl<Impl: SelectorImpl> Debug for AttrSelector<Impl> {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
|
||
}
|
||
impl<Impl: SelectorImpl> Debug for Namespace<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 {
|
||
self.complex_selector.to_css(dest)?;
|
||
if let Some(ref pseudo) = self.pseudo_element {
|
||
pseudo.to_css(dest)?;
|
||
}
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
impl<Impl: SelectorImpl> ToCss for ComplexSelector<Impl> {
|
||
fn to_css<W>(&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<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(" ~ "),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl<Impl: SelectorImpl> ToCss for SimpleSelector<Impl> {
|
||
fn to_css<W>(&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<Impl: SelectorImpl> ToCss for AttrSelector<Impl> {
|
||
fn to_css<W>(&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<Impl: SelectorImpl> ToCss for Namespace<Impl> {
|
||
fn to_css<W>(&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<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)
|
||
}
|
||
}
|
||
|
||
fn attr_selector_to_css<Impl, W>(attr: &AttrSelector<Impl>,
|
||
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<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)
|
||
}
|
||
|
||
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<u32> 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<Specificity> 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<Impl>(complex_selector: &ComplexSelector<Impl>,
|
||
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<Impl>(mut selector: &ComplexSelector<Impl>)
|
||
-> Specificity
|
||
where Impl: SelectorImpl {
|
||
fn compound_selector_specificity<Impl>(compound_selector: &[SimpleSelector<Impl>],
|
||
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<P, Impl>(parser: &P, input: &mut CssParser) -> Result<Selector<Impl>, ()>
|
||
where P: Parser<Impl=Impl>, 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<P, Impl>(
|
||
parser: &P,
|
||
input: &mut CssParser)
|
||
-> Result<(ComplexSelector<Impl>, Option<Impl::PseudoElement>), ()>
|
||
where P: Parser<Impl=Impl>, 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<P, Impl>(
|
||
parser: &P,
|
||
input: &mut CssParser)
|
||
-> Result<ComplexSelector<Impl>, ()>
|
||
where P: Parser<Impl=Impl>, 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<P, Impl>(parser: &P, input: &mut CssParser)
|
||
-> Result<Option<Vec<SimpleSelector<Impl>>>, ()>
|
||
where P: Parser<Impl=Impl>, 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<Impl: SelectorImpl> {
|
||
SimpleSelector(SimpleSelector<Impl>),
|
||
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<(NamespaceConstraint<Impl>, Option<Cow<'i, str>>)>, ()>
|
||
where P: Parser<Impl=Impl>, 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<P, Impl>(parser: &P, input: &mut CssParser)
|
||
-> Result<SimpleSelector<Impl>, ()>
|
||
where P: Parser<Impl=Impl>, 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<CaseSensitivity, ()> {
|
||
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 "<defaultns>|*" type selector.)
|
||
fn parse_negation<P, Impl>(parser: &P,
|
||
input: &mut CssParser)
|
||
-> Result<SimpleSelector<Impl>, ()>
|
||
where P: Parser<Impl=Impl>, 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<P, Impl>(
|
||
parser: &P,
|
||
input: &mut CssParser)
|
||
-> Result<(Vec<SimpleSelector<Impl>>, Option<Impl::PseudoElement>), ()>
|
||
where P: Parser<Impl=Impl>, 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 "<defaultns>|*" 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<P, Impl>(parser: &P,
|
||
input: &mut CssParser,
|
||
name: Cow<str>,
|
||
inside_negation: bool)
|
||
-> Result<SimpleSelector<Impl>, ()>
|
||
where P: Parser<Impl=Impl>, 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<Impl, F>(input: &mut CssParser, selector: F)
|
||
-> Result<SimpleSelector<Impl>, ()>
|
||
where Impl: SelectorImpl, F: FnOnce(i32, i32) -> SimpleSelector<Impl> {
|
||
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<P, Impl>(parser: &P,
|
||
input: &mut CssParser,
|
||
inside_negation: bool)
|
||
-> Result<Option<SimpleSelectorParseResult<Impl>>, ()>
|
||
where P: Parser<Impl=Impl>, 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<P, Impl>(parser: &P, name: Cow<str>) -> Result<SimpleSelector<Impl>, ()>
|
||
where P: Parser<Impl=Impl>, 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<W>(&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<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"),
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(PartialEq, Debug)]
|
||
pub struct DummySelectorImpl;
|
||
|
||
#[derive(Default)]
|
||
pub struct DummyParser {
|
||
default_ns: Option<String>,
|
||
ns_prefixes: HashMap<String, String>,
|
||
}
|
||
|
||
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<str>)
|
||
-> Result<PseudoClass, ()> {
|
||
match_ignore_ascii_case! { &name,
|
||
"hover" => Ok(PseudoClass::Hover),
|
||
_ => Err(())
|
||
}
|
||
}
|
||
|
||
fn parse_non_ts_functional_pseudo_class(&self, name: Cow<str>,
|
||
parser: &mut CssParser)
|
||
-> Result<PseudoClass, ()> {
|
||
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<str>)
|
||
-> Result<PseudoElement, ()> {
|
||
match_ignore_ascii_case! { &name,
|
||
"before" => Ok(PseudoElement::Before),
|
||
"after" => Ok(PseudoElement::After),
|
||
_ => Err(())
|
||
}
|
||
}
|
||
|
||
fn default_namespace(&self) -> Option<String> {
|
||
self.default_ns.clone()
|
||
}
|
||
|
||
fn namespace_for_prefix(&self, prefix: &str) -> Option<String> {
|
||
self.ns_prefixes.get(prefix).cloned()
|
||
}
|
||
}
|
||
|
||
fn parse(input: &str) -> Result<SelectorList<DummySelectorImpl>, ()> {
|
||
parse_ns(input, &DummyParser::default())
|
||
}
|
||
|
||
fn parse_ns(input: &str, parser: &DummyParser)
|
||
-> Result<SelectorList<DummySelectorImpl>, ()> {
|
||
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),
|
||
}))));
|
||
}
|
||
}
|