Implement parsing of tree pseudo-elements. r?heycam draft
authorXidorn Quan <me@upsuper.org>
Mon, 10 Jul 2017 20:11:02 +1000
changeset 606499 978ebe8ddb87a7dd4b27f7e2f6e122d14921eafd
parent 606498 31ff0cb3adf6a831ef783eac757b1bf396913c14
child 606500 9636f7484956439d579d9d0a278f22559b76dae4
push id67712
push userxquan@mozilla.com
push dateTue, 11 Jul 2017 00:23:47 +0000
reviewersheycam
milestone56.0a1
Implement parsing of tree pseudo-elements. r?heycam MozReview-Commit-ID: Ik3wD5XLQW5
servo/components/style/gecko/pseudo_element.rs
servo/components/style/gecko/pseudo_element_definition.mako.rs
servo/components/style/gecko/regen_atoms.py
servo/components/style/gecko/selector_parser.rs
--- a/servo/components/style/gecko/pseudo_element.rs
+++ b/servo/components/style/gecko/pseudo_element.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Gecko's definition of a pseudo-element.
 //!
 //! Note that a few autogenerated bits of this live in
 //! `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;
 use string_cache::Atom;
 
 include!(concat!(env!("OUT_DIR"), "/gecko/pseudo_element_definition.rs"));
 
 impl ::selectors::parser::PseudoElement for PseudoElement {
@@ -109,15 +109,8 @@ impl PseudoElement {
     /// canonical one as it is.
     pub fn canonical(&self) -> PseudoElement {
         match *self {
             PseudoElement::MozPlaceholder => PseudoElement::Placeholder,
             _ => self.clone(),
         }
     }
 }
-
-impl ToCss for PseudoElement {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        dest.write_char(':')?;
-        dest.write_str(self.as_str())
-    }
-}
--- a/servo/components/style/gecko/pseudo_element_definition.mako.rs
+++ b/servo/components/style/gecko/pseudo_element_definition.mako.rs
@@ -2,43 +2,51 @@
  * 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/. */
 
 /// Gecko's pseudo-element definition.
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
 pub enum PseudoElement {
     % for pseudo in PSEUDOS:
         /// ${pseudo.value}
-        ${pseudo.capitalized()},
+        % if pseudo.is_tree_pseudo_element():
+            ${pseudo.capitalized()}(Box<[String]>),
+        % else:
+            ${pseudo.capitalized()},
+        % endif
     % endfor
 }
 
 <% EAGER_PSEUDOS = ["Before", "After", "FirstLine", "FirstLetter"] %>
 
 /// The number of eager pseudo-elements.
 pub const EAGER_PSEUDO_COUNT: usize = ${len(EAGER_PSEUDOS)};
 
 /// The list of eager pseudos.
 pub const EAGER_PSEUDOS: [PseudoElement; EAGER_PSEUDO_COUNT] = [
     % for eager_pseudo_name in EAGER_PSEUDOS:
     PseudoElement::${eager_pseudo_name},
     % endfor
 ];
 
-<%def name="pseudo_element_variant(pseudo)">
+<% 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 ""}
 </%def>
 
 impl PseudoElement {
     /// Executes a closure with each simple (not functional)
     /// pseudo-element as an argument.
     pub fn each_simple<F>(mut fun: F)
         where F: FnMut(Self),
     {
-        % for pseudo in PSEUDOS:
+        % for pseudo in SIMPLE_PSEUDOS:
             fun(${pseudo_element_variant(pseudo)});
         % endfor
     }
 
     /// Get the pseudo-element as an atom.
     #[inline]
     pub fn atom(&self) -> Atom {
         match *self {
@@ -69,17 +77,19 @@ impl PseudoElement {
     }
 
     /// Gets the flags associated to this pseudo-element, or 0 if it's an
     /// anonymous box.
     pub fn flags(&self) -> u32 {
         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},
                 % endif
             % endfor
         }
     }
 
@@ -97,17 +107,19 @@ impl PseudoElement {
             _ => None,
         }
     }
 
     /// Construct a pseudo-element from an anonymous box `Atom`.
     #[inline]
     pub fn from_anon_box_atom(atom: &Atom) -> Option<Self> {
         % for pseudo in PSEUDOS:
-            % if pseudo.is_anon_box():
+            % if pseudo.is_tree_pseudo_element():
+                // We cannot generate tree pseudo-element from just an atom.
+            % elif pseudo.is_anon_box():
                 if atom == &atom!("${pseudo.value}") {
                     return Some(${pseudo_element_variant(pseudo)});
                 }
             % endif
         % endfor
         None
     }
 
