servo: Merge #17209 - Introduce more generics and more deriving (from servo:derive-all-the-things); r=emilio
authorAnthony Ramine <n.oxyde@gmail.com>
Wed, 07 Jun 2017 08:55:08 -0700
changeset 410947 dad55ab4119137316c3b5d6107e3afba00d02385
parent 410946 ff64a7889c1fac56d48205fc2fad4eb5aa481a73
child 410948 a1759d29a2cdf4f1bc7ed744b9b5f1d86f707a9c
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
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 #17209 - Introduce more generics and more deriving (from servo:derive-all-the-things); r=emilio Source-Repo: https://github.com/servo/servo Source-Revision: 738483742c32f6de680b13be31ffc552059b39b9
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhand/box.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/components/style/properties/longhand/text.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/computed/text.rs
servo/components/style/values/generics/background.rs
servo/components/style/values/generics/basic_shape.rs
servo/components/style/values/generics/border.rs
servo/components/style/values/generics/grid.rs
servo/components/style/values/generics/mod.rs
servo/components/style/values/generics/text.rs
servo/components/style/values/specified/border.rs
servo/components/style/values/specified/length.rs
servo/components/style/values/specified/mod.rs
servo/components/style/values/specified/text.rs
servo/components/style/values/specified/transform.rs
servo/components/style_derive/to_css.rs
servo/components/style_traits/values.rs
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -3889,28 +3889,28 @@ fn static_assert() {
         }
         self.clear_overflow_sides_if_string();
         set(&mut self.gecko.mTextOverflow.mLeft, &other.gecko.mTextOverflow.mLeft);
         set(&mut self.gecko.mTextOverflow.mRight, &other.gecko.mTextOverflow.mRight);
         self.gecko.mTextOverflow.mLogicalDirections = other.gecko.mTextOverflow.mLogicalDirections;
     }
 
     pub fn set_initial_letter(&mut self, v: longhands::initial_letter::computed_value::T) {
-        use properties::longhands::initial_letter::computed_value::T;
+        use values::generics::text::InitialLetter;
         match v {
-            T::Normal => {
+            InitialLetter::Normal => {
                 self.gecko.mInitialLetterSize = 0.;
                 self.gecko.mInitialLetterSink = 0;
             },
-            T::Specified(size, sink) => {
-                self.gecko.mInitialLetterSize = size.get();
+            InitialLetter::Specified(size, sink) => {
+                self.gecko.mInitialLetterSize = size;
                 if let Some(sink) = sink {
-                    self.gecko.mInitialLetterSink = sink.value();
+                    self.gecko.mInitialLetterSink = sink;
                 } else {
-                    self.gecko.mInitialLetterSink = size.get().floor() as i32;
+                    self.gecko.mInitialLetterSink = size.floor() as i32;
                 }
             }
         }
     }
 
     pub fn copy_initial_letter_from(&mut self, other: &Self) {
         self.gecko.mInitialLetterSize = other.gecko.mInitialLetterSize;
         self.gecko.mInitialLetterSink = other.gecko.mInitialLetterSink;
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -540,27 +540,25 @@
                           spec="https://drafts.csswg.org/css-transitions/#propdef-animation-timing-function")}
 
 <%helpers:vector_longhand name="animation-iteration-count"
                           need_index="True"
                           animation_value_type="none",
                           extra_prefixes="moz webkit"
                           spec="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count",
                           allowed_in_keyframe_block="False">
-    use std::fmt;
-    use style_traits::ToCss;
     use values::computed::ComputedValueAsSpecified;
 
     pub mod computed_value {
         pub use super::SpecifiedValue as T;
     }
 
     // https://drafts.csswg.org/css-animations/#animation-iteration-count
-    #[derive(Debug, Clone, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+    #[derive(Debug, Clone, PartialEq, ToCss)]
     pub enum SpecifiedValue {
         Number(f32),
         Infinite,
     }
 
     impl Parse for SpecifiedValue {
         fn parse(_context: &ParserContext, input: &mut ::cssparser::Parser) -> Result<Self, ()> {
             if input.try(|input| input.expect_ident_matching("infinite")).is_ok() {
@@ -571,25 +569,16 @@
             if number < 0.0 {
                 return Err(());
             }
 
             Ok(SpecifiedValue::Number(number))
         }
     }
 
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                SpecifiedValue::Number(n) => write!(dest, "{}", n),
-                SpecifiedValue::Infinite => dest.write_str("infinite"),
-            }
-        }
-    }
-
     no_viewport_percentage!(SpecifiedValue);
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         get_initial_specified_value()
     }
 
     #[inline]
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -1034,38 +1034,25 @@
             } else {
                 None
             }
         }
     }
 
     pub mod computed_value {
         use properties::animated_properties::Animatable;
-        use std::fmt;
-        use style_traits::ToCss;
         use values::CSSFloat;
 
-        #[derive(Copy, Clone, Debug, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+        #[derive(Copy, Clone, Debug, PartialEq, ToCss)]
         pub enum T {
             None,
             Number(CSSFloat),
         }
 
-        impl ToCss for T {
-            fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-                where W: fmt::Write,
-            {
-                match *self {
-                    T::None => dest.write_str("none"),
-                    T::Number(number) => number.to_css(dest),
-                }
-            }
-        }
-
         impl T {
             pub fn from_gecko_adjust(gecko: f32) -> Self {
                 if gecko == -1.0 {
                     T::None
                 } else {
                     T::Number(gecko)
                 }
             }
@@ -2209,49 +2196,37 @@ https://drafts.csswg.org/css-fonts-4/#lo
         //! the specified system font. When the cascade function (in helpers)
         //! detects that a value has a system font, it will resolve it, and
         //! cache it on the ComputedValues. After this, it can be just fetched
         //! whenever a font longhand on the same element needs the system font.
 
         use app_units::Au;
         use cssparser::Parser;
         use properties::longhands;
-        use std::fmt;
         use std::hash::{Hash, Hasher};
-        use style_traits::ToCss;
         use values::computed::{ToComputedValue, Context};
         <%
             system_fonts = """caption icon menu message-box small-caption status-bar
                               -moz-window -moz-document -moz-workspace -moz-desktop
                               -moz-info -moz-dialog -moz-button -moz-pull-down-menu
                               -moz-list -moz-field""".split()
             kw_font_props = """font_style font_variant_caps font_stretch
                                font_kerning font_variant_position font_variant_alternates
                                font_variant_ligatures font_variant_east_asian
                                font_variant_numeric""".split()
             kw_cast = """font_style font_variant_caps font_stretch
                          font_kerning font_variant_position""".split()
         %>
-        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+        #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ToCss)]
         pub enum SystemFont {
             % for font in system_fonts:
                 ${to_camel_case(font)},
             % endfor
         }
 
-        impl ToCss for SystemFont {
-            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-                dest.write_str(match *self {
-                    % for font in system_fonts:
-                        SystemFont::${to_camel_case(font)} => "${font}",
-                    % endfor
-                })
-            }
-        }
-
         // ComputedValues are compared at times
         // so we need these impls. We don't want to
         // add Eq to Number (which contains a float)
         // so instead we have an eq impl which skips the
         // cached values
         impl PartialEq for ComputedSystemFont {
             fn eq(&self, other: &Self) -> bool {
                 self.system_font == other.system_font
--- a/servo/components/style/properties/longhand/text.mako.rs
+++ b/servo/components/style/properties/longhand/text.mako.rs
@@ -282,77 +282,16 @@
     "computed::CSSColor::CurrentColor",
     initial_specified_value="specified::CSSColor::currentcolor()",
     complex_color=True,
     products="gecko",
     animation_value_type="IntermediateColor",
     ignored_when_colors_disabled=True,
     spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-color")}
 
-<%helpers:longhand name="initial-letter"
-                   animation_value_type="none"
-                   products="gecko"
-                   spec="https://drafts.csswg.org/css-inline/#sizing-drop-initials">
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::computed::ComputedValueAsSpecified;
-    use values::specified::{Number, Integer};
-
-    impl ComputedValueAsSpecified for SpecifiedValue {}
-    no_viewport_percentage!(SpecifiedValue);
-
-    #[derive(PartialEq, Clone, Debug)]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub enum SpecifiedValue {
-        Normal,
-        Specified(Number, Option<Integer>)
-    }
-
-    pub mod computed_value {
-        pub use super::SpecifiedValue as T;
-    }
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                SpecifiedValue::Normal => try!(dest.write_str("normal")),
-                SpecifiedValue::Specified(size, sink) => {
-                    try!(size.to_css(dest));
-                    if let Some(sink) = sink {
-                        try!(dest.write_str(" "));
-                        try!(sink.to_css(dest));
-                    }
-                }
-            };
-
-            Ok(())
-        }
-    }
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        computed_value::T::Normal
-    }
-
-    #[inline]
-    pub fn get_initial_specified_value() -> SpecifiedValue {
-        SpecifiedValue::Normal
-    }
-
-    /// normal | <number> <integer>?
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
-            return Ok(SpecifiedValue::Normal);
-        }
-
-        let size = try!(Number::parse_at_least_one(context, input));
-
-        match input.try(|input| Integer::parse(context, input)) {
-            Ok(number) => {
-                if number.value() < 1 {
-                    return Err(());
-                }
-                Ok(SpecifiedValue::Specified(size, Some(number)))
-            }
-            Err(()) => Ok(SpecifiedValue::Specified(size, None)),
-        }
-    }
-</%helpers:longhand>
+${helpers.predefined_type(
+    "initial-letter",
+    "InitialLetter",
+    "computed::InitialLetter::normal()",
+    initial_specified_value="specified::InitialLetter::normal()",
+    animation_value_type="none",
+    products="gecko",
+    spec="https://drafts.csswg.org/css-inline/#sizing-drop-initials")}
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -445,18 +445,18 @@ impl PropertyDeclarationIdSet {
                     )
                 ).borrow()
             );
         }
     % endif
 % endfor
 
 /// An enum to represent a CSS Wide keyword.
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Copy, Clone, Debug, Eq, PartialEq, ToCss)]
 pub enum CSSWideKeyword {
     /// The `initial` keyword.
     Initial,
     /// The `inherit` keyword.
     Inherit,
     /// The `unset` keyword.
     Unset,
 }
