diff --git a/components/style/gecko/pseudo_element.rs b/components/style/gecko/pseudo_element.rs index 98f5c9a6e63..564a288b448 100644 --- a/components/style/gecko/pseudo_element.rs +++ b/components/style/gecko/pseudo_element.rs @@ -8,7 +8,7 @@ //! `pseudo_element_definition.mako.rs`. If you touch that file, you probably //! need to update the checked-in files for Servo. -use cssparser::ToCss; +use cssparser::{ToCss, serialize_identifier}; use gecko_bindings::structs::{self, CSSPseudoElementType}; use selector_parser::{NonTSPseudoClass, PseudoElementCascadeType, SelectorImpl}; use std::fmt; @@ -114,10 +114,3 @@ impl PseudoElement { } } } - -impl ToCss for PseudoElement { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - dest.write_char(':')?; - dest.write_str(self.as_str()) - } -} diff --git a/components/style/gecko/pseudo_element_definition.mako.rs b/components/style/gecko/pseudo_element_definition.mako.rs index 1c78aa47c6b..5eb08071d39 100644 --- a/components/style/gecko/pseudo_element_definition.mako.rs +++ b/components/style/gecko/pseudo_element_definition.mako.rs @@ -7,7 +7,11 @@ pub enum PseudoElement { % for pseudo in PSEUDOS: /// ${pseudo.value} + % if pseudo.is_tree_pseudo_element(): + ${pseudo.capitalized()}(Box<[String]>), + % else: ${pseudo.capitalized()}, + % endif % endfor } @@ -23,8 +27,11 @@ pub const EAGER_PSEUDOS: [PseudoElement; EAGER_PSEUDO_COUNT] = [ % endfor ]; -<%def name="pseudo_element_variant(pseudo)"> - PseudoElement::${pseudo.capitalized()} +<% TREE_PSEUDOS = [pseudo for pseudo in PSEUDOS if pseudo.is_tree_pseudo_element()] %> +<% SIMPLE_PSEUDOS = [pseudo for pseudo in PSEUDOS if not pseudo.is_tree_pseudo_element()] %> + +<%def name="pseudo_element_variant(pseudo, tree_arg='..')">\ +PseudoElement::${pseudo.capitalized()}${"({})".format(tree_arg) if pseudo.is_tree_pseudo_element() else ""}\ impl PseudoElement { @@ -33,7 +40,7 @@ impl PseudoElement { pub fn each_simple(mut fun: F) where F: FnMut(Self), { - % for pseudo in PSEUDOS: + % for pseudo in SIMPLE_PSEUDOS: fun(${pseudo_element_variant(pseudo)}); % endfor } @@ -74,7 +81,9 @@ impl PseudoElement { match *self { % for pseudo in PSEUDOS: ${pseudo_element_variant(pseudo)} => - % if pseudo.is_anon_box(): + % if pseudo.is_tree_pseudo_element(): + 0, + % elif pseudo.is_anon_box(): structs::CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY, % else: structs::SERVO_CSS_PSEUDO_ELEMENT_FLAGS_${pseudo.original_ident}, @@ -102,7 +111,9 @@ impl PseudoElement { #[inline] pub fn from_anon_box_atom(atom: &Atom) -> Option { % for pseudo in PSEUDOS: - % if pseudo.is_anon_box(): + % if pseudo.is_tree_pseudo_element(): + // We cannot generate ${pseudo_element_variant(pseudo)} from just an atom. + % elif pseudo.is_anon_box(): if atom == &atom!("${pseudo.value}") { return Some(${pseudo_element_variant(pseudo)}); } @@ -122,7 +133,10 @@ impl PseudoElement { pub fn from_slice(s: &str, in_ua_stylesheet: bool) -> Option { use std::ascii::AsciiExt; - % for pseudo in PSEUDOS: + // We don't need to support tree pseudos because functional + // pseudo-elements needs arguments, and thus should be created + // via other methods. + % for pseudo in SIMPLE_PSEUDOS: if in_ua_stylesheet || ${pseudo_element_variant(pseudo)}.exposed_in_non_ua_sheets() { if s.eq_ignore_ascii_case("${pseudo.value[1:]}") { return Some(${pseudo_element_variant(pseudo)}); @@ -133,13 +147,47 @@ impl PseudoElement { None } - /// Returns the pseudo-element's definition as a string, with only one colon - /// before it. - pub fn as_str(&self) -> &'static str { - match *self { - % for pseudo in PSEUDOS: - PseudoElement::${pseudo.capitalized()} => "${pseudo.value}", + /// Constructs a tree pseudo-element from the given name and arguments. + /// "name" must start with "-moz-tree-". + /// + /// Returns `None` if the pseudo-element is not recognized. + #[inline] + pub fn tree_pseudo_element(name: &str, args: Box<[String]>) -> Option { + use std::ascii::AsciiExt; + debug_assert!(name.starts_with("-moz-tree-")); + let tree_part = &name[10..]; + % for pseudo in TREE_PSEUDOS: + if tree_part.eq_ignore_ascii_case("${pseudo.value[11:]}") { + return Some(${pseudo_element_variant(pseudo, "args")}); + } % endfor + None + } +} + +impl ToCss for PseudoElement { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + dest.write_char(':')?; + match *self { + % for pseudo in PSEUDOS: + ${pseudo_element_variant(pseudo)} => dest.write_str("${pseudo.value}")?, + % endfor + } + match *self { + ${" |\n ".join("PseudoElement::{}(ref args)".format(pseudo.capitalized()) + for pseudo in TREE_PSEUDOS)} => { + dest.write_char('(')?; + let mut iter = args.iter(); + if let Some(first) = iter.next() { + serialize_identifier(first, dest)?; + for item in iter { + dest.write_str(", ")?; + serialize_identifier(item, dest)?; + } + } + dest.write_char(')') + } + _ => Ok(()), } } } diff --git a/components/style/gecko/regen_atoms.py b/components/style/gecko/regen_atoms.py index 810729c2d2a..9c8545bf8db 100755 --- a/components/style/gecko/regen_atoms.py +++ b/components/style/gecko/regen_atoms.py @@ -103,6 +103,9 @@ class Atom: def is_anon_box(self): return self.type() == "nsICSSAnonBoxPseudo" + def is_tree_pseudo_element(self): + return self.value.startswith(":-moz-tree-") + def collect_atoms(objdir): atoms = [] diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 601e3fbac2a..0dc03c4e6ed 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -4,7 +4,7 @@ //! Gecko-specific bits for selector-parsing. -use cssparser::{Parser, ToCss, CompactCowStr}; +use cssparser::{BasicParseError, Parser, ToCss, Token, CompactCowStr}; use element_state::ElementState; use gecko_bindings::structs::CSSPseudoClassType; use selector_parser::{SelectorParser, PseudoElementCascadeType}; @@ -267,6 +267,11 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { type Impl = SelectorImpl; type Error = StyleParseError<'i>; + fn is_pseudo_element_allows_single_colon(name: &CompactCowStr<'i>) -> bool { + ::selectors::parser::is_css2_pseudo_element(name) || + name.starts_with("-moz-tree-") // tree pseudo-elements + } + fn parse_non_ts_pseudo_class(&self, name: CompactCowStr<'i>) -> Result> { macro_rules! pseudo_class_parse { @@ -338,6 +343,30 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { .ok_or(SelectorParseError::UnexpectedIdent(name.clone()).into()) } + fn parse_functional_pseudo_element<'t>(&self, name: CompactCowStr<'i>, + parser: &mut Parser<'i, 't>) + -> Result> { + if name.starts_with("-moz-tree-") { + // Tree pseudo-elements can have zero or more arguments, + // separated by either comma or space. + let mut args = Vec::new(); + loop { + match parser.next() { + Ok(Token::Ident(ident)) => args.push(ident.into_owned()), + Ok(Token::Comma) => {}, + Ok(t) => return Err(BasicParseError::UnexpectedToken(t).into()), + Err(BasicParseError::EndOfInput) => break, + _ => unreachable!("Parser::next() shouldn't return any other error"), + } + } + let args = args.into_boxed_slice(); + if let Some(pseudo) = PseudoElement::tree_pseudo_element(&name, args) { + return Ok(pseudo); + } + } + Err(SelectorParseError::UnexpectedIdent(name.clone()).into()) + } + fn default_namespace(&self) -> Option { self.namespaces.default.clone().as_ref().map(|&(ref ns, _)| ns.clone()) }