diff options
Diffstat (limited to 'components/style/gecko/selector_parser.rs')
-rw-r--r-- | components/style/gecko/selector_parser.rs | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs new file mode 100644 index 00000000000..32125f0fa38 --- /dev/null +++ b/components/style/gecko/selector_parser.rs @@ -0,0 +1,254 @@ +/* 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::ToCss; +use element_state::ElementState; +use selector_parser::{attr_equals_selector_is_shareable, attr_exists_selector_is_shareable}; +use selector_parser::PseudoElementCascadeType; +use selectors::parser::{AttrSelector, ParserContext, SelectorImpl}; +use std::fmt; +use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GeckoSelectorImpl; + +/// NOTE: The boolean field represents whether this element is an anonymous box. +/// +/// This is just for convenience, instead of recomputing it. Also, note that +/// Atom is always a static atom, so if space is a concern, we can use the +/// raw pointer and use the lower bit to represent it without space overhead. +/// +/// FIXME(emilio): we know all these atoms are static. Patches are starting to +/// pile up, but a further potential optimisation is generating bindings without +/// `-no-gen-bitfield-methods` (that was removed to compile on stable, but it no +/// longer depends on it), and using the raw *mut nsIAtom (properly asserting +/// we're a static atom). +/// +/// This should allow us to avoid random FFI overhead when cloning/dropping +/// pseudos. +/// +/// Also, we can further optimize PartialEq and hash comparing/hashing only the +/// atoms. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct PseudoElement(Atom, bool); + +impl PseudoElement { + #[inline] + pub fn as_atom(&self) -> &Atom { + &self.0 + } + + #[inline] + fn is_anon_box(&self) -> bool { + self.1 + } + + #[inline] + pub fn from_atom_unchecked(atom: Atom, is_anon_box: bool) -> Self { + if cfg!(debug_assertions) { + // Do the check on debug regardless. + match Self::from_atom(&*atom, true) { + Some(pseudo) => { + assert_eq!(pseudo.is_anon_box(), is_anon_box); + return pseudo; + } + None => panic!("Unknown pseudo: {:?}", atom), + } + } + + PseudoElement(atom, is_anon_box) + } + + #[inline] + fn from_atom(atom: &WeakAtom, _in_ua: bool) -> Option<Self> { + macro_rules! pseudo_element { + ($pseudo_str_with_colon:expr, $atom:expr, $is_anon_box:expr) => {{ + if atom == &*$atom { + return Some(PseudoElement($atom, $is_anon_box)); + } + }} + } + + include!("generated/gecko_pseudo_element_helper.rs"); + + None + } + + #[inline] + fn from_slice(s: &str, in_ua_stylesheet: bool) -> Option<Self> { + use std::ascii::AsciiExt; + macro_rules! pseudo_element { + ($pseudo_str_with_colon:expr, $atom:expr, $is_anon_box:expr) => {{ + if !$is_anon_box || in_ua_stylesheet { + if s.eq_ignore_ascii_case(&$pseudo_str_with_colon[1..]) { + return Some(PseudoElement($atom, $is_anon_box)) + } + } + }} + } + + include!("generated/gecko_pseudo_element_helper.rs"); + + None + } +} + +impl ToCss for PseudoElement { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + // FIXME: why does the atom contain one colon? Pseudo-element has two + debug_assert!(self.0.as_slice().starts_with(&[b':' as u16]) && + !self.0.as_slice().starts_with(&[b':' as u16, b':' as u16])); + try!(dest.write_char(':')); + write!(dest, "{}", self.0) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum NonTSPseudoClass { + AnyLink, + Link, + Visited, + Active, + Focus, + Hover, + Enabled, + Disabled, + Checked, + Indeterminate, + ReadWrite, + ReadOnly, +} + +impl ToCss for NonTSPseudoClass { + fn to_css<W>(&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", + }) + } +} + +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, + + AnyLink | + Link | + Visited => ElementState::empty(), + } + } +} + +impl SelectorImpl for GeckoSelectorImpl { + type AttrValue = Atom; + type Identifier = Atom; + type ClassName = Atom; + type LocalName = Atom; + type NamespacePrefix = Atom; + type NamespaceUrl = Namespace; + type BorrowedNamespaceUrl = WeakNamespace; + type BorrowedLocalName = WeakAtom; + + type PseudoElement = PseudoElement; + type NonTSPseudoClass = NonTSPseudoClass; + + fn attr_exists_selector_is_shareable(attr_selector: &AttrSelector<Self>) -> bool { + attr_exists_selector_is_shareable(attr_selector) + } + + fn attr_equals_selector_is_shareable(attr_selector: &AttrSelector<Self>, + value: &Self::AttrValue) -> bool { + attr_equals_selector_is_shareable(attr_selector, value) + } + + fn parse_non_ts_pseudo_class(_context: &ParserContext<Self>, + name: &str) -> Result<NonTSPseudoClass, ()> { + 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, + _ => return Err(()) + }; + + Ok(pseudo_class) + } + + fn parse_pseudo_element(context: &ParserContext<Self>, + name: &str) -> Result<PseudoElement, ()> { + match PseudoElement::from_slice(name, context.in_user_agent_stylesheet) { + Some(pseudo) => Ok(pseudo), + None => Err(()), + } + } +} + +impl GeckoSelectorImpl { + #[inline] + pub fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType { + if Self::pseudo_is_before_or_after(pseudo) { + return PseudoElementCascadeType::Eager + } + + if pseudo.is_anon_box() { + return PseudoElementCascadeType::Precomputed + } + + PseudoElementCascadeType::Lazy + } + + #[inline] + pub fn each_pseudo_element<F>(mut fun: F) + where F: FnMut(PseudoElement) + { + macro_rules! pseudo_element { + ($pseudo_str_with_colon:expr, $atom:expr, $is_anon_box:expr) => {{ + fun(PseudoElement($atom, $is_anon_box)); + }} + } + + include!("generated/gecko_pseudo_element_helper.rs") + } + + #[inline] + pub fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool { + *pseudo.as_atom() == atom!(":before") || + *pseudo.as_atom() == atom!(":after") + } + + #[inline] + pub fn pseudo_class_state_flag(pc: &NonTSPseudoClass) -> ElementState { + pc.state_flag() + } +} |