@@ -478,22 +478,16 @@ impl CSSWideKeyword {
             "initial" => Some(CSSWideKeyword::Initial),
             "inherit" => Some(CSSWideKeyword::Inherit),
             "unset" => Some(CSSWideKeyword::Unset),
             _ => None
         }
     }
 }
 
-impl ToCss for CSSWideKeyword {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        dest.write_str(self.to_str())
-    }
-}
-
 impl Parse for CSSWideKeyword {
     fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         let ident = input.expect_ident()?;
         input.expect_exhausted()?;
         CSSWideKeyword::from_ident(&ident).ok_or(())
     }
 }
 
@@ -623,33 +617,25 @@ impl LonghandId {
             LonghandId::TextDecorationLine |
             LonghandId::WritingMode |
             LonghandId::Direction
         )
     }
 }
 
 /// An identifier for a given shorthand property.
-#[derive(Clone, Copy, Eq, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, ToCss)]
 pub enum ShorthandId {
     % for property in data.shorthands:
         /// ${property.name}
         ${property.camel_case},
     % endfor
 }
 
-impl ToCss for ShorthandId {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-        where W: fmt::Write,
-    {
-        dest.write_str(self.name())
-    }
-}
-
 impl ShorthandId {
     /// Get the name for this shorthand property.
     pub fn name(&self) -> &'static str {
         match *self {
             % for property in data.shorthands:
                 ShorthandId::${property.camel_case} => "${property.name}",
             % endfor
         }
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -37,17 +37,17 @@ pub use super::{Auto, Either, None_};
 pub use super::specified::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use super::specified::{BorderStyle, Percentage, UrlOrNone};
 pub use super::generics::grid::GridLine;
 pub use super::specified::url::SpecifiedUrl;
 pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrAutoOrContent, LengthOrPercentageOrNone, LengthOrNone};
 pub use self::length::{MaxLength, MozLength};
 pub use self::position::Position;
