use crate::attributes::ExpandedName; use cssparser::{self, CowRcStr, ParseError, SourceLocation, ToCss}; use html5ever::{LocalName, Namespace}; use crate::iter::{NodeIterator, Select}; use crate::node_data_ref::NodeDataRef; use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; use selectors::context::QuirksMode; use selectors::parser::SelectorParseErrorKind; use selectors::parser::{ NonTSPseudoClass, Parser, Selector as GenericSelector, SelectorImpl, SelectorList, }; use selectors::{self, matching, OpaqueElement}; use std::fmt; use crate::tree::{ElementData, Node, NodeData, NodeRef}; /// The definition of whitespace per CSS Selectors Level 3 ยง 4. /// /// Copied from rust-selectors. static SELECTOR_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r', '\x0C']; #[derive(Debug, Clone)] pub struct KuchikiSelectors; impl SelectorImpl for KuchikiSelectors { type AttrValue = String; type Identifier = LocalName; type ClassName = LocalName; type LocalName = LocalName; type PartName = LocalName; type NamespacePrefix = LocalName; type NamespaceUrl = Namespace; type BorrowedNamespaceUrl = Namespace; type BorrowedLocalName = LocalName; type NonTSPseudoClass = PseudoClass; type PseudoElement = PseudoElement; type ExtraMatchingData = (); } struct KuchikiParser; impl<'i> Parser<'i> for KuchikiParser { type Impl = KuchikiSelectors; type Error = SelectorParseErrorKind<'i>; fn parse_non_ts_pseudo_class( &self, location: SourceLocation, name: CowRcStr<'i>, ) -> Result>> { use self::PseudoClass::*; if name.eq_ignore_ascii_case("any-link") { Ok(AnyLink) } else if name.eq_ignore_ascii_case("link") { Ok(Link) } else if name.eq_ignore_ascii_case("visited") { Ok(Visited) } else if name.eq_ignore_ascii_case("active") { Ok(Active) } else if name.eq_ignore_ascii_case("focus") { Ok(Focus) } else if name.eq_ignore_ascii_case("hover") { Ok(Hover) } else if name.eq_ignore_ascii_case("enabled") { Ok(Enabled) } else if name.eq_ignore_ascii_case("disabled") { Ok(Disabled) } else if name.eq_ignore_ascii_case("checked") { Ok(Checked) } else if name.eq_ignore_ascii_case("indeterminate") { Ok(Indeterminate) } else { Err( location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( name, )), ) } } } #[derive(PartialEq, Eq, Clone, Debug, Hash)] pub enum PseudoClass { AnyLink, Link, Visited, Active, Focus, Hover, Enabled, Disabled, Checked, Indeterminate, } impl NonTSPseudoClass for PseudoClass { type Impl = KuchikiSelectors; fn is_active_or_hover(&self) -> bool { matches!(*self, PseudoClass::Active | PseudoClass::Hover) } fn is_user_action_state(&self) -> bool { matches!(*self, PseudoClass::Active | PseudoClass::Hover | PseudoClass::Focus) } fn has_zero_specificity(&self) -> bool { false } } impl ToCss for PseudoClass { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, { dest.write_str(match *self { PseudoClass::AnyLink => ":any-link", PseudoClass::Link => ":link", PseudoClass::Visited => ":visited", PseudoClass::Active => ":active", PseudoClass::Focus => ":focus", PseudoClass::Hover => ":hover", PseudoClass::Enabled => ":enabled", PseudoClass::Disabled => ":disabled", PseudoClass::Checked => ":checked", PseudoClass::Indeterminate => ":indeterminate", }) } } #[derive(PartialEq, Eq, Clone, Debug, Hash)] pub enum PseudoElement {} impl ToCss for PseudoElement { fn to_css(&self, _dest: &mut W) -> fmt::Result where W: fmt::Write, { match *self {} } } impl selectors::parser::PseudoElement for PseudoElement { type Impl = KuchikiSelectors; } impl selectors::Element for NodeDataRef { type Impl = KuchikiSelectors; #[inline] fn opaque(&self) -> OpaqueElement { let node: &Node = self.as_node(); OpaqueElement::new(node) } #[inline] fn is_html_slot_element(&self) -> bool { false } #[inline] fn parent_node_is_shadow_root(&self) -> bool { false } #[inline] fn containing_shadow_host(&self) -> Option { None } #[inline] fn parent_element(&self) -> Option { self.as_node().parent().and_then(NodeRef::into_element_ref) } #[inline] fn prev_sibling_element(&self) -> Option { self.as_node().preceding_siblings().elements().next() } #[inline] fn next_sibling_element(&self) -> Option { self.as_node().following_siblings().elements().next() } #[inline] fn is_empty(&self) -> bool { self.as_node().children().all(|child| match *child.data() { NodeData::Element(_) => false, NodeData::Text(ref text) => text.borrow().is_empty(), _ => true, }) } #[inline] fn is_root(&self) -> bool { match self.as_node().parent() { None => false, Some(parent) => matches!(*parent.data(), NodeData::Document(_)), } } #[inline] fn is_html_element_in_html_document(&self) -> bool { // FIXME: Have a notion of HTML document v.s. XML document? self.name.ns == ns!(html) } #[inline] fn has_local_name(&self, name: &LocalName) -> bool { self.name.local == *name } #[inline] fn has_namespace(&self, namespace: &Namespace) -> bool { self.name.ns == *namespace } #[inline] fn is_part(&self, _name: &LocalName) -> bool { false } #[inline] fn exported_part(&self, _: &LocalName) -> Option { None } #[inline] fn imported_part(&self, _: &LocalName) -> Option { None } #[inline] fn is_pseudo_element(&self) -> bool { false } #[inline] fn is_same_type(&self, other: &Self) -> bool { self.name == other.name } #[inline] fn is_link(&self) -> bool { self.name.ns == ns!(html) && matches!( self.name.local, local_name!("a") | local_name!("area") | local_name!("link") ) && self .attributes .borrow() .map .contains_key(&ExpandedName::new(ns!(), local_name!("href"))) } #[inline] fn has_id(&self, id: &LocalName, case_sensitivity: CaseSensitivity) -> bool { self.attributes .borrow() .get(local_name!("id")) .map_or(false, |id_attr| { case_sensitivity.eq(id.as_bytes(), id_attr.as_bytes()) }) } #[inline] fn has_class(&self, name: &LocalName, case_sensitivity: CaseSensitivity) -> bool { let name = name.as_bytes(); !name.is_empty() && if let Some(class_attr) = self.attributes.borrow().get(local_name!("class")) { class_attr .split(SELECTOR_WHITESPACE) .any(|class| case_sensitivity.eq(class.as_bytes(), name)) } else { false } } #[inline] fn attr_matches( &self, ns: &NamespaceConstraint<&Namespace>, local_name: &LocalName, operation: &AttrSelectorOperation<&String>, ) -> bool { let attrs = self.attributes.borrow(); match *ns { NamespaceConstraint::Any => attrs .map .iter() .any(|(name, attr)| name.local == *local_name && operation.eval_str(&attr.value)), NamespaceConstraint::Specific(ns_url) => attrs .map .get(&ExpandedName::new(ns_url, local_name.clone())) .map_or(false, |attr| operation.eval_str(&attr.value)), } } fn match_pseudo_element( &self, pseudo: &PseudoElement, _context: &mut matching::MatchingContext, ) -> bool { match *pseudo {} } fn match_non_ts_pseudo_class( &self, pseudo: &PseudoClass, _context: &mut matching::MatchingContext, _flags_setter: &mut F, ) -> bool where F: FnMut(&Self, matching::ElementSelectorFlags), { use self::PseudoClass::*; match *pseudo { Active | Focus | Hover | Enabled | Disabled | Checked | Indeterminate | Visited => { false } AnyLink | Link => { self.name.ns == ns!(html) && matches!( self.name.local, local_name!("a") | local_name!("area") | local_name!("link") ) && self.attributes.borrow().contains(local_name!("href")) } } } } /// A pre-compiled list of CSS Selectors. pub struct Selectors(pub Vec); /// A pre-compiled CSS Selector. pub struct Selector(GenericSelector); /// The specificity of a selector. /// /// Opaque, but ordered. /// /// Determines precedence in the cascading algorithm. /// When equal, a rule later in source order takes precedence. #[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct Specificity(u32); impl Selectors { /// Compile a list of selectors. This may fail on syntax errors or unsupported selectors. #[inline] pub fn compile(s: &str) -> Result { let mut input = cssparser::ParserInput::new(s); match SelectorList::parse(&KuchikiParser, &mut cssparser::Parser::new(&mut input)) { Ok(list) => Ok(Selectors(list.0.into_iter().map(Selector).collect())), Err(_) => Err(()), } } /// Returns whether the given element matches this list of selectors. #[inline] pub fn matches(&self, element: &NodeDataRef) -> bool { self.0.iter().any(|s| s.matches(element)) } /// Filter an element iterator, yielding those matching this list of selectors. #[inline] pub fn filter(&self, iter: I) -> Select where I: Iterator>, { Select { iter, selectors: self, } } } impl Selector { /// Returns whether the given element matches this selector. #[inline] pub fn matches(&self, element: &NodeDataRef) -> bool { let mut context = matching::MatchingContext::new( matching::MatchingMode::Normal, None, None, QuirksMode::NoQuirks, ); matching::matches_selector(&self.0, 0, None, element, &mut context, &mut |_, _| {}) } /// Return the specificity of this selector. pub fn specificity(&self) -> Specificity { Specificity(self.0.specificity()) } } impl ::std::str::FromStr for Selectors { type Err = (); #[inline] fn from_str(s: &str) -> Result { Selectors::compile(s) } } impl fmt::Display for Selector { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.to_css(f) } } impl fmt::Display for Selectors { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut iter = self.0.iter(); let first = iter .next() .expect("Empty Selectors, should contain at least one selector"); first.0.to_css(f)?; for selector in iter { f.write_str(", ")?; selector.0.to_css(f)?; } Ok(()) } } impl fmt::Debug for Selector { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) } } impl fmt::Debug for Selectors { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) } }