Bug 1367315 - stylo: Make :dir argument a case-insensitive identifier. r?Manishearth draft
authorMatt Brubeck <mbrubeck@mozilla.com>
Mon, 05 Jun 2017 11:08:30 -0700
changeset 589183 d68123bb51d35b9a895b10708db49897dda3b903
parent 589150 cad53f061da634a16ea75887558301b77f65745d
child 589184 f1f6e6b1816d885cf87aaba1c5d4d162687af75e
push id62270
push userbmo:mbrubeck@mozilla.com
push dateMon, 05 Jun 2017 19:39:34 +0000
reviewersManishearth
bugs1367315
milestone55.0a1
Bug 1367315 - stylo: Make :dir argument a case-insensitive identifier. r?Manishearth MozReview-Commit-ID: HzongidY4RY
servo/components/style/gecko/non_ts_pseudo_class_list.rs
servo/components/style/gecko/selector_parser.rs
--- a/servo/components/style/gecko/non_ts_pseudo_class_list.rs
+++ b/servo/components/style/gecko/non_ts_pseudo_class_list.rs
@@ -9,24 +9,25 @@
 
  * FIXME: Find a way to autogenerate this file.
  *
  * Expected usage is as follows:
  * ```
  * macro_rules! pseudo_class_macro{
  *     (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),)*]) => {
  *         // do stuff
  *     }
  * }
  * apply_non_ts_list!(pseudo_class_macro)
  * ```
  *
- * The string variables will be applied to pseudoclasses that are of the form
- * of a function with a string argument.
+ * The `string` and `keyword` variables will be applied to pseudoclasses that are of the form of
+ * functions with string or keyword arguments.
  *
  * Pending pseudo-classes:
  *
  *  :-moz-lwtheme, :-moz-lwtheme-brighttext, :-moz-lwtheme-darktext,
  *  :-moz-window-inactive.
  *
  *  :scope -> <style scoped>, pending discussion.
  *
@@ -106,17 +107,19 @@ macro_rules! apply_non_ts_list {
                 ("-moz-last-node", MozLastNode, lastNode, _, _),
                 ("-moz-only-whitespace", MozOnlyWhitespace, mozOnlyWhitespace, _, _),
                 ("-moz-native-anonymous", MozNativeAnonymous, mozNativeAnonymous, _, PSEUDO_CLASS_INTERNAL),
                 ("-moz-is-html", MozIsHTML, mozIsHTML, _, _),
                 ("-moz-placeholder", MozPlaceholder, mozPlaceholder, _, _),
             ],
             string: [
                 ("-moz-system-metric", MozSystemMetric, mozSystemMetric, _, PSEUDO_CLASS_INTERNAL),
-                ("-moz-locale-dir", MozLocaleDir, mozLocaleDir, _, PSEUDO_CLASS_INTERNAL),
                 ("-moz-empty-except-children-with-localname", MozEmptyExceptChildrenWithLocalname,
                  mozEmptyExceptChildrenWithLocalname, _, PSEUDO_CLASS_INTERNAL),
+                ("lang", Lang, lang, _, _),
+            ],
+            keyword: [
+                ("-moz-locale-dir", MozLocaleDir, mozLocaleDir, _, PSEUDO_CLASS_INTERNAL),
                 ("dir", Dir, dir, _, _),
-                ("lang", Lang, lang, _, _),
             ]
         }
     }
 }
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -21,59 +21,70 @@ bitflags! {
     flags NonTSPseudoClassFlag: u8 {
         // See NonTSPseudoClass::is_internal()
         const PSEUDO_CLASS_INTERNAL = 0x01,
     }
 }
 
 macro_rules! pseudo_class_name {
     (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),)*]) => {
+     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),)*]) => {
         #[doc = "Our representation of a non tree-structural pseudo-class."]
         #[derive(Clone, Debug, PartialEq, Eq)]
         pub enum NonTSPseudoClass {
             $(
                 #[doc = $css]
                 $name,
             )*
             $(
                 #[doc = $s_css]
                 $s_name(Box<[u16]>),
             )*
+            $(
+                #[doc = $k_css]
+                $k_name(Box<[u16]>),
+            )*
             /// The non-standard `:-moz-any` pseudo-class.
             ///
             /// TODO(emilio): We disallow combinators and pseudos here, so we
             /// should use SimpleSelector instead
             MozAny(Box<[ComplexSelector<SelectorImpl>]>),
         }
     }
 }
 apply_non_ts_list!(pseudo_class_name);
 
 impl ToCss for NonTSPseudoClass {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         use cssparser::CssStringWriter;
         use fmt::Write;
         macro_rules! pseudo_class_serialize {
             (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),)*]) => {
+             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 *self {
                     $(NonTSPseudoClass::$name => concat!(":", $css),)*
                     $(NonTSPseudoClass::$s_name(ref s) => {
                         write!(dest, ":{}(", $s_css)?;
                         {
                             // FIXME(emilio): Avoid the extra allocation!
                             let mut css = CssStringWriter::new(dest);
 
                             // Discount the null char in the end from the
                             // string.
                             css.write_str(&String::from_utf16(&s[..s.len() - 1]).unwrap())?;
                         }
                         return dest.write_str(")")
                     }, )*
+                    $(NonTSPseudoClass::$k_name(ref s) => {
+                        // Don't include the terminating nul.
+                        let value = String::from_utf16(&s[..s.len() - 1]).unwrap();
+                        return write!(dest, ":{}({})", $k_css, value)
+                    }, )*
                     NonTSPseudoClass::MozAny(ref selectors) => {
                         dest.write_str(":-moz-any(")?;
                         let mut iter = selectors.iter();
                         let first = iter.next().expect(":-moz-any must have at least 1 selector");
                         first.to_css(dest)?;
                         for selector in iter {
                             dest.write_str(", ")?;
                             selector.to_css(dest)?;
@@ -112,20 +123,22 @@ impl NonTSPseudoClass {
     /// user agent style sheets.
     pub fn is_internal(&self) -> bool {
         macro_rules! check_flag {
             (_) => (false);
             ($flags:expr) => ($flags.contains(PSEUDO_CLASS_INTERNAL));
         }
         macro_rules! pseudo_class_check_internal {
             (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),)*]) => {
+            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 *self {
                     $(NonTSPseudoClass::$name => check_flag!($flags),)*
                     $(NonTSPseudoClass::$s_name(..) => check_flag!($s_flags),)*
+                    $(NonTSPseudoClass::$k_name(..) => check_flag!($k_flags),)*
                     NonTSPseudoClass::MozAny(_) => false,
                 }
             }
         }
         apply_non_ts_list!(pseudo_class_check_internal)
     }
 
     /// https://drafts.csswg.org/selectors-4/#useraction-pseudos
@@ -140,20 +153,22 @@ impl NonTSPseudoClass {
     /// Get the state flag associated with a pseudo-class, if any.
     pub fn state_flag(&self) -> ElementState {
         macro_rules! flag {
             (_) => (ElementState::empty());
             ($state:ident) => (::element_state::$state);
         }
         macro_rules! pseudo_class_state {
             (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),)*]) => {
+             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 *self {
                     $(NonTSPseudoClass::$name => flag!($state),)*
                     $(NonTSPseudoClass::$s_name(..) => flag!($s_state),)*
+                    $(NonTSPseudoClass::$k_name(..) => flag!($k_state),)*
                     NonTSPseudoClass::MozAny(..) => ElementState::empty(),
                 }
             }
         }
         apply_non_ts_list!(pseudo_class_state)
     }
 
     /// Returns true if the given pseudoclass should trigger style sharing cache revalidation.