-pub use self::text::{LetterSpacing, LineHeight, WordSpacing};
+pub use self::text::{InitialLetter, LetterSpacing, LineHeight, WordSpacing};
 pub use self::transform::{TimingFunction, TransformOrigin};
 
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod image;
 #[cfg(feature = "gecko")]
 pub mod gecko;
--- a/servo/components/style/values/computed/text.rs
+++ b/servo/components/style/values/computed/text.rs
@@ -1,19 +1,24 @@
 /* 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/. */
 
 //! Computed types for text properties.
 
 use app_units::Au;
 use properties::animated_properties::Animatable;
-use values::CSSFloat;
+use values::{CSSInteger, CSSFloat};
 use values::computed::length::{Length, LengthOrPercentage};
-use values::generics::text::{LineHeight as GenericLineHeight, Spacing};
+use values::generics::text::InitialLetter as GenericInitialLetter;
+use values::generics::text::LineHeight as GenericLineHeight;
+use values::generics::text::Spacing;
+
+/// A computed value for the `initial-letter` property.
+pub type InitialLetter = GenericInitialLetter<CSSFloat, CSSInteger>;
 
 /// A computed value for the `letter-spacing` property.
 pub type LetterSpacing = Spacing<Length>;
 
 /// A computed value for the `word-spacing` property.
 pub type WordSpacing = Spacing<LengthOrPercentage>;
 
 /// A computed value for the `line-height` property.
--- a/servo/components/style/values/generics/background.rs
+++ b/servo/components/style/values/generics/background.rs
@@ -1,19 +1,16 @@
 /* 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/. */
 
 //! Generic types for CSS values related to backgrounds.
 
-use std::fmt;
-use style_traits::ToCss;
-
 /// A generic value for the `background-size` property.
-#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)]
+#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum BackgroundSize<LengthOrPercentageOrAuto> {
     /// `<width> <height>`
     Explicit {
         /// Explicit width.
         width: LengthOrPercentageOrAuto,
         /// Explicit height.
         height: LengthOrPercentageOrAuto
@@ -27,26 +24,8 @@ pub enum BackgroundSize<LengthOrPercenta
 impl<L> From<L> for BackgroundSize<L>
     where L: Clone,
 {
     #[inline]
     fn from(value: L) -> Self {
         BackgroundSize::Explicit { width: value.clone(), height: value }
     }
 }
-
-impl<L> ToCss for BackgroundSize<L>
-    where L: ToCss
-{
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-        where W: fmt::Write
-    {
-        match *self {
-            BackgroundSize::Explicit { ref width, ref height } => {
-                width.to_css(dest)?;
-                dest.write_str(" ")?;
-                height.to_css(dest)
-            },
-            BackgroundSize::Cover => dest.write_str("cover"),
-            BackgroundSize::Contain => dest.write_str("contain"),
-        }
-    }
-}
--- a/servo/components/style/values/generics/basic_shape.rs
+++ b/servo/components/style/values/generics/basic_shape.rs
@@ -14,17 +14,17 @@ use values::generics::rect::Rect;
 use values::specified::url::SpecifiedUrl;
 
 /// A clipping shape, for `clip-path`.
 pub type ClippingShape<BasicShape> = ShapeSource<BasicShape, GeometryBox>;
 
 /// https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box
 #[allow(missing_docs)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Clone, Copy, Debug, PartialEq, ToCss)]
 pub enum GeometryBox {
     FillBox,
     StrokeBox,
     ViewBox,
     ShapeBox(ShapeBox),
 }
 impl ComputedValueAsSpecified for GeometryBox {}
 
@@ -137,27 +137,16 @@ impl<B: ToCss, T: ToCss> ToCss for Shape
             },
             ShapeSource::Shape(ref shape, None) => shape.to_css(dest),
             ShapeSource::Box(ref val) => val.to_css(dest),
             ShapeSource::None => dest.write_str("none"),
         }
     }
 }
 
