Bug 1361985 - Implement grid-template-areas draft
authorAnthony Ramine <n.oxyde@gmail.com>
Thu, 04 May 2017 12:25:05 +0200
changeset 572590 b6da8390dbe3360cdecac9c5738a01c1c00a3081
parent 572589 13182219ac507d584104fcfbb3509c9781dbdb1b
child 572591 07c1800b244e6fbeed73b330572ae03663b15c8d
push id57118
push userbmo:nox@mozilla.com
push dateThu, 04 May 2017 10:30:03 +0000
bugs1361985
milestone55.0a1
Bug 1361985 - Implement grid-template-areas MozReview-Commit-ID: LiPSRR3os1C
servo/components/style/build_gecko.rs
servo/components/style/gecko_bindings/sugar/refptr.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhand/position.mako.rs
--- a/servo/components/style/build_gecko.rs
+++ b/servo/components/style/build_gecko.rs
@@ -618,16 +618,17 @@ mod bindings {
             .hide_type("nsACString_internal")
             .hide_type("nsAString_internal")
             .raw_line("pub use nsstring::{nsACString, nsAString, nsString};")
             .raw_line("type nsACString_internal = nsACString;")
             .raw_line("type nsAString_internal = nsAString;")
             .whitelisted_function("Servo_.*")
             .whitelisted_function("Gecko_.*");
         let structs_types = [
+            "mozilla::css::GridTemplateAreasValue",
             "mozilla::css::URLValue",
             "mozilla::Side",
             "RawGeckoAnimationPropertySegment",
             "RawGeckoComputedTiming",
             "RawGeckoDocument",
             "RawGeckoElement",
             "RawGeckoKeyframeList",
             "RawGeckoComputedKeyframeValuesList",
--- a/servo/components/style/gecko_bindings/sugar/refptr.rs
+++ b/servo/components/style/gecko_bindings/sugar/refptr.rs
@@ -272,8 +272,11 @@ impl_threadsafe_refcount!(::gecko_bindin
                           Gecko_AddRefQuoteValuesArbitraryThread,
                           Gecko_ReleaseQuoteValuesArbitraryThread);
 impl_threadsafe_refcount!(::gecko_bindings::structs::nsCSSValueSharedList,
                           Gecko_AddRefCSSValueSharedListArbitraryThread,
                           Gecko_ReleaseCSSValueSharedListArbitraryThread);
 impl_threadsafe_refcount!(::gecko_bindings::structs::mozilla::css::URLValue,
                           Gecko_AddRefCSSURLValueArbitraryThread,
                           Gecko_ReleaseCSSURLValueArbitraryThread);
+impl_threadsafe_refcount!(::gecko_bindings::structs::mozilla::css::GridTemplateAreasValue,
+                          Gecko_AddRefGridTemplateAreasValueArbitraryThread,
+                          Gecko_ReleaseGridTemplateAreasValueArbitraryThread);
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -1047,17 +1047,18 @@ fn static_assert() {
                               need_clone=True) %>
     % endfor
 </%self:impl_trait>
 
 <% skip_position_longhands = " ".join(x.ident for x in SIDES + GRID_LINES) %>
 <%self:impl_trait style_struct_name="Position"
                   skip_longhands="${skip_position_longhands} z-index box-sizing order align-content
                                   justify-content align-self justify-self align-items
-                                  justify-items grid-auto-rows grid-auto-columns grid-auto-flow">
+                                  justify-items grid-auto-rows grid-auto-columns grid-auto-flow
+                                  grid-template-areas">
     % for side in SIDES:
     <% impl_split_style_coord("%s" % side.ident,
                               "mOffset",
                               side.index,
                               need_clone=True) %>
     % endfor
 
     pub fn set_z_index(&mut self, v: longhands::z_index::computed_value::T) {
@@ -1223,16 +1224,52 @@ fn static_assert() {
         self.gecko.mGridAutoFlow |= value as u8;
 
         if v.dense {
             self.gecko.mGridAutoFlow |= NS_STYLE_GRID_AUTO_FLOW_DENSE as u8;
         }
     }
 
     ${impl_simple_copy('grid_auto_flow', 'mGridAutoFlow')}
+
+    pub fn set_grid_template_areas(&mut self, v: longhands::grid_template_areas::computed_value::T) {
+        use gecko_bindings::bindings::Gecko_NewGridTemplateAreasValue;
+        use gecko_bindings::sugar::refptr::UniqueRefPtr;
+
+        let v = match v {
+            Either::First(areas) => areas,
+            Either::Second(_) => {
+                unsafe { self.gecko.mGridTemplateAreas.clear() }
+                return;
+            },
+        };
+
+        let mut refptr = unsafe {
+            UniqueRefPtr::from_addrefed(
+                Gecko_NewGridTemplateAreasValue(v.areas.len() as u32, v.strings.len() as u32, v.width))
+        };
+
+        for (servo, gecko) in v.areas.into_iter().zip(refptr.mNamedAreas.iter_mut()) {
+            gecko.mName.assign_utf8(&*servo.name);
+            gecko.mColumnStart = servo.columns.start;
+            gecko.mColumnEnd = servo.columns.end;
+            gecko.mRowStart = servo.rows.start;
+            gecko.mRowEnd = servo.rows.end;
+        }
+
+        for (servo, gecko) in v.strings.into_iter().zip(refptr.mTemplates.iter_mut()) {
+            gecko.assign_utf8(&*servo);
+        }
+
+        unsafe { self.gecko.mGridTemplateAreas.set_move(refptr.get()) }
+    }
+
+    pub fn copy_grid_template_areas_from(&mut self, other: &Self) {
+        unsafe { self.gecko.mGridTemplateAreas.set(&other.gecko.mGridTemplateAreas) }
+    }
 </%self:impl_trait>
 
 <% skip_outline_longhands = " ".join("outline-style outline-width".split() +
                                      ["-moz-outline-radius-{0}".format(x.ident.replace("_", ""))
                                       for x in CORNERS]) %>
 <%self:impl_trait style_struct_name="Outline"
                   skip_longhands="${skip_outline_longhands}"
                   skip_additionals="*">
--- a/servo/components/style/properties/longhand/position.mako.rs
+++ b/servo/components/style/properties/longhand/position.mako.rs
@@ -401,8 +401,177 @@
                 autoflow: value.unwrap_or(AutoFlow::Row),
                 dense: dense,
             })
         } else {
             Err(())
         }
     }
 </%helpers:longhand>