@@ -174,20 +189,22 @@ impl NonTSPseudoClass {
     pub fn to_gecko_pseudoclasstype(&self) -> Option<CSSPseudoClassType> {
         macro_rules! gecko_type {
             (_) => (None);
             ($gecko_type:ident) =>
                 (Some(::gecko_bindings::structs::CSSPseudoClassType::$gecko_type));
         }
         macro_rules! pseudo_class_geckotype {
             (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),)*]) => {
+             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 *self {
                     $(NonTSPseudoClass::$name => gecko_type!($gecko_type),)*
                     $(NonTSPseudoClass::$s_name(..) => gecko_type!($s_gecko_type),)*
+                    $(NonTSPseudoClass::$k_name(..) => gecko_type!($k_gecko_type),)*
                     NonTSPseudoClass::MozAny(_) => gecko_type!(any),
                 }
             }
         }
         apply_non_ts_list!(pseudo_class_geckotype)
     }
 }
 
@@ -210,17 +227,18 @@ impl ::selectors::SelectorImpl for Selec
 }
 
 impl<'a> ::selectors::Parser for SelectorParser<'a> {
     type Impl = SelectorImpl;
 
     fn parse_non_ts_pseudo_class(&self, name: Cow<str>) -> Result<NonTSPseudoClass, ()> {
         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),)*]) => {
+             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,)*
                     _ => return Err(())
                 }
             }
         }
         let pseudo_class = apply_non_ts_list!(pseudo_class_parse);
         if !pseudo_class.is_internal() || self.in_user_agent_stylesheet() {
@@ -231,25 +249,33 @@ impl<'a> ::selectors::Parser for Selecto
     }
 
     fn parse_non_ts_functional_pseudo_class(&self,
                                             name: Cow<str>,
                                             parser: &mut Parser)
                                             -> Result<NonTSPseudoClass, ()> {
         macro_rules! pseudo_class_string_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),)*]) => {
+             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,
                     $($s_css => {
                         let name = parser.expect_ident_or_string()?;
                         // convert to null terminated utf16 string
                         // since that's what Gecko deals with
                         let utf16: Vec<u16> = name.encode_utf16().chain(Some(0u16)).collect();
                         NonTSPseudoClass::$s_name(utf16.into_boxed_slice())
                     }, )*
+                    $($k_css => {
+                        let name = parser.expect_ident()?;
+                        // Convert to ASCII-lowercase nul-terminated UTF-16 string.
+                        let utf16: Vec<u16> = name.encode_utf16().map(utf16_to_ascii_lowercase)
+                            .chain(Some(0u16)).collect();
+                        NonTSPseudoClass::$k_name(utf16.into_boxed_slice())
+                    }, )*
                     "-moz-any" => {
                         let selectors = parser.parse_comma_separated(|input| {
                             ComplexSelector::parse(self, input)
                         })?;
                         // Selectors inside `:-moz-any` may not include combinators.
                         if selectors.iter().flat_map(|x| x.iter_raw()).any(|s| s.is_combinator()) {
                             return Err(())
                         }
@@ -310,8 +336,15 @@ impl SelectorImpl {
 
     #[inline]
     /// Returns the relevant state flag for a given non-tree-structural
     /// pseudo-class.
     pub fn pseudo_class_state_flag(pc: &NonTSPseudoClass) -> ElementState {
         pc.state_flag()
     }
 }
+
+fn utf16_to_ascii_lowercase(unit: u16) -> u16 {
+    match unit {
+        65...90 => unit + 32, // A-Z => a-z
+        _ => unit
+    }
+}