servo: Merge #15628 - Stylo: Implement grid-auto-{rows,columns} (from Wafflespeanut:grid); r=Manishearth
authorRavi Shankar <wafflespeanut@gmail.com>
Mon, 20 Feb 2017 01:11:03 -0800
changeset 372890 0b4d2f0778ca14a732e5cf224cb7f37858371c1e
parent 372889 4e1d46e028bf3275883431fb10d8b1263af3b288
child 372891 5124565a51dc947de479de0658e70e844993e7b7
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersManishearth
bugs15628, 15312
milestone54.0a1
servo: Merge #15628 - Stylo: Implement grid-auto-{rows,columns} (from Wafflespeanut:grid); r=Manishearth <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #15312 (github issue number if applicable). <!-- Either: --> - [x] These changes do not require tests because it's a stylo thing r? @emilio (cc @Manishearth @upsuper) Source-Repo: https://github.com/servo/servo Source-Revision: 30a31fb744a2fd2b11b901d722704fe458df3022
servo/components/style/build_gecko.rs
servo/components/style/gecko/values.rs
servo/components/style/gecko_bindings/structs_debug.rs
servo/components/style/gecko_bindings/structs_release.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhand/position.mako.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/specified/grid.rs
servo/components/style/values/specified/mod.rs
--- a/servo/components/style/build_gecko.rs
+++ b/servo/components/style/build_gecko.rs
@@ -280,16 +280,17 @@ mod bindings {
             "mozilla::ServoStyleSheet",
             "mozilla::ServoElementSnapshot.*",
             "mozilla::CSSPseudoClassType",
             "mozilla::css::SheetParsingMode",
             "mozilla::HalfCorner",
             "mozilla::PropertyStyleAnimationValuePair",
             "mozilla::TraversalRootBehavior",
             "mozilla::StyleShapeRadius",
+            "mozilla::StyleGrid.*",
             ".*ThreadSafe.*Holder",
             "AnonymousContent",
             "AudioContext",
             "CapturingContentInfo",
             "DefaultDelete",
             "DOMIntersectionObserverEntry",
             "Element",
             "FontFamilyList",
--- a/servo/components/style/gecko/values.rs
+++ b/servo/components/style/gecko/values.rs
@@ -3,23 +3,24 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #![allow(unsafe_code)]
 
 //! Different kind of helpers to interact with Gecko values.
 
 use app_units::Au;
 use cssparser::RGBA;
-use gecko_bindings::structs::{nsStyleCoord, StyleShapeRadius};
+use gecko_bindings::structs::{nsStyleCoord, StyleGridTrackBreadth, StyleShapeRadius};
 use gecko_bindings::sugar::ns_style_coord::{CoordData, CoordDataMut, CoordDataValue};
 use std::cmp::max;
 use values::{Auto, Either, None_};
 use values::computed::{Angle, LengthOrPercentageOrNone, Number};
 use values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto};
 use values::computed::basic_shape::ShapeRadius;
+use values::specified::grid::{TrackBreadth, TrackKeyword};
 
 /// A trait that defines an interface to convert from and to `nsStyleCoord`s.
 pub trait GeckoStyleCoordConvertible : Sized {
     /// Convert this to a `nsStyleCoord`.
     fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T);
     /// Given a `nsStyleCoord`, try to get a value of this type..
     fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self>;
 }
@@ -132,16 +133,49 @@ impl GeckoStyleCoordConvertible for Leng
             CoordDataValue::Percent(p) => Some(LengthOrPercentageOrNone::Percentage(p)),
             CoordDataValue::None => Some(LengthOrPercentageOrNone::None),
             CoordDataValue::Calc(calc) => Some(LengthOrPercentageOrNone::Calc(calc.into())),
             _ => None,
         }
     }
 }
 