+
+<%helpers:longhand name="grid-template-areas"
+        spec="https://drafts.csswg.org/css-grid/#propdef-grid-template-areas"
+        products="gecko"
+        animation_value_type="none"
+        disable_when_testing="True">
+    use cssparser::serialize_string;
+    use std::collections::HashMap;
+    use std::fmt;
+    use std::ops::Range;
+    use str::HTML_SPACE_CHARACTERS;
+    use style_traits::ToCss;
+    use style_traits::values::Css;
+    use values::HasViewportPercentage;
+    use values::computed::ComputedValueAsSpecified;
+
+    pub mod computed_value {
+        pub use super::SpecifiedValue as T;
+    }
+
+    pub type SpecifiedValue = Either<TemplateAreas, None_>;
+
+    #[inline]
+    pub fn get_initial_value() -> computed_value::T {
+        Either::Second(None_)
+    }
+
+    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+        SpecifiedValue::parse(context, input)
+    }
+
+    #[derive(Clone, PartialEq)]
+    pub struct TemplateAreas {
+        pub areas: Box<[NamedArea]>,
+        pub strings: Box<[Box<str>]>,
+        pub width: u32,
+    }
+
+    #[derive(Clone, PartialEq)]
+    pub struct NamedArea {
+        pub name: Box<str>,
+        pub rows: Range<u32>,
+        pub columns: Range<u32>,
+    }
+
+    no_viewport_percentage!(TemplateAreas);
+    impl ComputedValueAsSpecified for TemplateAreas {}
+
+    impl Parse for TemplateAreas {
+        fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+            let mut strings = vec![];
+            while let Ok(string) = input.try(Parser::expect_string) {
+                strings.push(string.into_owned().into_boxed_str());
+            }
+            if strings.is_empty() {
+                return Err(());
+            }
+            let mut areas: Vec<NamedArea> = vec![];
+            let mut width = 0;
+            {
+                let mut row = 0u32;
+                let mut area_indices = HashMap::<(&str), usize>::new();
+                for string in &strings {
+                    let mut current_area_index: Option<usize> = None;
+                    row += 1;
+                    let mut column = 0u32;
+                    for token in Tokenizer(string) {
+                        column += 1;
+                        let token = if let Some(token) = token? {
+                            token
+                        } else {
+                            if let Some(index) = current_area_index.take() {
+                                if areas[index].columns.end != column {
+                                    return Err(());
+                                }
+                            }
+                            continue;
+                        };
+                        if let Some(index) = current_area_index {
+                            if &*areas[index].name == token {
+                                if areas[index].rows.start == row {
+                                    areas[index].columns.end += 1;
+                                }
+                                continue;
+                            }
+                            if areas[index].columns.end != column {
+                                return Err(());
+                            }
+                        }
+                        if let Some(index) = area_indices.get(token).cloned() {
+                            if areas[index].columns.start != column || areas[index].rows.end != row {
+                                return Err(());
+                            }
+                            areas[index].rows.end += 1;
+                            current_area_index = Some(index);
+                            continue;
+                        }
+                        let index = areas.len();
+                        areas.push(NamedArea {
+                            name: token.to_owned().into_boxed_str(),
+                            columns: column..(column + 1),
+                            rows: row..(row + 1),
+                        });
+                        assert!(area_indices.insert(token, index).is_none());
+                        current_area_index = Some(index);
+                    }
+                    if let Some(index) = current_area_index {
+                        if areas[index].columns.end != column + 1 {
+                            assert!(areas[index].rows.start != row);
+                            return Err(());
+                        }
+                    }
+                    if row == 1 {
+                        width = column;
+                    } else if width != column {
+                        return Err(());
+                    }
+                }
+            }
+            Ok(TemplateAreas {
+                areas: areas.into_boxed_slice(),
+                strings: strings.into_boxed_slice(),
+                width: width,
+            })
+        }
+    }
+
+    impl ToCss for TemplateAreas {
+        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+            for (i, string) in self.strings.iter().enumerate() {
+                if i != 0 {
+                    dest.write_str(" ")?;
+                }
+                serialize_string(string, dest)?;
+            }
+            Ok(())
+        }
+    }
+
+    struct Tokenizer<'a>(&'a str);
+
+    impl<'a> Iterator for Tokenizer<'a> {
+        type Item = Result<Option<(&'a str)>, ()>;
+
+        fn next(&mut self) -> Option<Self::Item> {
+            let rest = self.0.trim_left_matches(HTML_SPACE_CHARACTERS);
+            if rest.is_empty() {
+                return None;
+            }
+            if rest.starts_with('.') {
+                self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
+                return Some(Ok(None));
+            }
+            if !rest.starts_with(is_name_code_point) {
+                return Some(Err(()));
+            }
+            let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
+            let token = &rest[..token_len];
+            self.0 = &rest[token_len..];
+            Some(Ok(Some(token)))
+        }
+    }
+
+    fn is_name_code_point(c: char) -> bool {
+        c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' ||
+        c >= '\u{80}' || c == '_' ||
+        c >= '0' && c <= '9' || c == '-'
+    }
+</%helpers:longhand>