-impl ToCss for GeometryBox {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            GeometryBox::FillBox => dest.write_str("fill-box"),
-            GeometryBox::StrokeBox => dest.write_str("stroke-box"),
-            GeometryBox::ViewBox => dest.write_str("view-box"),
-            GeometryBox::ShapeBox(s) => s.to_css(dest),
-        }
-    }
-}
-
 impl<L> ToCss for InsetRect<L>
     where L: ToCss + PartialEq
 {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         dest.write_str("inset(")?;
         self.rect.to_css(dest)?;
         if let Some(ref radius) = self.round {
             dest.write_str(" round ")?;
--- a/servo/components/style/values/generics/border.rs
+++ b/servo/components/style/values/generics/border.rs
@@ -6,17 +6,17 @@
 
 use euclid::Size2D;
 use std::fmt;
 use style_traits::ToCss;
 use values::generics::rect::Rect;
 
 /// A generic value for a single side of a `border-image-width` property.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)]
+#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)]
 pub enum BorderImageSideWidth<LengthOrPercentage, Number> {
     /// `<length-or-percentage>`
     Length(LengthOrPercentage),
     /// `<number>`
     Number(Number),
     /// `auto`
     Auto,
 }
@@ -47,30 +47,16 @@ pub struct BorderRadius<LengthOrPercenta
     pub bottom_left: BorderCornerRadius<LengthOrPercentage>,
 }
 
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)]
 /// A generic value for `border-*-radius` longhand properties.
 pub struct BorderCornerRadius<L>(pub Size2D<L>);
 
-impl<L, N> ToCss for BorderImageSideWidth<L, N>
-    where L: ToCss, N: ToCss,
-{
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-        where W: fmt::Write
-    {
-        match *self {
-            BorderImageSideWidth::Length(ref length) => length.to_css(dest),
-            BorderImageSideWidth::Number(ref number) => number.to_css(dest),
-            BorderImageSideWidth::Auto => dest.write_str("auto"),
-        }
-    }
-}
-
 impl<N> From<N> for BorderImageSlice<N>
     where N: Clone,
 {
     #[inline]
     fn from(value: N) -> Self {
         Self {
             offsets: value.into(),
             fill: false,
--- a/servo/components/style/values/generics/grid.rs
+++ b/servo/components/style/values/generics/grid.rs
@@ -324,37 +324,27 @@ pub fn concat_serialize_idents<W>(prefix
     }
 
     Ok(())
 }
 
 /// The initial argument of the `repeat` function.
 ///
 /// https://drafts.csswg.org/css-grid/#typedef-track-repeat
-#[derive(Clone, Copy, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Copy, Debug, PartialEq, ToCss)]
 pub enum RepeatCount {
     /// A positive integer. This is allowed only for `<track-repeat>` and `<fixed-repeat>`
     Number(Integer),
     /// An `<auto-fill>` keyword allowed only for `<auto-repeat>`
     AutoFill,
     /// An `<auto-fit>` keyword allowed only for `<auto-repeat>`
     AutoFit,
 }
 
-impl ToCss for RepeatCount {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            RepeatCount::Number(ref c) => c.to_css(dest),
-            RepeatCount::AutoFill => dest.write_str("auto-fill"),
-            RepeatCount::AutoFit => dest.write_str("auto-fit"),
-        }
-    }
-}
-
 impl Parse for RepeatCount {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         if let Ok(i) = input.try(|i| Integer::parse(context, i)) {
             if i.value() > 0 {
                 Ok(RepeatCount::Number(i))
             } else {
                 Err(())
             }
--- a/servo/components/style/values/generics/mod.rs
+++ b/servo/components/style/values/generics/mod.rs
@@ -177,34 +177,25 @@ impl<T: Parse> Parse for FontSettingTag<
         let u_tag = raw.read_u32::<BigEndian>().unwrap();
 
         Ok(FontSettingTag { tag: u_tag, value: T::parse(context, input)? })
     }
 }
 
 
 /// A font settings value for font-variation-settings or font-feature-settings
-#[derive(Clone, Debug, Eq, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Debug, Eq, PartialEq, ToCss)]
 pub enum FontSettings<T> {
     /// No settings (default)
     Normal,
     /// Set of settings
     Tag(Vec<FontSettingTag<T>>)
 }
 
-impl<T: ToCss> ToCss for FontSettings<T> {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            FontSettings::Normal => dest.write_str("normal"),
-            FontSettings::Tag(ref ftvs) => ftvs.to_css(dest)
-        }
-    }
-}
-
 impl<T: Parse> Parse for FontSettings<T> {
     /// https://www.w3.org/TR/css-fonts-3/#propdef-font-feature-settings
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         if input.try(|i| i.expect_ident_matching("normal")).is_ok() {
             return Ok(FontSettings::Normal);
         }
         Vec::parse(context, input).map(FontSettings::Tag)
     }