@@ -117,29 +129,63 @@ impl PseudoElement {
     /// If we're not in a user-agent stylesheet, we will never parse anonymous
     /// box pseudo-elements.
     ///
     /// Returns `None` if the pseudo-element is not recognised.
     #[inline]
     pub fn from_slice(s: &str, in_ua_stylesheet: bool) -> Option<Self> {
         use std::ascii::AsciiExt;
 
-        % for pseudo in PSEUDOS:
+        % 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)});
                 }
             }
         % endfor
 
         None
     }
 
-    /// Returns the pseudo-element's definition as a string, with only one colon
-    /// before it.
-    pub fn as_str(&self) -> &'static str {
+    /// 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<Self> {
+        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<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        dest.write_char(':')?;
         match *self {
-        % for pseudo in PSEUDOS:
-            PseudoElement::${pseudo.capitalized()} => "${pseudo.value}",
-        % endfor
+            % for pseudo in PSEUDOS:
+                ${pseudo_element_variant(pseudo)} => dest.write_str("${pseudo.value}")?,
+            % endfor
+        }
+        match *self {
+            ${" | ".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(()),
         }
     }
 }
--- a/servo/components/style/gecko/regen_atoms.py
+++ b/servo/components/style/gecko/regen_atoms.py
@@ -98,16 +98,19 @@ class Atom:
         return self.source.TYPE
 
     def capitalized(self):
         return self.original_ident[0].upper() + self.original_ident[1:]
 
     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 = []
     for source in SOURCES:
         path = os.path.abspath(os.path.join(objdir, source.FILE))
         print("cargo:rerun-if-changed={}".format(path))
         with open(path) as f:
             for line in f.readlines():
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -1,15 +1,15 @@
 /* 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/. */
 
 //! 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};
 use selectors::parser::{Selector, SelectorMethods, SelectorParseError};
 use selectors::visitor::SelectorVisitor;
 use std::fmt;
 use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
 use style_traits::{ParseError, StyleParseError};
@@ -262,16 +262,21 @@ impl ::selectors::SelectorImpl for Selec
                                 NonTSPseudoClass::Hover)
     }
 }
 
 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<NonTSPseudoClass, ParseError<'i>> {
         macro_rules! pseudo_class_parse {
             (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
              string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*],
              keyword: [$(($k_css:expr, $k_name:ident, $k_gecko_type:tt, $k_state:tt, $k_flags:tt),)*]) => {
                 match_ignore_ascii_case! { &name,
                     $($css => NonTSPseudoClass::$name,)*
@@ -333,16 +338,40 @@ impl<'a, 'i> ::selectors::Parser<'i> for
         }
     }
 
     fn parse_pseudo_element(&self, name: CompactCowStr<'i>) -> Result<PseudoElement, ParseError<'i>> {
         PseudoElement::from_slice(&name, self.in_user_agent_stylesheet())
             .ok_or(SelectorParseError::UnexpectedIdent(name.clone()).into())
     }
 
+    fn parse_functional_pseudo_element<'t>(&self, name: CompactCowStr<'i>,
+                                           parser: &mut Parser<'i, 't>)
+                                           -> Result<PseudoElement, ParseError<'i>> {
+        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<Namespace> {
         self.namespaces.default.clone().as_ref().map(|&(ref ns, _)| ns.clone())
     }
 
     fn namespace_for_prefix(&self, prefix: &Atom) -> Option<Namespace> {
         self.namespaces.prefixes.get(prefix).map(|&(ref ns, _)| ns.clone())
     }
 }