+impl<L: GeckoStyleCoordConvertible> GeckoStyleCoordConvertible for TrackBreadth<L> {
+    fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
+        match *self {
+            TrackBreadth::Breadth(ref lop) => lop.to_gecko_style_coord(coord),
+            TrackBreadth::Flex(fr) => coord.set_value(CoordDataValue::FlexFraction(fr)),
+            TrackBreadth::Keyword(TrackKeyword::Auto) => coord.set_value(CoordDataValue::Auto),
+            TrackBreadth::Keyword(TrackKeyword::MinContent) =>
+                coord.set_value(CoordDataValue::Enumerated(StyleGridTrackBreadth::MinContent as u32)),
+            TrackBreadth::Keyword(TrackKeyword::MaxContent) =>
+                coord.set_value(CoordDataValue::Enumerated(StyleGridTrackBreadth::MaxContent as u32)),
+        }
+    }
+
+    fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
+        L::from_gecko_style_coord(coord).map(TrackBreadth::Breadth).or_else(|| {
+            match coord.as_value() {
+                CoordDataValue::Enumerated(v) => {
+                    if v == StyleGridTrackBreadth::MinContent as u32 {
+                        Some(TrackBreadth::Keyword(TrackKeyword::MinContent))
+                    } else if v == StyleGridTrackBreadth::MaxContent as u32 {
+                        Some(TrackBreadth::Keyword(TrackKeyword::MaxContent))
+                    } else {
+                        None
+                    }
+                },
+                CoordDataValue::FlexFraction(fr) => Some(TrackBreadth::Flex(fr)),
+                CoordDataValue::Auto => Some(TrackBreadth::Keyword(TrackKeyword::Auto)),
+                _ => L::from_gecko_style_coord(coord).map(TrackBreadth::Breadth),
+            }
+        })
+    }
+}
+
 impl GeckoStyleCoordConvertible for ShapeRadius {
     fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
         match *self {
             ShapeRadius::ClosestSide => {
                 coord.set_value(CoordDataValue::Enumerated(StyleShapeRadius::ClosestSide as u32))
             }
             ShapeRadius::FarthestSide => {
                 coord.set_value(CoordDataValue::Enumerated(StyleShapeRadius::FarthestSide as u32))
--- a/servo/components/style/gecko_bindings/structs_debug.rs
+++ b/servo/components/style/gecko_bindings/structs_debug.rs
@@ -532,22 +532,16 @@ pub mod root {
     pub const NS_STYLE_FONT_BUTTON: ::std::os::raw::c_uint = 13;
     pub const NS_STYLE_FONT_PULL_DOWN_MENU: ::std::os::raw::c_uint = 14;
     pub const NS_STYLE_FONT_LIST: ::std::os::raw::c_uint = 15;
     pub const NS_STYLE_FONT_FIELD: ::std::os::raw::c_uint = 16;
     pub const NS_STYLE_GRID_AUTO_FLOW_ROW: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_GRID_AUTO_FLOW_COLUMN: ::std::os::raw::c_uint = 2;
     pub const NS_STYLE_GRID_AUTO_FLOW_DENSE: ::std::os::raw::c_uint = 4;
     pub const NS_STYLE_GRID_TEMPLATE_SUBGRID: ::std::os::raw::c_uint = 0;
-    pub const NS_STYLE_GRID_TRACK_BREADTH_MAX_CONTENT: ::std::os::raw::c_uint
-              =
-        1;
-    pub const NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT: ::std::os::raw::c_uint
-              =
-        2;
     pub const NS_STYLE_GRID_REPEAT_AUTO_FILL: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_GRID_REPEAT_AUTO_FIT: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_WIDTH_MAX_CONTENT: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_WIDTH_MIN_CONTENT: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_WIDTH_FIT_CONTENT: ::std::os::raw::c_uint = 2;
     pub const NS_STYLE_WIDTH_AVAILABLE: ::std::os::raw::c_uint = 3;
     pub const NS_STYLE_POSITION_STATIC: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_POSITION_RELATIVE: ::std::os::raw::c_uint = 1;
@@ -5808,16 +5802,19 @@ pub mod root {
             MozGridGroup = 32,
             MozGridLine = 33,
             MozStack = 34,
             MozInlineStack = 35,
             MozDeck = 36,
             MozGroupbox = 37,
             MozPopup = 38,
         }
+        #[repr(u8)]
+        #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+        pub enum StyleGridTrackBreadth { MaxContent = 1, MinContent = 2, }
         #[repr(C)]
         #[derive(Debug, Copy, Clone)]
         pub struct WritingMode([u8; 0]);
         #[repr(u32)]
         #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
         pub enum LogicalSide {
             eLogicalSideBStart = 0,
             eLogicalSideBEnd = 1,
--- a/servo/components/style/gecko_bindings/structs_release.rs
+++ b/servo/components/style/gecko_bindings/structs_release.rs
@@ -532,22 +532,16 @@ pub mod root {
     pub const NS_STYLE_FONT_BUTTON: ::std::os::raw::c_uint = 13;
     pub const NS_STYLE_FONT_PULL_DOWN_MENU: ::std::os::raw::c_uint = 14;
     pub const NS_STYLE_FONT_LIST: ::std::os::raw::c_uint = 15;
     pub const NS_STYLE_FONT_FIELD: ::std::os::raw::c_uint = 16;
     pub const NS_STYLE_GRID_AUTO_FLOW_ROW: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_GRID_AUTO_FLOW_COLUMN: ::std::os::raw::c_uint = 2;
     pub const NS_STYLE_GRID_AUTO_FLOW_DENSE: ::std::os::raw::c_uint = 4;
     pub const NS_STYLE_GRID_TEMPLATE_SUBGRID: ::std::os::raw::c_uint = 0;
-    pub const NS_STYLE_GRID_TRACK_BREADTH_MAX_CONTENT: ::std::os::raw::c_uint
-              =
-        1;
-    pub const NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT: ::std::os::raw::c_uint
-              =
-        2;
     pub const NS_STYLE_GRID_REPEAT_AUTO_FILL: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_GRID_REPEAT_AUTO_FIT: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_WIDTH_MAX_CONTENT: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_WIDTH_MIN_CONTENT: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_WIDTH_FIT_CONTENT: ::std::os::raw::c_uint = 2;
     pub const NS_STYLE_WIDTH_AVAILABLE: ::std::os::raw::c_uint = 3;
     pub const NS_STYLE_POSITION_STATIC: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_POSITION_RELATIVE: ::std::os::raw::c_uint = 1;
@@ -5648,16 +5642,19 @@ pub mod root {
             MozGridGroup = 32,
             MozGridLine = 33,
             MozStack = 34,
             MozInlineStack = 35,
             MozDeck = 36,
             MozGroupbox = 37,
             MozPopup = 38,
         }
+        #[repr(u8)]
+        #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+        pub enum StyleGridTrackBreadth { MaxContent = 1, MinContent = 2, }
         #[repr(C)]
         #[derive(Debug, Copy, Clone)]
         pub struct WritingMode([u8; 0]);
         #[repr(u32)]
         #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
         pub enum LogicalSide {
             eLogicalSideBStart = 0,
             eLogicalSideBEnd = 1,
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -956,17 +956,17 @@ 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">
+                                  justify-items grid-auto-rows grid-auto-columns">
     % 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) {
@@ -1078,16 +1078,46 @@ fn static_assert() {
 
     pub fn copy_${value.name}_from(&mut self, other: &Self) {
         self.gecko.${value.gecko}.mHasSpan = other.gecko.${value.gecko}.mHasSpan;
         self.gecko.${value.gecko}.mInteger = other.gecko.${value.gecko}.mInteger;
         self.gecko.${value.gecko}.mLineName.assign(&*other.gecko.${value.gecko}.mLineName);
     }
     % endfor
 
+    % for kind in ["rows", "columns"]:
+    pub fn set_grid_auto_${kind}(&mut self, v: longhands::grid_auto_rows::computed_value::T) {
+        use values::specified::grid::TrackSize;
+
+        match v {
+            TrackSize::FitContent(lop) => {
+                // Gecko sets min value to None and max value to the actual value in fit-content
+                // https://dxr.mozilla.org/mozilla-central/rev/0eef1d5/layout/style/nsRuleNode.cpp#8221
+                self.gecko.mGridAuto${kind.title()}Min.set_value(CoordDataValue::None);
+                lop.to_gecko_style_coord(&mut self.gecko.mGridAuto${kind.title()}Max);
+            },
+            TrackSize::Breadth(breadth) => {
+                // Set the value to both fields if there's one breadth value
+                // https://dxr.mozilla.org/mozilla-central/rev/0eef1d5/layout/style/nsRuleNode.cpp#8230
+                breadth.to_gecko_style_coord(&mut self.gecko.mGridAuto${kind.title()}Min);
+                breadth.to_gecko_style_coord(&mut self.gecko.mGridAuto${kind.title()}Max);
+            },
+            TrackSize::MinMax(min, max) => {
+                min.to_gecko_style_coord(&mut self.gecko.mGridAuto${kind.title()}Min);
+                max.to_gecko_style_coord(&mut self.gecko.mGridAuto${kind.title()}Max);
+            },
+        }
+    }
+
+    pub fn copy_grid_auto_${kind}_from(&mut self, other: &Self) {
+        self.gecko.mGridAuto${kind.title()}Min.copy_from(&other.gecko.mGridAuto${kind.title()}Min);
+        self.gecko.mGridAuto${kind.title()}Max.copy_from(&other.gecko.mGridAuto${kind.title()}Max);
+    }
+    % endfor
+
 </%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
@@ -250,33 +250,36 @@
 ${helpers.predefined_type("object-position",
                           "Position",
                           "computed::Position::zero()",
                           products="gecko",
                           boxed="True",
                           spec="https://drafts.csswg.org/css-images-3/#the-object-position",
                           animatable=True)}
 
-<% grid_longhands = ["grid-row-start", "grid-row-end", "grid-column-start", "grid-column-end"] %>
+% for kind in ["row", "column"]:
+    ${helpers.predefined_type("grid-%s-gap" % kind,
+                              "LengthOrPercentage",
+                              "computed::LengthOrPercentage::Length(Au(0))",
+                              spec="https://drafts.csswg.org/css-grid/#propdef-grid-%s-gap" % kind,
+                              animatable=True,
+                              products="gecko")}
 
-% for longhand in grid_longhands:
-    ${helpers.predefined_type("%s" % longhand,
-                              "GridLine",
+    % for range in ["start", "end"]:
+        ${helpers.predefined_type("grid-%s-%s" % (kind, range),
+                                  "GridLine",
+                                  "Default::default()",
+                                  animatable=False,
+                                  spec="https://drafts.csswg.org/css-grid/#propdef-grid-%s-%s" % (kind, range),
+                                  products="gecko",
+                                  boxed=True)}
+    % endfor
+
+    // NOTE: According to the spec, this should handle multiple values of `<track-size>`,
+    // but gecko supports only a single value
+    ${helpers.predefined_type("grid-auto-%ss" % kind,
+                              "TrackSize",
                               "Default::default()",
                               animatable=False,
-                              spec="https://drafts.csswg.org/css-grid/#propdef-%s" % longhand,
+                              spec="https://drafts.csswg.org/css-grid/#propdef-grid-auto-%ss" % kind,
                               products="gecko",
                               boxed=True)}
 % endfor
-
-${helpers.predefined_type("grid-row-gap",
-                          "LengthOrPercentage",
-                          "computed::LengthOrPercentage::Length(Au(0))",
-                          spec="https://drafts.csswg.org/css-grid/#propdef-grid-row-gap",
-                          animatable=True,
-                          products="gecko")}
-
-${helpers.predefined_type("grid-column-gap",
-                          "LengthOrPercentage",
-                          "computed::LengthOrPercentage::Length(Au(0))",
-                          spec="https://drafts.csswg.org/css-grid/#propdef-grid-column-gap",
-                          animatable=True,
-                          products="gecko")}
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -6,16 +6,17 @@
 
 use app_units::Au;
 use euclid::size::Size2D;
 use font_metrics::FontMetricsProvider;
 use properties::ComputedValues;
 use std::fmt;
 use style_traits::ToCss;
 use super::{CSSFloat, RGBA, specified};
+use super::specified::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize};
 
 pub use cssparser::Color as CSSColor;
 pub use self::image::{AngleOrCorner, EndingShape as GradientShape, Gradient, GradientKind, Image};
 pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword};
 pub use super::{Auto, Either, None_};
 #[cfg(feature = "gecko")]
 pub use super::specified::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use super::specified::{Angle, BorderStyle, GridLine, Time, UrlOrNone};
@@ -321,16 +322,22 @@ impl ToCss for ClipRect {
         try!(self.left.to_css(dest));
         dest.write_str(")")
     }
 }
 
 /// rect(...) | auto
 pub type ClipRectOrAuto = Either<ClipRect, Auto>;
 
+/// The computed value of a grid `<track-breadth>`
+pub type TrackBreadth = GenericTrackBreadth<LengthOrPercentage>;
+
+/// The computed value of a grid `<track-size>`
+pub type TrackSize = GenericTrackSize<LengthOrPercentage>;
+
 impl ClipRectOrAuto {
     /// Return an auto (default for clip-rect and image-region) value
     pub fn auto() -> Self {
         Either::Second(Auto)
     }
 
     /// Check if it is auto
     pub fn is_auto(&self) -> bool {
--- a/servo/components/style/values/specified/grid.rs
+++ b/servo/components/style/values/specified/grid.rs
@@ -1,28 +1,36 @@
 /* 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/. */
 
-//! A grid line type.
+//! Necessary types for [grid](https://drafts.csswg.org/css-grid/).
 
-use cssparser::Parser;
+use cssparser::{Parser, Token};
 use parser::{Parse, ParserContext};
 use std::fmt;
 use style_traits::ToCss;
-use values::HasViewportPercentage;
-use values::computed::ComputedValueAsSpecified;
+use values::{CSSFloat, HasViewportPercentage};
+use values::computed::{ComputedValueAsSpecified, Context, ToComputedValue};
+use values::specified::LengthOrPercentage;
 
 #[derive(PartialEq, Clone, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+/// A `<grid-line>` type.
+///
 /// https://drafts.csswg.org/css-grid/#typedef-grid-row-start-grid-line
 #[allow(missing_docs)]
 pub struct GridLine {
+    /// Flag to check whether it's a `span` keyword.
     pub is_span: bool,
+    /// A custom identifier for named lines.
+    ///
+    /// https://drafts.csswg.org/css-grid/#grid-placement-slot
     pub ident: Option<String>,
+    /// Denotes the nth grid line from grid item's placement.
     pub integer: Option<i32>,
 }
 
 impl Default for GridLine {
     fn default() -> Self {
         GridLine {
             is_span: false,
             ident: None,
@@ -92,8 +100,204 @@ impl Parse for GridLine {
         }
 
         Ok(grid_line)
     }
 }
 
 impl ComputedValueAsSpecified for GridLine {}
 no_viewport_percentage!(GridLine);
+
+define_css_keyword_enum!{ TrackKeyword:
+    "auto" => Auto,
+    "max-content" => MaxContent,
+    "min-content" => MinContent
+}
+
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+/// A track breadth for explicit grid track sizing. It's generic solely to
+/// avoid re-implementing it for the computed type.
+///
+/// https://drafts.csswg.org/css-grid/#typedef-track-breadth
+pub enum TrackBreadth<L> {
+    /// The generic type is almost always a non-negative `<length-percentage>`
+    Breadth(L),
+    /// A flex fraction specified in `fr` units.
+    Flex(CSSFloat),
+    /// One of the track-sizing keywords (`auto`, `min-content`, `max-content`)
+    Keyword(TrackKeyword),
+}
+
+/// Parse a single flexible length.
+pub fn parse_flex(input: &mut Parser) -> Result<CSSFloat, ()> {
+    match try!(input.next()) {
+        Token::Dimension(ref value, ref unit) if unit.to_lowercase() == "fr" && value.value.is_sign_positive()
+            => Ok(value.value),
+        _ => Err(()),
+    }
+}
+
+impl Parse for TrackBreadth<LengthOrPercentage> {
+    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        if let Ok(lop) = input.try(LengthOrPercentage::parse_non_negative) {
+            Ok(TrackBreadth::Breadth(lop))
+        } else {
+            if let Ok(f) = input.try(parse_flex) {
+                Ok(TrackBreadth::Flex(f))
+            } else {
+                TrackKeyword::parse(input).map(TrackBreadth::Keyword)
+            }
+        }
+    }
+}
+
+impl<L: ToCss> ToCss for TrackBreadth<L> {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            TrackBreadth::Breadth(ref lop) => lop.to_css(dest),
+            TrackBreadth::Flex(ref value) => write!(dest, "{}fr", value),
+            TrackBreadth::Keyword(ref k) => k.to_css(dest),
+        }
+    }
+}
+
+impl HasViewportPercentage for TrackBreadth<LengthOrPercentage> {
+    #[inline]
+    fn has_viewport_percentage(&self) -> bool {
+        if let TrackBreadth::Breadth(ref lop) = *self {
+            lop.has_viewport_percentage()
+        } else {
+            false
+        }
+    }
+}
+
+impl<L: ToComputedValue> ToComputedValue for TrackBreadth<L> {
+    type ComputedValue = TrackBreadth<L::ComputedValue>;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            TrackBreadth::Breadth(ref lop) => TrackBreadth::Breadth(lop.to_computed_value(context)),
+            TrackBreadth::Flex(fr) => TrackBreadth::Flex(fr),
+            TrackBreadth::Keyword(k) => TrackBreadth::Keyword(k),
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            TrackBreadth::Breadth(ref lop) =>
+                TrackBreadth::Breadth(ToComputedValue::from_computed_value(lop)),
+            TrackBreadth::Flex(fr) => TrackBreadth::Flex(fr),
+            TrackBreadth::Keyword(k) => TrackBreadth::Keyword(k),
+        }
+    }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+/// A `<track-size>` type for explicit grid track sizing. Like `<track-breadth>`, this is
+/// generic only to avoid code bloat. It only takes `<length-percentage>`
+///
+/// https://drafts.csswg.org/css-grid/#typedef-track-size
+pub enum TrackSize<L> {
+    /// A flexible `<track-breadth>`
+    Breadth(TrackBreadth<L>),
+    /// A `minmax` function for a range over an inflexible `<track-breadth>`
+    /// and a flexible `<track-breadth>`
+    ///
+    /// https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-minmax
+    MinMax(TrackBreadth<L>, TrackBreadth<L>),
+    /// A `fit-content` function.
+    ///
+    /// https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-fit-content
+    FitContent(L),
+}
+
+impl<L> Default for TrackSize<L> {
+    fn default() -> Self {
+        TrackSize::Breadth(TrackBreadth::Keyword(TrackKeyword::Auto))
+    }
+}
+
+impl Parse for TrackSize<LengthOrPercentage> {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        if let Ok(b) = input.try(|i| TrackBreadth::parse(context, i)) {
+            Ok(TrackSize::Breadth(b))
+        } else {
+            if input.try(|i| i.expect_function_matching("minmax")).is_ok() {
+                Ok(try!(input.parse_nested_block(|input| {
+                    let inflexible_breadth = if let Ok(lop) = input.try(LengthOrPercentage::parse_non_negative) {
+                        Ok(TrackBreadth::Breadth(lop))
+                    } else {
+                        TrackKeyword::parse(input).map(TrackBreadth::Keyword)
+                    };
+
+                    try!(input.expect_comma());
+                    Ok(TrackSize::MinMax(try!(inflexible_breadth), try!(TrackBreadth::parse(context, input))))
+                })))
+            } else {
+                try!(input.expect_function_matching("fit-content"));
+                Ok(try!(LengthOrPercentage::parse(context, input).map(TrackSize::FitContent)))
+            }
+        }
+    }
+}
+
+impl<L: ToCss> ToCss for TrackSize<L> {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            TrackSize::Breadth(ref b) => b.to_css(dest),
+            TrackSize::MinMax(ref infexible, ref flexible) => {
+                try!(dest.write_str("minmax("));
+                try!(infexible.to_css(dest));
+                try!(dest.write_str(","));
+                try!(flexible.to_css(dest));
+                dest.write_str(")")
+            },
+            TrackSize::FitContent(ref lop) => {
+                try!(dest.write_str("fit-content("));
+                try!(lop.to_css(dest));
+                dest.write_str(")")
+            },
+        }
+    }
+}
+
+impl HasViewportPercentage for TrackSize<LengthOrPercentage> {
+    #[inline]
+    fn has_viewport_percentage(&self) -> bool {
+        match *self {
+            TrackSize::Breadth(ref b) => b.has_viewport_percentage(),
+            TrackSize::MinMax(ref inf_b, ref b) => inf_b.has_viewport_percentage() || b.has_viewport_percentage(),
+            TrackSize::FitContent(ref lop) => lop.has_viewport_percentage(),
+        }
+    }
+}
+
+impl<L: ToComputedValue> ToComputedValue for TrackSize<L> {
+    type ComputedValue = TrackSize<L::ComputedValue>;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            TrackSize::Breadth(ref b) => TrackSize::Breadth(b.to_computed_value(context)),
+            TrackSize::MinMax(ref b_1, ref b_2) =>
+                TrackSize::MinMax(b_1.to_computed_value(context), b_2.to_computed_value(context)),
+            TrackSize::FitContent(ref lop) => TrackSize::FitContent(lop.to_computed_value(context)),
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            TrackSize::Breadth(ref b) =>
+                TrackSize::Breadth(ToComputedValue::from_computed_value(b)),
+            TrackSize::MinMax(ref b_1, ref b_2) =>
+                TrackSize::MinMax(ToComputedValue::from_computed_value(b_1),
+                                  ToComputedValue::from_computed_value(b_2)),
+            TrackSize::FitContent(ref lop) =>
+                TrackSize::FitContent(ToComputedValue::from_computed_value(lop)),
+        }
+    }
+}
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -5,29 +5,30 @@
 //! Specified values.
 //!
 //! TODO(emilio): Enhance docs.
 
 use app_units::Au;
 use cssparser::{self, Parser, Token};
 use euclid::size::Size2D;
 use parser::{ParserContext, Parse};
+use self::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize};
 use self::url::SpecifiedUrl;
 use std::ascii::AsciiExt;
 use std::f32::consts::PI;
 use std::fmt;
 use std::ops::Mul;
 use style_traits::ToCss;
 use super::{Auto, CSSFloat, HasViewportPercentage, Either, None_};
 use super::computed::{ComputedValueAsSpecified, Context, ToComputedValue};
 use super::computed::Shadow as ComputedShadow;
 
 #[cfg(feature = "gecko")]
 pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
-pub use self::grid::GridLine;
+pub use self::grid::{GridLine, TrackKeyword};
 pub use self::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use self::image::{GradientKind, HorizontalDirection, Image, LengthOrKeyword, LengthOrPercentageOrKeyword};
 pub use self::image::{SizeKeyword, VerticalDirection};
 pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage};
 pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, NoCalcLength, CalcUnit};
 pub use self::position::{HorizontalPosition, Position, VerticalPosition};
 
@@ -549,16 +550,22 @@ impl ToCss for Opacity {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         self.0.to_css(dest)
     }
 }
 
 #[allow(missing_docs)]
 pub type UrlOrNone = Either<SpecifiedUrl, None_>;
 
+/// The specified value of a grid `<track-breadth>`
+pub type TrackBreadth = GenericTrackBreadth<LengthOrPercentage>;
+
+/// The specified value of a grid `<track-size>`
+pub type TrackSize = GenericTrackSize<LengthOrPercentage>;
+
 #[derive(Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub struct Shadow {
     pub offset_x: Length,
     pub offset_y: Length,
     pub blur_radius: Length,
     pub spread_radius: Length,