@@ -285,18 +276,18 @@ pub struct SVGPaint<ColorType> {
     pub fallback: Option<ColorType>,
 }
 
 /// An SVG paint value without the fallback
 ///
 /// Whereas the spec only allows PaintServer
 /// to have a fallback, Gecko lets the context
 /// properties have a fallback as well.
-#[derive(Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Debug, PartialEq, ToCss)]
 pub enum SVGPaintKind<ColorType> {
     /// `none`
     None,
     /// `<color>`
     Color(ColorType),
     /// `url(...)`
     PaintServer(SpecifiedUrl),
     /// `context-fill`
@@ -373,28 +364,16 @@ impl<ColorType: Parse> Parse for SVGPain
                 fallback: None,
             })
         } else {
             Err(())
         }
     }
 }
 
-impl<ColorType: ToCss> ToCss for SVGPaintKind<ColorType> {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            SVGPaintKind::None => dest.write_str("none"),
-            SVGPaintKind::ContextStroke => dest.write_str("context-stroke"),
-            SVGPaintKind::ContextFill => dest.write_str("context-fill"),
-            SVGPaintKind::Color(ref color) => color.to_css(dest),
-            SVGPaintKind::PaintServer(ref server) => server.to_css(dest),
-        }
-    }
-}
-
 impl<ColorType: ToCss> ToCss for SVGPaint<ColorType> {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         self.kind.to_css(dest)?;
         if let Some(ref fallback) = self.fallback {
             fallback.to_css(dest)?;
         }
         Ok(())
     }
--- a/servo/components/style/values/generics/text.rs
+++ b/servo/components/style/values/generics/text.rs
@@ -6,19 +6,60 @@
 
 use app_units::Au;
 use cssparser::Parser;
 use parser::ParserContext;
 use properties::animated_properties::Animatable;
 use std::fmt;
 use style_traits::ToCss;
 
-/// A generic spacing value for the `letter-spacing` and `word-spacing` properties.alloc
+/// A generic value for the `initial-letter` property.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)]
+pub enum InitialLetter<Number, Integer> {
+    /// `normal`
+    Normal,
+    /// `<number> <integer>?`
+    Specified(Number, Option<Integer>),
+}
+
+impl<N, I> InitialLetter<N, I> {
+    /// Returns `normal`.
+    #[inline]
+    pub fn normal() -> Self {
+        InitialLetter::Normal
+    }
+}
+
+impl<N, I> ToCss for InitialLetter<N, I>
+where
+    N: ToCss,
+    I: ToCss,
+{
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+    where
+        W: fmt::Write,
+    {
+        match *self {
+            InitialLetter::Normal => dest.write_str("normal"),
+            InitialLetter::Specified(ref size, ref sink) => {
+                size.to_css(dest)?;
+                if let Some(ref sink) = *sink {
+                    dest.write_str(" ")?;
+                    sink.to_css(dest)?;
+                }
+                Ok(())
+            },
+        }
+    }
+}
+
+/// A generic spacing value for the `letter-spacing` and `word-spacing` properties.
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)]
 pub enum Spacing<Value> {
     /// `normal`
     Normal,
     /// `<value>`
     Value(Value),
 }
 
 impl<Value> Spacing<Value> {
@@ -71,32 +112,19 @@ impl<Value> Animatable for Spacing<Value
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         let zero = Value::from(Au(0));
         let this = self.value().unwrap_or(&zero);
         let other = other.value().unwrap_or(&zero);
         this.compute_distance(other)
     }
 }
 
-impl<Value> ToCss for Spacing<Value>
-    where Value: ToCss,
-{
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-        where W: fmt::Write
-    {
-        match *self {
-            Spacing::Normal => dest.write_str("normal"),
-            Spacing::Value(ref value) => value.to_css(dest),
-        }
-    }
-}
-
 /// A generic value for the `line-height` property.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq)]
