servo: Merge #16727 - Implement grid-template-areas (fixes #16079) (from nox:grid-template-areas); r=Manishearth
authorAnthony Ramine <n.oxyde@gmail.com>
Thu, 04 May 2017 19:58:22 -0500
changeset 356577 90c1a1964ca9113e4c0dbc050d527fc3434c0072
parent 356576 beace30c107344274ebfa786ac723247e4ff413c
child 356578 e36712843b3bedb06bd08c081c27425d4a067f15
push id31767
push usercbook@mozilla.com
push dateFri, 05 May 2017 13:15:58 +0000
treeherdermozilla-central@8872ad4d52b6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersManishearth
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #16727 - Implement grid-template-areas (fixes #16079) (from nox:grid-template-areas); r=Manishearth Source-Repo: https://github.com/servo/servo Source-Revision: 18c8c290daea3419b02799e206c2cb50845cd0af
servo/components/style/build_gecko.rs
servo/components/style/gecko_bindings/bindings.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/bindings.rs
+++ b/servo/components/style/gecko_bindings/bindings.rs
@@ -1,13 +1,14 @@
 /* automatically generated by rust-bindgen */
 
 pub use nsstring::{nsACString, nsAString, nsString};
 type nsACString_internal = nsACString;
 type nsAString_internal = nsAString;
+use gecko_bindings::structs::mozilla::css::GridTemplateAreasValue;
 use gecko_bindings::structs::mozilla::css::URLValue;
 use gecko_bindings::structs::mozilla::Side;
 use gecko_bindings::structs::RawGeckoAnimationPropertySegment;
 use gecko_bindings::structs::RawGeckoComputedTiming;
 use gecko_bindings::structs::RawGeckoDocument;
 use gecko_bindings::structs::RawGeckoElement;
 use gecko_bindings::structs::RawGeckoKeyframeList;
 use gecko_bindings::structs::RawGeckoComputedKeyframeValuesList;
@@ -889,16 +890,29 @@ extern "C" {
 }
 extern "C" {
     pub fn Gecko_CopyStyleGridTemplateValues(grid_template:
                                                  *mut nsStyleGridTemplate,
                                              other:
                                                  *const nsStyleGridTemplate);
 }
 extern "C" {
+    pub fn Gecko_NewGridTemplateAreasValue(areas: u32, templates: u32,
+                                           columns: u32)
+     -> *mut GridTemplateAreasValue;
+}
+extern "C" {
+    pub fn Gecko_AddRefGridTemplateAreasValueArbitraryThread(aPtr:
+                                                                 *mut GridTemplateAreasValue);
+}
+extern "C" {
+    pub fn Gecko_ReleaseGridTemplateAreasValueArbitraryThread(aPtr:
+                                                                  *mut GridTemplateAreasValue);
+}
+extern "C" {
     pub fn Gecko_ClearAndResizeStyleContents(content: *mut nsStyleContent,
                                              how_many: u32);
 }
 extern "C" {
     pub fn Gecko_ClearAndResizeCounterIncrements(content: *mut nsStyleContent,
                                                  how_many: u32);
 }
 extern "C" {
--- 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,178 @@
                 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"
+        boxed="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>