/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use attr::{AttrIdentifier, AttrValue}; use cssparser::ToCss; use element_state::ElementState; use restyle_hints::ElementSnapshot; use selector_impl::{ElementExt, PseudoElementCascadeType, TheSelectorImpl}; use selector_impl::{attr_equals_selector_is_shareable, attr_exists_selector_is_shareable}; use selectors::{Element, MatchAttrGeneric}; use selectors::parser::{AttrSelector, ParserContext, SelectorImpl}; use std::fmt; use string_cache::{Atom, Namespace}; #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum PseudoElement { Before, After, Selection, DetailsSummary, DetailsContent, } impl ToCss for PseudoElement { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { use self::PseudoElement::*; dest.write_str(match *self { Before => "::before", After => "::after", Selection => "::selection", DetailsSummary => "::-servo-details-summary", DetailsContent => "::-servo-details-content", }) } } impl PseudoElement { #[inline] pub fn is_before_or_after(&self) -> bool { match *self { PseudoElement::Before | PseudoElement::After => true, _ => false, } } #[inline] pub fn cascade_type(&self) -> PseudoElementCascadeType { match *self { PseudoElement::Before | PseudoElement::After | PseudoElement::Selection => PseudoElementCascadeType::Eager, PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy, PseudoElement::DetailsContent => PseudoElementCascadeType::Precomputed, } } } #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum NonTSPseudoClass { AnyLink, Link, Visited, Active, Focus, Hover, Enabled, Disabled, Checked, Indeterminate, ServoNonZeroBorder, ReadWrite, ReadOnly, PlaceholderShown, Target, } impl ToCss for NonTSPseudoClass { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { use self::NonTSPseudoClass::*; dest.write_str(match *self { AnyLink => ":any-link", Link => ":link", Visited => ":visited", Active => ":active", Focus => ":focus", Hover => ":hover", Enabled => ":enabled", Disabled => ":disabled", Checked => ":checked", Indeterminate => ":indeterminate", ReadWrite => ":read-write", ReadOnly => ":read-only", PlaceholderShown => ":placeholder-shown", Target => ":target", ServoNonZeroBorder => ":-servo-nonzero-border", }) } } impl NonTSPseudoClass { pub fn state_flag(&self) -> ElementState { use element_state::*; use self::NonTSPseudoClass::*; match *self { Active => IN_ACTIVE_STATE, Focus => IN_FOCUS_STATE, Hover => IN_HOVER_STATE, Enabled => IN_ENABLED_STATE, Disabled => IN_DISABLED_STATE, Checked => IN_CHECKED_STATE, Indeterminate => IN_INDETERMINATE_STATE, ReadOnly | ReadWrite => IN_READ_WRITE_STATE, PlaceholderShown => IN_PLACEHOLDER_SHOWN_STATE, Target => IN_TARGET_STATE, AnyLink | Link | Visited | ServoNonZeroBorder => ElementState::empty(), } } } #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct ServoSelectorImpl; impl SelectorImpl for ServoSelectorImpl { type PseudoElement = PseudoElement; type NonTSPseudoClass = NonTSPseudoClass; type AttrValue = String; type Identifier = Atom; type ClassName = Atom; type LocalName = Atom; type NamespacePrefix = Atom; type NamespaceUrl = Namespace; type BorrowedLocalName = Atom; type BorrowedNamespaceUrl = Namespace; fn attr_exists_selector_is_shareable(attr_selector: &AttrSelector) -> bool { attr_exists_selector_is_shareable(attr_selector) } fn attr_equals_selector_is_shareable(attr_selector: &AttrSelector, value: &Self::AttrValue) -> bool { attr_equals_selector_is_shareable(attr_selector, value) } fn parse_non_ts_pseudo_class(context: &ParserContext, name: &str) -> Result { use self::NonTSPseudoClass::*; let pseudo_class = match_ignore_ascii_case! { name, "any-link" => AnyLink, "link" => Link, "visited" => Visited, "active" => Active, "focus" => Focus, "hover" => Hover, "enabled" => Enabled, "disabled" => Disabled, "checked" => Checked, "indeterminate" => Indeterminate, "read-write" => ReadWrite, "read-only" => ReadOnly, "placeholder-shown" => PlaceholderShown, "target" => Target, "-servo-nonzero-border" => { if !context.in_user_agent_stylesheet { return Err(()); } ServoNonZeroBorder }, _ => return Err(()) }; Ok(pseudo_class) } fn parse_pseudo_element(context: &ParserContext, name: &str) -> Result { use self::PseudoElement::*; let pseudo_element = match_ignore_ascii_case! { name, "before" => Before, "after" => After, "selection" => Selection, "-servo-details-summary" => { if !context.in_user_agent_stylesheet { return Err(()) } DetailsSummary }, "-servo-details-content" => { if !context.in_user_agent_stylesheet { return Err(()) } DetailsContent }, _ => return Err(()) }; Ok(pseudo_element) } } impl ServoSelectorImpl { #[inline] pub fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType { pseudo.cascade_type() } #[inline] pub fn each_pseudo_element(mut fun: F) where F: FnMut(PseudoElement) { fun(PseudoElement::Before); fun(PseudoElement::After); fun(PseudoElement::DetailsContent); fun(PseudoElement::DetailsSummary); fun(PseudoElement::Selection); } #[inline] pub fn pseudo_class_state_flag(pc: &NonTSPseudoClass) -> ElementState { pc.state_flag() } #[inline] pub fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool { pseudo.is_before_or_after() } } /// Servo's version of an element snapshot. #[derive(Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct ServoElementSnapshot { pub state: Option, pub attrs: Option>, pub is_html_element_in_html_document: bool, } impl ServoElementSnapshot { pub fn new(is_html_element_in_html_document: bool) -> Self { ServoElementSnapshot { state: None, attrs: None, is_html_element_in_html_document: is_html_element_in_html_document, } } fn get_attr(&self, namespace: &Namespace, name: &Atom) -> Option<&AttrValue> { self.attrs.as_ref().unwrap().iter() .find(|&&(ref ident, _)| ident.local_name == *name && ident.namespace == *namespace) .map(|&(_, ref v)| v) } fn get_attr_ignore_ns(&self, name: &Atom) -> Option<&AttrValue> { self.attrs.as_ref().unwrap().iter() .find(|&&(ref ident, _)| ident.local_name == *name) .map(|&(_, ref v)| v) } } impl ElementSnapshot for ServoElementSnapshot { fn state(&self) -> Option { self.state.clone() } fn has_attrs(&self) -> bool { self.attrs.is_some() } fn id_attr(&self) -> Option { self.get_attr(&ns!(), &atom!("id")).map(|v| v.as_atom().clone()) } fn has_class(&self, name: &Atom) -> bool { self.get_attr(&ns!(), &atom!("class")) .map_or(false, |v| v.as_tokens().iter().any(|atom| atom == name)) } fn each_class(&self, mut callback: F) where F: FnMut(&Atom) { if let Some(v) = self.get_attr(&ns!(), &atom!("class")) { for class in v.as_tokens() { callback(class); } } } } impl MatchAttrGeneric for ServoElementSnapshot { type Impl = ServoSelectorImpl; fn match_attr(&self, attr: &AttrSelector, test: F) -> bool where F: Fn(&str) -> bool { use selectors::parser::NamespaceConstraint; let html = self.is_html_element_in_html_document; let local_name = if html { &attr.lower_name } else { &attr.name }; match attr.namespace { NamespaceConstraint::Specific(ref ns) => self.get_attr(&ns.url, local_name), NamespaceConstraint::Any => self.get_attr_ignore_ns(local_name), }.map_or(false, |v| test(v)) } } impl> ElementExt for E { type Snapshot = ServoElementSnapshot; fn is_link(&self) -> bool { self.match_non_ts_pseudo_class(NonTSPseudoClass::AnyLink) } }