+#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToCss)]
 pub enum LineHeight<Number, LengthOrPercentage> {
     /// `normal`
     Normal,
     /// `-moz-block-height`
     #[cfg(feature = "gecko")]
     MozBlockHeight,
     /// `<number>`
     Number(Number),
@@ -106,24 +134,8 @@ pub enum LineHeight<Number, LengthOrPerc
 
 impl<N, L> LineHeight<N, L> {
     /// Returns `normal`.
     #[inline]
     pub fn normal() -> Self {
         LineHeight::Normal
     }
 }
-
-impl<N, L> ToCss for LineHeight<N, L>
-    where N: ToCss, L: ToCss,
-{
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-        where W: fmt::Write,
-    {
-        match *self {
-            LineHeight::Normal => dest.write_str("normal"),
-            #[cfg(feature = "gecko")]
-            LineHeight::MozBlockHeight => dest.write_str("-moz-block-height"),
-            LineHeight::Number(ref number) => number.to_css(dest),
-            LineHeight::Length(ref value) => value.to_css(dest),
-        }
-    }
-}
--- a/servo/components/style/values/specified/border.rs
+++ b/servo/components/style/values/specified/border.rs
@@ -2,30 +2,28 @@
  * 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/. */
 
 //! Specified types for CSS values related to borders.
 
 use app_units::Au;
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
-use std::fmt;
-use style_traits::ToCss;
 use values::computed::{Context, ToComputedValue};
 use values::generics::border::BorderCornerRadius as GenericBorderCornerRadius;
 use values::generics::border::BorderImageSideWidth as GenericBorderImageSideWidth;
 use values::generics::border::BorderImageSlice as GenericBorderImageSlice;
 use values::generics::border::BorderRadius as GenericBorderRadius;
 use values::generics::rect::Rect;
 use values::specified::{AllowQuirks, Number, NumberOrPercentage};
 use values::specified::length::{Length, LengthOrPercentage};
 
 /// A specified value for a single side of the `border-width` property.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
+#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
 pub enum BorderSideWidth {
     /// `thin`
     Thin,
     /// `medium`
     Medium,
     /// `thick`
     Thick,
     /// `<length>`
@@ -68,27 +66,16 @@ impl BorderSideWidth {
 }
 
 impl Parse for BorderSideWidth {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         Self::parse_quirky(context, input, AllowQuirks::No)
     }
 }
 
-impl ToCss for BorderSideWidth {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            BorderSideWidth::Thin => dest.write_str("thin"),
-            BorderSideWidth::Medium => dest.write_str("medium"),
-            BorderSideWidth::Thick => dest.write_str("thick"),
-            BorderSideWidth::Length(ref length) => length.to_css(dest)
-        }
-    }
-}
-
 impl ToComputedValue for BorderSideWidth {
     type ComputedValue = Au;
 
     #[inline]
     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
         // We choose the pixel length of the keyword values the same as both spec and gecko.
         // Spec: https://drafts.csswg.org/css-backgrounds-3/#line-width
         // Gecko: https://bugzilla.mozilla.org/show_bug.cgi?id=1312155#c0
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -889,19 +889,19 @@ impl LengthOrPercentage {
     pub fn parse_quirky(context: &ParserContext,
                         input: &mut Parser,
                         allow_quirks: AllowQuirks) -> Result<Self, ()> {
         Self::parse_internal(context, input, AllowedLengthType::All, allow_quirks)
     }
 }
 
 /// Either a `<length>`, a `<percentage>`, or the `auto` keyword.
-#[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
+#[allow(missing_docs)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
+#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
 pub enum LengthOrPercentageOrAuto {
     Length(NoCalcLength),
     Percentage(Percentage),
     Auto,
     Calc(Box<CalcLengthOrPercentage>),
 }
 
 impl From<NoCalcLength> for LengthOrPercentageOrAuto {
@@ -913,27 +913,16 @@ impl From<NoCalcLength> for LengthOrPerc
 
 impl From<Percentage> for LengthOrPercentageOrAuto {
     #[inline]
     fn from(pc: Percentage) -> Self {
         LengthOrPercentageOrAuto::Percentage(pc)
     }
 }
 
-impl ToCss for LengthOrPercentageOrAuto {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            LengthOrPercentageOrAuto::Length(ref length) => length.to_css(dest),
-            LengthOrPercentageOrAuto::Percentage(percentage) => percentage.to_css(dest),
-            LengthOrPercentageOrAuto::Auto => dest.write_str("auto"),
-            LengthOrPercentageOrAuto::Calc(ref calc) => calc.to_css(dest),
-        }
-    }
-}
-
 impl LengthOrPercentageOrAuto {
     fn parse_internal(context: &ParserContext,
                       input: &mut Parser,
                       num_context: AllowedLengthType,
                       allow_quirks: AllowQuirks)
                       -> Result<Self, ()> {
         match try!(input.next()) {
             Token::Dimension(ref value, ref unit) if num_context.is_ok(value.value) =>
@@ -1007,36 +996,26 @@ impl LengthOrPercentageOrAuto {
                         input: &mut Parser,
                         allow_quirks: AllowQuirks)
                         -> Result<Self, ()> {
         Self::parse_internal(context, input, AllowedLengthType::All, allow_quirks)
     }
 }
 
 /// Either a `<length>`, a `<percentage>`, or the `none` keyword.
-#[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
 #[allow(missing_docs)]
 pub enum LengthOrPercentageOrNone {
     Length(NoCalcLength),
     Percentage(Percentage),
     Calc(Box<CalcLengthOrPercentage>),
     None,
 }
 
-impl ToCss for LengthOrPercentageOrNone {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            LengthOrPercentageOrNone::Length(ref length) => length.to_css(dest),
-            LengthOrPercentageOrNone::Percentage(ref percentage) => percentage.to_css(dest),
-            LengthOrPercentageOrNone::Calc(ref calc) => calc.to_css(dest),
-            LengthOrPercentageOrNone::None => dest.write_str("none"),
-        }
-    }
-}
 impl LengthOrPercentageOrNone {
     fn parse_internal(context: &ParserContext,
                       input: &mut Parser,
                       num_context: AllowedLengthType,
                       allow_quirks: AllowQuirks)
                       -> Result<LengthOrPercentageOrNone, ()>
     {
         match try!(input.next()) {
@@ -1094,18 +1073,18 @@ pub type LengthOrNone = Either<Length, N
 /// Either a `<length>` or the `normal` keyword.
 pub type LengthOrNormal = Either<Length, Normal>;
 
 /// Either a `<length>` or the `auto` keyword.
 pub type LengthOrAuto = Either<Length, Auto>;
 
 /// Either a `<length>` or a `<percentage>` or the `auto` keyword or the
 /// `content` keyword.
-#[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
 pub enum LengthOrPercentageOrAutoOrContent {
     /// A `<length>`.
     Length(NoCalcLength),
     /// A percentage.
     Percentage(Percentage),
     /// A `calc` node.
     Calc(Box<CalcLengthOrPercentage>),
     /// The `auto` keyword.
@@ -1151,28 +1130,16 @@ impl LengthOrPercentageOrAutoOrContent {
     }
 
     /// Returns a value representing `0%`.
     pub fn zero_percent() -> Self {
         LengthOrPercentageOrAutoOrContent::Percentage(Percentage::zero())
     }
 }
 
-impl ToCss for LengthOrPercentageOrAutoOrContent {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            LengthOrPercentageOrAutoOrContent::Length(ref len) => len.to_css(dest),
-            LengthOrPercentageOrAutoOrContent::Percentage(perc) => perc.to_css(dest),
-            LengthOrPercentageOrAutoOrContent::Auto => dest.write_str("auto"),
-            LengthOrPercentageOrAutoOrContent::Content => dest.write_str("content"),
-            LengthOrPercentageOrAutoOrContent::Calc(ref calc) => calc.to_css(dest),
-        }
-    }
-}
-
 /// Either a `<length>` or a `<number>`.
 pub type LengthOrNumber = Either<Length, Number>;
 
 impl LengthOrNumber {
     /// Parse a non-negative LengthOrNumber.
     pub fn parse_non_negative(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         // We try to parse as a Number first because, for cases like
         // LengthOrNumber, we want "0" to be parsed as a plain Number rather
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -40,17 +40,17 @@ pub use self::gecko::ScrollSnapPoint;
 pub use self::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use self::image::{GradientItem, GradientKind, Image, ImageRect, ImageLayer};
 pub use self::length::AbsoluteLength;
 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};
 pub use self::length::{MaxLength, MozLength};
 pub use self::position::{Position, PositionComponent};
-pub use self::text::{LetterSpacing, LineHeight, WordSpacing};
+pub use self::text::{InitialLetter, LetterSpacing, LineHeight, WordSpacing};
 pub use self::transform::{TimingFunction, TransformOrigin};
 pub use super::generics::grid::GridLine;
 
 #[cfg(feature = "gecko")]
 pub mod align;
 pub mod background;
 pub mod basic_shape;
 pub mod border;
--- a/servo/components/style/values/specified/text.rs
+++ b/servo/components/style/values/specified/text.rs
@@ -4,29 +4,45 @@
 
 //! Specified types for text properties.
 
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use std::ascii::AsciiExt;
 use values::computed::{Context, ToComputedValue};
 use values::computed::text::LineHeight as ComputedLineHeight;
-use values::generics::text::{LineHeight as GenericLineHeight, Spacing};
-use values::specified::{AllowQuirks, Number};
+use values::generics::text::InitialLetter as GenericInitialLetter;
+use values::generics::text::LineHeight as GenericLineHeight;
+use values::generics::text::Spacing;
+use values::specified::{AllowQuirks, Integer, Number};
 use values::specified::length::{FontRelativeLength, Length, LengthOrPercentage, NoCalcLength};
 
+/// A specified type for the `initial-letter` property.
+pub type InitialLetter = GenericInitialLetter<Number, Integer>;
+
 /// A specified value for the `letter-spacing` property.
 pub type LetterSpacing = Spacing<Length>;
 
 /// A specified value for the `word-spacing` property.
 pub type WordSpacing = Spacing<LengthOrPercentage>;
 
 /// A specified value for the `line-height` property.
 pub type LineHeight = GenericLineHeight<Number, LengthOrPercentage>;
 
+impl Parse for InitialLetter {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        if input.try(|i| i.expect_ident_matching("normal")).is_ok() {
+            return Ok(GenericInitialLetter::Normal);
+        }
+        let size = Number::parse_at_least_one(context, input)?;
+        let sink = input.try(|i| Integer::parse_positive(context, i)).ok();
+        Ok(GenericInitialLetter::Specified(size, sink))
+    }
+}
+
 impl Parse for LetterSpacing {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         Spacing::parse_with(context, input, |c, i| {
             Length::parse_quirky(c, i, AllowQuirks::Yes)
         })
     }
 }
 
--- a/servo/components/style/values/specified/transform.rs
+++ b/servo/components/style/values/specified/transform.rs
@@ -2,32 +2,30 @@
  * 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/. */
 
 //! Specified types for CSS values that are related to transformations.
 
 use cssparser::Parser;
 use euclid::Point2D;
 use parser::{Parse, ParserContext};
-use std::fmt;
-use style_traits::ToCss;
 use values::computed::{LengthOrPercentage as ComputedLengthOrPercentage, Context, ToComputedValue};
 use values::computed::transform::TimingFunction as ComputedTimingFunction;
 use values::generics::transform::{StepPosition, TimingFunction as GenericTimingFunction};
 use values::generics::transform::{TimingKeyword, TransformOrigin as GenericTransformOrigin};
 use values::specified::{Integer, Number};
 use values::specified::length::{Length, LengthOrPercentage};
 use values::specified::position::{Side, X, Y};
 
 /// The specified value of a CSS `<transform-origin>`
 pub type TransformOrigin = GenericTransformOrigin<OriginComponent<X>, OriginComponent<Y>, Length>;
 
 /// The specified value of a component of a CSS `<transform-origin>`.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
+#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
 pub enum OriginComponent<S> {
     /// `center`
     Center,
     /// `<lop>`
     Length(LengthOrPercentage),
     /// `<side>`
     Side(S),
 }
@@ -94,30 +92,16 @@ impl<S> Parse for OriginComponent<S>
         if let Ok(lop) = input.try(|i| LengthOrPercentage::parse(context, i)) {
             return Ok(OriginComponent::Length(lop));
         }
         let keyword = S::parse(context, input)?;
         Ok(OriginComponent::Side(keyword))
     }
 }
 
-impl<S: ToCss> ToCss for OriginComponent<S>
-    where S: ToCss,
-{
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-        where W: fmt::Write,
-    {
-        match *self {
-            OriginComponent::Center => dest.write_str("center"),
-            OriginComponent::Length(ref lop) => lop.to_css(dest),
-            OriginComponent::Side(ref keyword) => keyword.to_css(dest),
-        }
-    }
-}
-
 impl<S> ToComputedValue for OriginComponent<S>
     where S: Side,
 {
     type ComputedValue = ComputedLengthOrPercentage;
 
     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
         match *self {
             OriginComponent::Center => {
--- a/servo/components/style_derive/to_css.rs
+++ b/servo/components/style_derive/to_css.rs
@@ -10,30 +10,33 @@ pub fn derive(input: syn::DeriveInput) -
     let name = &input.ident;
     let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
     let mut where_clause = where_clause.clone();
     for param in &input.generics.ty_params {
         where_clause.predicates.push(where_predicate(syn::Ty::Path(None, param.ident.clone().into())))
     }
 
     let style = synstructure::BindStyle::Ref.into();
-    let match_body = synstructure::each_variant(&input, &style, |bindings, _| {
+    let match_body = synstructure::each_variant(&input, &style, |bindings, variant| {
         if bindings.is_empty() {
-            panic!("unit variants are not yet supported");
+            let identifier = to_css_identifier(variant.ident.as_ref());
+            return Some(quote! {
+                ::std::fmt::Write::write_str(dest, #identifier)
+            });
         }
         let (first, rest) = bindings.split_first().expect("unit variants are not yet supported");
         where_clause.predicates.push(where_predicate(first.field.ty.clone()));
         let mut expr = quote! {
             ::style_traits::ToCss::to_css(#first, dest)
         };
         for binding in rest {
             where_clause.predicates.push(where_predicate(binding.field.ty.clone()));
             expr = quote! {
                 #expr?;
-                dest.write_str(" ")?;
+                ::std::fmt::Write::write_str(dest, " ")?;
                 ::style_traits::ToCss::to_css(#binding, dest)
             };
         }
         Some(expr)
     });
 
     quote! {
         impl #impl_generics ::style_traits::ToCss for #name #ty_generics #where_clause {
@@ -63,8 +66,45 @@ fn where_predicate(ty: syn::Ty) -> syn::
                     global: true,
                     segments: vec!["style_traits".into(), "ToCss".into()],
                 },
             },
             syn::TraitBoundModifier::None
         )],
     })
 }
+
+/// Transforms "FooBar" to "foo-bar".
+///
+/// If the first Camel segment is "Moz"" or "Webkit", the result string
+/// is prepended with "-".
+fn to_css_identifier(mut camel_case: &str) -> String {
+    let mut first = true;
+    let mut result = String::with_capacity(camel_case.len());
+    while let Some(segment) = split_camel_segment(&mut camel_case) {
+        if first {
+            match segment {
+                "Moz" | "Webkit" => first = false,
+                _ => {},
+            }
+        }
+        if !first {
+            result.push_str("-");
+        }
+        first = false;
+        result.push_str(&segment.to_lowercase());
+    }
+    result
+}
+
+/// Given "FooBar", returns "Foo" and sets `camel_case` to "Bar".
+fn split_camel_segment<'input>(camel_case: &mut &'input str) -> Option<&'input str> {
+    let index = match camel_case.chars().next() {
+        None => return None,
+        Some(ch) => ch.len_utf8(),
+    };
+    let end_position = camel_case[index..]
+        .find(char::is_uppercase)
+        .map_or(camel_case.len(), |pos| index + pos);
+    let result = &camel_case[..end_position];
+    *camel_case = &camel_case[end_position..];
+    Some(result)
+}
--- a/servo/components/style_traits/values.rs
+++ b/servo/components/style_traits/values.rs
@@ -3,18 +3,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Helper types and traits for the handling of CSS values.
 
 use app_units::Au;
 use cssparser::UnicodeRange;
 use std::fmt;
 
-/// The real `ToCss` trait can't be implemented for types in crates that don't
-/// depend on each other.
+/// Serialises a value according to its CSS representation.
+///
+/// This trait is derivable with `#[derive(ToCss)]`, with the following behaviour:
+/// * unit variants get serialised as the `snake-case` representation
+///   of their name;
+/// * unit variants whose name starts with "Moz" or "Webkit" are prepended
+///   with a "-";
+/// * variants with fields get serialised as the space-separated serialisations
+///   of their fields.
 pub trait ToCss {
     /// Serialize `self` in CSS syntax, writing to `dest`.
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write;
 
     /// Serialize `self` in CSS syntax and return a string.
     ///
     /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
     #[inline]