servo: Merge #17122 - Continue to slim down the code for CSS properties (from servo:derive-all-the-things); r=emilio
authorAnthony Ramine <n.oxyde@gmail.com>
Fri, 02 Jun 2017 01:53:55 -0700
changeset 410169 0563db4622e5652cca07986ac9369d691278d0d6
parent 410168 bb5fc68d7f713ca944ab2d85a66269405323963a
child 410170 d3888af691ae559bce34f856075e6be7f36111b5
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
bugs17122
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 #17122 - Continue to slim down the code for CSS properties (from servo:derive-all-the-things); r=emilio Source-Repo: https://github.com/servo/servo Source-Revision: 028908ee269a680464d079ba4f6e9e8ef5b41749
servo/components/layout/text.rs
servo/components/script/dom/element.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/border.mako.rs
servo/components/style/properties/longhand/column.mako.rs
servo/components/style/properties/longhand/inherited_text.mako.rs
servo/components/style/properties/longhand/outline.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/properties/shorthand/border.mako.rs
servo/components/style/properties/shorthand/font.mako.rs
servo/components/style/values/computed/border.rs
servo/components/style/values/computed/length.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/computed/text.rs
servo/components/style/values/generics/border.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/mod.rs
servo/components/style/values/specified/text.rs
servo/ports/geckolib/glue.rs
servo/tests/unit/style/parsing/inherited_text.rs
servo/tests/unit/style/properties/serialization.rs
servo/tests/unit/style/properties/viewport.rs
--- a/servo/components/layout/text.rs
+++ b/servo/components/layout/text.rs
@@ -18,21 +18,22 @@ use gfx::text::util::{self, CompressionM
 use inline::{FIRST_FRAGMENT_OF_ELEMENT, InlineFragments, LAST_FRAGMENT_OF_ELEMENT};
 use linked_list::split_off_head;
 use ordered_float::NotNaN;
 use range::Range;
 use std::borrow::ToOwned;
 use std::collections::LinkedList;
 use std::mem;
 use std::sync::Arc;
-use style::computed_values::{line_height, text_rendering, text_transform};
+use style::computed_values::{text_rendering, text_transform};
 use style::computed_values::{word_break, white_space};
 use style::logical_geometry::{LogicalSize, WritingMode};
 use style::properties::ServoComputedValues;
 use style::properties::style_structs;
+use style::values::generics::text::LineHeight;
 use unicode_bidi as bidi;
 use unicode_script::{Script, get_script};
 
 /// Returns the concatenated text of a list of unscanned text fragments.
 fn text(fragments: &LinkedList<Fragment>) -> String {
     // FIXME: Some of this work is later duplicated in split_first_fragment_at_newline_if_necessary
     // and transform_text.  This code should be refactored so that the all the scanning for
     // newlines is done in a single pass.
@@ -160,18 +161,18 @@ impl TextRunScanner {
                 compression = match in_fragment.white_space() {
                     white_space::T::normal |
                     white_space::T::nowrap => CompressionMode::CompressWhitespaceNewline,
                     white_space::T::pre |
                     white_space::T::pre_wrap => CompressionMode::CompressNone,
                     white_space::T::pre_line => CompressionMode::CompressWhitespace,
                 };
                 text_transform = inherited_text_style.text_transform;
-                letter_spacing = inherited_text_style.letter_spacing.0;
-                word_spacing = inherited_text_style.word_spacing.0
+                letter_spacing = inherited_text_style.letter_spacing;
+                word_spacing = inherited_text_style.word_spacing.value()
                                .map(|lop| lop.to_hash_key())
                                .unwrap_or((Au(0), NotNaN::new(0.0).unwrap()));
                 text_rendering = inherited_text_style.text_rendering;
                 word_break = inherited_text_style.word_break;
             }
 
             // First, transform/compress text of all the nodes.
             let (mut run_info_list, mut run_info) = (Vec::new(), RunInfo::new());
@@ -283,29 +284,29 @@ impl TextRunScanner {
             // Push the final run info.
             run_info.flush(&mut run_info_list, &mut insertion_point);
 
             // Per CSS 2.1 § 16.4, "when the resultant space between two characters is not the same
             // as the default space, user agents should not use ligatures." This ensures that, for
             // example, `finally` with a wide `letter-spacing` renders as `f i n a l l y` and not
             // `fi n a l l y`.
             let mut flags = ShapingFlags::empty();
-            match letter_spacing {
-                Some(Au(0)) | None => {}
+            match letter_spacing.value() {
+                Some(&Au(0)) | None => {}
                 Some(_) => flags.insert(IGNORE_LIGATURES_SHAPING_FLAG),
             }
             if text_rendering == text_rendering::T::optimizespeed {
                 flags.insert(IGNORE_LIGATURES_SHAPING_FLAG);
                 flags.insert(DISABLE_KERNING_SHAPING_FLAG)
             }
             if word_break == word_break::T::keep_all {
                 flags.insert(KEEP_ALL_FLAG);
             }
             let options = ShapingOptions {
-                letter_spacing: letter_spacing,
+                letter_spacing: letter_spacing.value().cloned(),
                 word_spacing: word_spacing,
                 script: Script::Common,
                 flags: flags,
             };
 
             // FIXME(https://github.com/rust-lang/rust/issues/23338)
             run_info_list.into_iter().map(|run_info| {
                 let mut options = options;
@@ -442,19 +443,19 @@ pub fn font_metrics_for_style(font_conte
     let font = fontgroup.fonts[0].borrow();
     font.metrics.clone()
 }
 
 /// Returns the line block-size needed by the given computed style and font size.
 pub fn line_height_from_style(style: &ServoComputedValues, metrics: &FontMetrics) -> Au {
     let font_size = style.get_font().font_size;
     match style.get_inheritedtext().line_height {
-        line_height::T::Normal => metrics.line_gap,
-        line_height::T::Number(l) => font_size.scale_by(l),
-        line_height::T::Length(l) => l
+        LineHeight::Normal => metrics.line_gap,
+        LineHeight::Number(l) => font_size.scale_by(l),
+        LineHeight::Length(l) => l
     }
 }
 
 fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragment>) {
     if fragments.is_empty() {
         return
     }
 
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -641,17 +641,17 @@ impl LayoutElementHelpers for LayoutJS<E
 
         let border = if let Some(this) = self.downcast::<HTMLTableElement>() {
             this.get_border()
         } else {
             None
         };
 
         if let Some(border) = border {
-            let width_value = specified::BorderWidth::from_length(specified::Length::from_px(border as f32));
+            let width_value = specified::BorderSideWidth::Length(specified::Length::from_px(border as f32));
             hints.push(from_declaration(
                 shared_lock,
                 PropertyDeclaration::BorderTopWidth(width_value.clone())));
             hints.push(from_declaration(
                 shared_lock,
                 PropertyDeclaration::BorderLeftWidth(width_value.clone())));
             hints.push(from_declaration(
                 shared_lock,
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -993,27 +993,27 @@ fn static_assert() {
     }
 
     pub fn copy_border_image_repeat_from(&mut self, other: &Self) {
         self.gecko.mBorderImageRepeatH = other.gecko.mBorderImageRepeatH;
         self.gecko.mBorderImageRepeatV = other.gecko.mBorderImageRepeatV;
     }
 
     pub fn set_border_image_width(&mut self, v: longhands::border_image_width::computed_value::T) {
-        use values::generics::border::BorderImageWidthSide;
+        use values::generics::border::BorderImageSideWidth;
 
         % for side in SIDES:
         match v.${side.index} {
-            BorderImageWidthSide::Auto => {
+            BorderImageSideWidth::Auto => {
                 self.gecko.mBorderImageWidth.data_at_mut(${side.index}).set_value(CoordDataValue::Auto)
             },
-            BorderImageWidthSide::Length(l) => {
+            BorderImageSideWidth::Length(l) => {
                 l.to_gecko_style_coord(&mut self.gecko.mBorderImageWidth.data_at_mut(${side.index}))
             },
-            BorderImageWidthSide::Number(n) => {
+            BorderImageSideWidth::Number(n) => {
                 self.gecko.mBorderImageWidth.data_at_mut(${side.index}).set_value(CoordDataValue::Factor(n))
             },
         }
         % endfor
     }
 
     pub fn copy_border_image_width_from(&mut self, other: &Self) {
         % for side in SIDES:
@@ -3632,83 +3632,82 @@ fn static_assert() {
                 color: Color::RGBA(convert_nscolor_to_rgba(shadow.mColor)),
             }
 
         }).collect();
         longhands::text_shadow::computed_value::T(buf)
     }
 
     pub fn set_line_height(&mut self, v: longhands::line_height::computed_value::T) {
-        use properties::longhands::line_height::computed_value::T;
+        use values::generics::text::LineHeight;
         // FIXME: Align binary representations and ditch |match| for cast + static_asserts
         let en = match v {
-            T::Normal => CoordDataValue::Normal,
-            T::Length(val) => CoordDataValue::Coord(val.0),
-            T::Number(val) => CoordDataValue::Factor(val),
-            T::MozBlockHeight =>
+            LineHeight::Normal => CoordDataValue::Normal,
+            LineHeight::Length(val) => CoordDataValue::Coord(val.0),
+            LineHeight::Number(val) => CoordDataValue::Factor(val),
+            LineHeight::MozBlockHeight =>
                     CoordDataValue::Enumerated(structs::NS_STYLE_LINE_HEIGHT_BLOCK_HEIGHT),
         };
         self.gecko.mLineHeight.set_value(en);
     }
 
     pub fn clone_line_height(&self) -> longhands::line_height::computed_value::T {
-        use properties::longhands::line_height::computed_value::T;
+        use values::generics::text::LineHeight;
         return match self.gecko.mLineHeight.as_value() {
-            CoordDataValue::Normal => T::Normal,
-            CoordDataValue::Coord(coord) => T::Length(Au(coord)),
-            CoordDataValue::Factor(n) => T::Number(n),
+            CoordDataValue::Normal => LineHeight::Normal,
+            CoordDataValue::Coord(coord) => LineHeight::Length(Au(coord)),
+            CoordDataValue::Factor(n) => LineHeight::Number(n),
             CoordDataValue::Enumerated(val) if val == structs::NS_STYLE_LINE_HEIGHT_BLOCK_HEIGHT =>
-                T::MozBlockHeight,
-            _ => {
-                debug_assert!(false);
-                T::MozBlockHeight
-            }
+                LineHeight::MozBlockHeight,
+            _ => panic!("this should not happen"),
         }
     }
 
     <%call expr="impl_coord_copy('line_height', 'mLineHeight')"></%call>
 
     pub fn set_letter_spacing(&mut self, v: longhands::letter_spacing::computed_value::T) {
-        match v.0 {
-            Some(au) => self.gecko.mLetterSpacing.set(au),
-            None => self.gecko.mLetterSpacing.set_value(CoordDataValue::Normal)
+        use values::generics::text::Spacing;
+        match v {
+            Spacing::Value(value) => self.gecko.mLetterSpacing.set(value),
+            Spacing::Normal => self.gecko.mLetterSpacing.set_value(CoordDataValue::Normal)
         }
     }
 
     pub fn clone_letter_spacing(&self) -> longhands::letter_spacing::computed_value::T {
-        use properties::longhands::letter_spacing::computed_value::T;
+        use values::generics::text::Spacing;
         debug_assert!(
             matches!(self.gecko.mLetterSpacing.as_value(),
                      CoordDataValue::Normal |
                      CoordDataValue::Coord(_)),
             "Unexpected computed value for letter-spacing");
-        T(Au::from_gecko_style_coord(&self.gecko.mLetterSpacing))
+        Au::from_gecko_style_coord(&self.gecko.mLetterSpacing).map_or(Spacing::Normal, Spacing::Value)
     }
 
     <%call expr="impl_coord_copy('letter_spacing', 'mLetterSpacing')"></%call>
 
     pub fn set_word_spacing(&mut self, v: longhands::word_spacing::computed_value::T) {
-        match v.0 {
-            Some(lop) => self.gecko.mWordSpacing.set(lop),
+        use values::generics::text::Spacing;
+        match v {
+            Spacing::Value(lop) => self.gecko.mWordSpacing.set(lop),
             // https://drafts.csswg.org/css-text-3/#valdef-word-spacing-normal
-            None => self.gecko.mWordSpacing.set_value(CoordDataValue::Coord(0)),
+            Spacing::Normal => self.gecko.mWordSpacing.set_value(CoordDataValue::Coord(0)),
         }
     }
 
     pub fn clone_word_spacing(&self) -> longhands::word_spacing::computed_value::T {
-        use properties::longhands::word_spacing::computed_value::T;
         use values::computed::LengthOrPercentage;
+        use values::generics::text::Spacing;
         debug_assert!(
             matches!(self.gecko.mWordSpacing.as_value(),
                      CoordDataValue::Normal |
                      CoordDataValue::Coord(_) |
                      CoordDataValue::Percent(_) |
                      CoordDataValue::Calc(_)),
             "Unexpected computed value for word-spacing");
-        T(LengthOrPercentage::from_gecko_style_coord(&self.gecko.mWordSpacing))
+        LengthOrPercentage::from_gecko_style_coord(&self.gecko.mWordSpacing).map_or(Spacing::Normal, Spacing::Value)
     }
 
     <%call expr="impl_coord_copy('word_spacing', 'mWordSpacing')"></%call>
 
     fn clear_text_emphasis_style_if_string(&mut self) {
         use nsstring::nsString;
         if self.gecko.mTextEmphasisStyle == structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING as u8 {
             self.gecko.mTextEmphasisStyleString.assign(&nsString::new());
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -13,17 +13,16 @@ use euclid::{Point2D, Size2D};
 #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID;
 #[cfg(feature = "gecko")] use gecko_bindings::sugar::ownership::{HasFFI, HasSimpleFFI};
 #[cfg(feature = "gecko")] use gecko_string_cache::Atom;
 use properties::{CSSWideKeyword, PropertyDeclaration};
 use properties::longhands;
 use properties::longhands::background_size::computed_value::T as BackgroundSizeList;
 use properties::longhands::font_weight::computed_value::T as FontWeight;
 use properties::longhands::font_stretch::computed_value::T as FontStretch;
-use properties::longhands::line_height::computed_value::T as LineHeight;
 use properties::longhands::text_shadow::computed_value::T as TextShadowList;
 use properties::longhands::text_shadow::computed_value::TextShadow;
 use properties::longhands::box_shadow::computed_value::T as BoxShadowList;
 use properties::longhands::box_shadow::single_value::computed_value::T as BoxShadow;
 use properties::longhands::transform::computed_value::ComputedMatrix;
 use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation;
 use properties::longhands::transform::computed_value::T as TransformList;
 use properties::longhands::vertical_align::computed_value::T as VerticalAlign;
@@ -1295,53 +1294,16 @@ impl Animatable for MaxLength {
              MaxLength::LengthOrPercentageOrNone(ref other)) => {
                 this.compute_distance(other)
             },
             _ => Err(()),
         }
     }
 }
 
-/// https://drafts.csswg.org/css-transitions/#animtype-number
-/// https://drafts.csswg.org/css-transitions/#animtype-length
-impl Animatable for LineHeight {
-    #[inline]
-    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
-        match (*self, *other) {
-            (LineHeight::Length(ref this),
-             LineHeight::Length(ref other)) => {
-                this.add_weighted(other, self_portion, other_portion).map(LineHeight::Length)
-            }
-            (LineHeight::Number(ref this),
-             LineHeight::Number(ref other)) => {
-                this.add_weighted(other, self_portion, other_portion).map(LineHeight::Number)
-            }
-            (LineHeight::Normal, LineHeight::Normal) => {
-                Ok(LineHeight::Normal)
-            }
-            _ => Err(()),
-        }
-    }
-
-    #[inline]
-    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
-        match (*self, *other) {
-            (LineHeight::Length(ref this),
-             LineHeight::Length(ref other)) => {
-                this.compute_distance(other)
-            },
-            (LineHeight::Number(ref this),
-             LineHeight::Number(ref other)) => {
-                this.compute_distance(other)
-            },
-            _ => Err(()),
-        }
-    }
-}
-
 /// http://dev.w3.org/csswg/css-transitions/#animtype-font-weight
 impl Animatable for FontWeight {
     #[inline]
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         let a = (*self as u32) as f64;
         let b = (*other as u32) as f64;
         let weight = a * self_portion + b * other_portion;
         Ok(if weight < 150. {
--- a/servo/components/style/properties/longhand/border.mako.rs
+++ b/servo/components/style/properties/longhand/border.mako.rs
@@ -27,17 +27,19 @@
 
     ${helpers.predefined_type("border-%s-style" % side[0], "BorderStyle",
                               "specified::BorderStyle::none",
                               need_clone=True,
                               alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-style"),
                               spec=maybe_logical_spec(side, "style"),
                               animation_value_type="none", logical=side[1])}
 
-    ${helpers.predefined_type("border-%s-width" % side[0], "BorderWidth", "Au::from_px(3)",
+    ${helpers.predefined_type("border-%s-width" % side[0],
+                              "BorderSideWidth",
+                              "Au::from_px(3)",
                               computed_type="::app_units::Au",
                               alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-width"),
                               spec=maybe_logical_spec(side, "width"),
                               animation_value_type="ComputedValue",
                               logical=side[1],
                               allow_quirks=not side[1])}
 % endfor
 
@@ -282,18 +284,18 @@
         let first = try!(RepeatKeyword::parse(input));
         let second = input.try(RepeatKeyword::parse).ok();
 
         Ok(SpecifiedValue(first, second))
     }
 </%helpers:longhand>
 
 ${helpers.predefined_type("border-image-width", "BorderImageWidth",
-    initial_value="computed::BorderImageWidthSide::one().into()",
-    initial_specified_value="specified::BorderImageWidthSide::one().into()",
+    initial_value="computed::BorderImageSideWidth::one().into()",
+    initial_specified_value="specified::BorderImageSideWidth::one().into()",
     spec="https://drafts.csswg.org/css-backgrounds/#border-image-width",
     animation_value_type="none",
     boxed=True)}
 
 ${helpers.predefined_type("border-image-slice", "BorderImageSlice",
     initial_value="computed::NumberOrPercentage::Percentage(computed::Percentage(1.)).into()",
     initial_specified_value="specified::NumberOrPercentage::Percentage(specified::Percentage(1.)).into()",
     spec="https://drafts.csswg.org/css-backgrounds/#border-image-slice",
--- a/servo/components/style/properties/longhand/column.mako.rs
+++ b/servo/components/style/properties/longhand/column.mako.rs
@@ -35,21 +35,25 @@
                           experimental=True,
                           animation_value_type="ComputedValue",
                           spec="https://drafts.csswg.org/css-multicol/#propdef-column-gap")}
 
 ${helpers.single_keyword("column-fill", "balance auto", extra_prefixes="moz",
                          products="gecko", animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-multicol/#propdef-column-fill")}
 
-${helpers.predefined_type("column-rule-width", "BorderWidth", "Au::from_px(3)",
-                          initial_specified_value="specified::BorderWidth::Medium",
-                          products="gecko", computed_type="::app_units::Au",
+${helpers.predefined_type("column-rule-width",
+                          "BorderSideWidth",
+                          "Au::from_px(3)",
+                          initial_specified_value="specified::BorderSideWidth::Medium",
+                          computed_type="::app_units::Au",
+                          products="gecko",
                           spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-width",
-                          animation_value_type="ComputedValue", extra_prefixes="moz")}
+                          animation_value_type="ComputedValue",
+                          extra_prefixes="moz")}
 
 // https://drafts.csswg.org/css-multicol-1/#crc
 ${helpers.predefined_type("column-rule-color", "CSSColor",
                           "::cssparser::Color::CurrentColor",
                           initial_specified_value="specified::CSSColor::currentcolor()",
                           products="gecko", animation_value_type="IntermediateColor", extra_prefixes="moz",
                           complex_color=True, need_clone=True,
                           ignored_when_colors_disabled=True,
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -1,158 +1,21 @@
 /* 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/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 <% from data import Keyword %>
 <% data.new_style_struct("InheritedText", inherited=True, gecko_name="Text") %>
 
-<%helpers:longhand name="line-height" animation_value_type="ComputedValue"
-                   spec="https://drafts.csswg.org/css2/visudet.html#propdef-line-height">
-    use std::fmt;
-    use style_traits::ToCss;
-
-    #[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub enum SpecifiedValue {
-        Normal,
-        % if product == "gecko":
-            MozBlockHeight,
-        % endif
-        Number(specified::Number),
-        LengthOrPercentage(specified::LengthOrPercentage),
-    }
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                SpecifiedValue::Normal => dest.write_str("normal"),
-                % if product == "gecko":
-                    SpecifiedValue::MozBlockHeight => dest.write_str("-moz-block-height"),
-                % endif
-                SpecifiedValue::LengthOrPercentage(ref value) => value.to_css(dest),
-                SpecifiedValue::Number(number) => number.to_css(dest),
-            }
-        }
-    }
-    /// normal | <number> | <length> | <percentage>
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        use cssparser::Token;
-        use std::ascii::AsciiExt;
-
-        // We try to parse as a Number first because, for 'line-height', we want
-        // "0" to be parsed as a plain Number rather than a Length (0px); this
-        // matches the behaviour of all major browsers
-        if let Ok(number) = input.try(|i| specified::Number::parse_non_negative(context, i)) {
-            return Ok(SpecifiedValue::Number(number))
-        }
-
-        if let Ok(lop) = input.try(|i| specified::LengthOrPercentage::parse_non_negative(context, i)) {
-            return Ok(SpecifiedValue::LengthOrPercentage(lop))
-        }
-
-
-        match try!(input.next()) {
-            Token::Ident(ref value) if value.eq_ignore_ascii_case("normal") => {
-                Ok(SpecifiedValue::Normal)
-            }
-            % if product == "gecko":
-            Token::Ident(ref value) if value.eq_ignore_ascii_case("-moz-block-height") => {
-                Ok(SpecifiedValue::MozBlockHeight)
-            }
-            % endif
-            _ => Err(()),
-        }
-    }
-    pub mod computed_value {
-        use app_units::Au;
-        use values::CSSFloat;
-        #[derive(PartialEq, Copy, Clone, Debug)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub enum T {
-            Normal,
-            % if product == "gecko":
-                MozBlockHeight,
-            % endif
-            Length(Au),
-            Number(CSSFloat),
-        }
-    }
-    impl ToCss for computed_value::T {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                computed_value::T::Normal => dest.write_str("normal"),
-                % if product == "gecko":
-                    computed_value::T::MozBlockHeight => dest.write_str("-moz-block-height"),
-                % endif
-                computed_value::T::Length(length) => length.to_css(dest),
-                computed_value::T::Number(number) => write!(dest, "{}", number),
-            }
-        }
-    }
-     #[inline]
-    pub fn get_initial_value() -> computed_value::T { computed_value::T::Normal }
-
-    #[inline]
-    pub fn get_initial_specified_value() -> SpecifiedValue {
-        SpecifiedValue::Normal
-    }
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            match *self {
-                SpecifiedValue::Normal => computed_value::T::Normal,
-                % if product == "gecko":
-                    SpecifiedValue::MozBlockHeight => computed_value::T::MozBlockHeight,
-                % endif
-                SpecifiedValue::Number(value) => computed_value::T::Number(value.to_computed_value(context)),
-                SpecifiedValue::LengthOrPercentage(ref value) => {
-                    match *value {
-                        specified::LengthOrPercentage::Length(ref value) =>
-                            computed_value::T::Length(value.to_computed_value(context)),
-                        specified::LengthOrPercentage::Percentage(specified::Percentage(value)) => {
-                            let fr = specified::Length::NoCalc(specified::NoCalcLength::FontRelative(
-                                specified::FontRelativeLength::Em(value)));
-                            computed_value::T::Length(fr.to_computed_value(context))
-                        },
-                        specified::LengthOrPercentage::Calc(ref calc) => {
-                            let calc = calc.to_computed_value(context);
-                            let fr = specified::FontRelativeLength::Em(calc.percentage());
-                            let fr = specified::Length::NoCalc(specified::NoCalcLength::FontRelative(fr));
-                            let length = calc.unclamped_length();
-                            computed_value::T::Length(calc.clamping_mode.clamp(length + fr.to_computed_value(context)))
-                        }
-                    }
-                }
-            }
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            match *computed {
-                computed_value::T::Normal => SpecifiedValue::Normal,
-                % if product == "gecko":
-                    computed_value::T::MozBlockHeight => SpecifiedValue::MozBlockHeight,
-                % endif
-                computed_value::T::Number(ref value) => {
-                    SpecifiedValue::Number(specified::Number::from_computed_value(value))
-                },
-                computed_value::T::Length(au) => {
-                    SpecifiedValue::LengthOrPercentage(specified::LengthOrPercentage::Length(
-                        ToComputedValue::from_computed_value(&au)
-                    ))
-                }
-            }
-        }
-    }
-</%helpers:longhand>
+${helpers.predefined_type("line-height",
+                          "LineHeight",
+                          "computed::LineHeight::normal()",
+                          animation_value_type="ComputedValue",
+                          spec="https://drafts.csswg.org/css2/visudet.html#propdef-line-height")}
 
 // CSS Text Module Level 3
 
 // TODO(pcwalton): `full-width`
 ${helpers.single_keyword("text-transform",
                          "none capitalize uppercase lowercase",
                          extra_gecko_values="full-width",
                          animation_value_type="none",
@@ -390,167 +253,27 @@
         impl ComputedValueAsSpecified for SpecifiedValue {}
         pub use self::computed_value::T as SpecifiedValue;
         pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
             computed_value::T::parse(input)
         }
     % endif
 </%helpers:longhand>
 
-<%helpers:longhand name="letter-spacing" animation_value_type="ComputedValue"
-                   spec="https://drafts.csswg.org/css-text/#propdef-letter-spacing">
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::specified::AllowQuirks;
-
-    #[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub enum SpecifiedValue {
-        Normal,
-        Specified(specified::Length),
-    }
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                SpecifiedValue::Normal => dest.write_str("normal"),
-                SpecifiedValue::Specified(ref l) => l.to_css(dest),
-            }
-        }
-    }
-
-    pub mod computed_value {
-        use app_units::Au;
-        use properties::animated_properties::Animatable;
-
-        #[derive(Debug, Clone, PartialEq)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub struct T(pub Option<Au>);
-
-        ${helpers.impl_animatable_for_option_tuple('Au(0)')}
-    }
-
-    impl ToCss for computed_value::T {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match self.0 {
-                None => dest.write_str("normal"),
-                Some(l) => l.to_css(dest),
-            }
-        }
-    }
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        computed_value::T(None)
-    }
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            match *self {
-                SpecifiedValue::Normal => computed_value::T(None),
-                SpecifiedValue::Specified(ref l) =>
-                    computed_value::T(Some(l.to_computed_value(context)))
-            }
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            computed.0.map(|ref au| {
-                SpecifiedValue::Specified(ToComputedValue::from_computed_value(au))
-            }).unwrap_or(SpecifiedValue::Normal)
-        }
-    }
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
-            Ok(SpecifiedValue::Normal)
-        } else {
-            specified::Length::parse_quirky(context, input, AllowQuirks::Yes).map(SpecifiedValue::Specified)
-        }
-    }
-</%helpers:longhand>
+${helpers.predefined_type("letter-spacing",
+                          "LetterSpacing",
+                          "computed::LetterSpacing::normal()",
+                          animation_value_type="ComputedValue",
+                          spec="https://drafts.csswg.org/css-text/#propdef-letter-spacing")}
 
-<%helpers:longhand name="word-spacing" animation_value_type="ComputedValue"
-                   spec="https://drafts.csswg.org/css-text/#propdef-word-spacing">
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::specified::AllowQuirks;
-
-    #[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub enum SpecifiedValue {
-        Normal,
-        Specified(specified::LengthOrPercentage),
-    }
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                SpecifiedValue::Normal => dest.write_str("normal"),
-                SpecifiedValue::Specified(ref l) => l.to_css(dest),
-            }
-        }
-    }
-
-    pub mod computed_value {
-        use properties::animated_properties::Animatable;
-        use values::computed::LengthOrPercentage;
-        #[derive(Debug, Clone, PartialEq)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub struct T(pub Option<LengthOrPercentage>);
-
-        ${helpers.impl_animatable_for_option_tuple('LengthOrPercentage::zero()')}
-    }
-
-    impl ToCss for computed_value::T {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match self.0 {
-                None => dest.write_str("normal"),
-                Some(l) => l.to_css(dest),
-            }
-        }
-    }
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        computed_value::T(None)
-    }
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            match *self {
-                SpecifiedValue::Normal => computed_value::T(None),
-                SpecifiedValue::Specified(ref l) =>
-                    computed_value::T(Some(l.to_computed_value(context))),
-            }
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            computed.0.map(|ref lop| {
-                SpecifiedValue::Specified(ToComputedValue::from_computed_value(lop))
-            }).unwrap_or(SpecifiedValue::Normal)
-        }
-    }
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
-            Ok(SpecifiedValue::Normal)
-        } else {
-            specified::LengthOrPercentage::parse_quirky(context, input, AllowQuirks::Yes)
-                                          .map(SpecifiedValue::Specified)
-        }
-    }
-</%helpers:longhand>
+${helpers.predefined_type("word-spacing",
+                          "WordSpacing",
+                          "computed::WordSpacing::normal()",
+                          animation_value_type="ComputedValue",
+                          spec="https://drafts.csswg.org/css-text/#propdef-word-spacing")}
 
 <%helpers:longhand name="-servo-text-decorations-in-effect"
                    derived_from="display text-decoration"
                    need_clone="True" products="servo"
                    animation_value_type="none"
                    spec="Nonstandard (Internal property used by Servo)">
     use cssparser::RGBA;
     use std::fmt;
@@ -1144,23 +867,25 @@
     "-webkit-text-stroke-color", "CSSColor",
     "CSSParserColor::CurrentColor",
     initial_specified_value="specified::CSSColor::currentcolor()",
     products="gecko", animation_value_type="IntermediateColor",
     complex_color=True, need_clone=True,
     ignored_when_colors_disabled=True,
     spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-color")}
 
-${helpers.predefined_type("-webkit-text-stroke-width", "BorderWidth", "Au::from_px(0)",
-                          initial_specified_value="specified::BorderWidth::from_length(specified::Length::zero())",
-                          computed_type="::app_units::Au", products="gecko",
+${helpers.predefined_type("-webkit-text-stroke-width",
+                          "BorderSideWidth",
+                          "Au::from_px(0)",
+                          initial_specified_value="specified::BorderSideWidth::Length(specified::Length::zero())",
+                          computed_type="::app_units::Au",
+                          products="gecko",
                           spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-width",
                           animation_value_type="none")}
 
-
 // CSS Ruby Layout Module Level 1
 // https://drafts.csswg.org/css-ruby/
 ${helpers.single_keyword("ruby-align", "space-around start center space-between",
                          products="gecko", animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-ruby/#ruby-align-property")}
 
 ${helpers.single_keyword("ruby-position", "over under",
                          products="gecko", animation_value_type="discrete",
--- a/servo/components/style/properties/longhand/outline.mako.rs
+++ b/servo/components/style/properties/longhand/outline.mako.rs
@@ -56,60 +56,23 @@
                     Err(())
                 } else {
                     Ok(result)
                 }
             })
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="outline-width" animation_value_type="ComputedValue"
-                   spec="https://drafts.csswg.org/css-ui/#propdef-outline-width">
-    use std::fmt;
-    use style_traits::ToCss;
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            self.0.to_css(dest)
-        }
-    }
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        specified::parse_border_width(context, input).map(SpecifiedValue)
-    }
-
-    #[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub struct SpecifiedValue(pub specified::Length);
-
-    pub mod computed_value {
-        use app_units::Au;
-        pub type T = Au;
-    }
-
-    pub use super::border_top_width::get_initial_value;
-    #[inline]
-    pub fn get_initial_specified_value() -> SpecifiedValue {
-        SpecifiedValue(specified::Length::NoCalc(specified::NoCalcLength::medium()))
-    }
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            self.0.to_computed_value(context)
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            SpecifiedValue(ToComputedValue::from_computed_value(computed))
-        }
-    }
-</%helpers:longhand>
+${helpers.predefined_type("outline-width",
+                          "BorderSideWidth",
+                          "Au::from_px(3)",
+                          initial_specified_value="specified::BorderSideWidth::Medium",
+                          computed_type="::app_units::Au",
+                          animation_value_type="ComputedValue",
+                          spec="https://drafts.csswg.org/css-ui/#propdef-outline-width")}
 
 // The -moz-outline-radius-* properties are non-standard and not on a standards track.
 // TODO: Should they animate?
 % for corner in ["topleft", "topright", "bottomright", "bottomleft"]:
     ${helpers.predefined_type("-moz-outline-radius-" + corner, "BorderCornerRadius",
         "computed::LengthOrPercentage::zero().into()",
         products="gecko",
         boxed=True,
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -33,18 +33,19 @@ use media_queries::Device;
 use parser::{PARSING_MODE_DEFAULT, Parse, ParserContext};
 use properties::animated_properties::TransitionProperty;
 #[cfg(feature = "gecko")] use properties::longhands::system_font::SystemFont;
 #[cfg(feature = "servo")] use servo_config::prefs::PREFS;
 use shared_lock::StylesheetGuards;
 use style_traits::{HasViewportPercentage, ToCss};
 use stylesheets::{CssRuleType, MallocSizeOf, MallocSizeOfFn, Origin, UrlExtraData};
 #[cfg(feature = "servo")] use values::Either;
+use values::generics::text::LineHeight;
+use values::computed;
 use values::specified::Color;
-use values::computed;
 use cascade_info::CascadeInfo;
 use rule_tree::{CascadeLevel, StrongRuleNode};
 use style_adjuster::StyleAdjuster;
 #[cfg(feature = "servo")] use values::specified::BorderStyle;
 
 pub use self::declaration_block::*;
 
 #[cfg(feature = "gecko")]
@@ -1257,21 +1258,19 @@ impl PropertyDeclaration {
                     prop.get_system()
                 }
             % endfor
             _ => None,
         }
     }
 
     /// Is it the default value of line-height?
-    ///
-    /// (using match because it generates less code than)
     pub fn is_default_line_height(&self) -> bool {
         match *self {
-            PropertyDeclaration::LineHeight(longhands::line_height::SpecifiedValue::Normal) => true,
+            PropertyDeclaration::LineHeight(LineHeight::Normal) => true,
             _ => false
         }
     }
 
     #[cfg(feature = "servo")]
     /// Dummy method to avoid cfg()s
     pub fn get_system(&self) -> Option<()> {
         None
--- a/servo/components/style/properties/shorthand/border.mako.rs
+++ b/servo/components/style/properties/shorthand/border.mako.rs
@@ -13,21 +13,21 @@
                                "specified::BorderStyle::parse",
                                spec="https://drafts.csswg.org/css-backgrounds/#border-style")}
 
 <%helpers:shorthand name="border-width" sub_properties="${
         ' '.join('border-%s-width' % side
                  for side in PHYSICAL_SIDES)}"
     spec="https://drafts.csswg.org/css-backgrounds/#border-width">
     use values::generics::rect::Rect;
-    use values::specified::{AllowQuirks, BorderWidth};
+    use values::specified::{AllowQuirks, BorderSideWidth};
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let rect = Rect::parse_with(context, input, |_, i| {
-            BorderWidth::parse_quirky(context, i, AllowQuirks::Yes)
+            BorderSideWidth::parse_quirky(context, i, AllowQuirks::Yes)
         })?;
         Ok(expanded! {
             border_top_width: rect.0,
             border_right_width: rect.1,
             border_bottom_width: rect.2,
             border_left_width: rect.3,
         })
     }
@@ -41,18 +41,18 @@
         }
     }
 </%helpers:shorthand>
 
 
 pub fn parse_border(context: &ParserContext, input: &mut Parser)
                  -> Result<(specified::CSSColor,
                             specified::BorderStyle,
-                            specified::BorderWidth), ()> {
-    use values::specified::{CSSColor, BorderStyle, BorderWidth};
+                            specified::BorderSideWidth), ()> {
+    use values::specified::{CSSColor, BorderStyle, BorderSideWidth};
     let _unused = context;
     let mut color = None;
     let mut style = None;
     let mut width = None;
     let mut any = false;
     loop {
         if color.is_none() {
             if let Ok(value) = input.try(|i| CSSColor::parse(context, i)) {
@@ -64,28 +64,28 @@ pub fn parse_border(context: &ParserCont
         if style.is_none() {
             if let Ok(value) = input.try(|i| BorderStyle::parse(context, i)) {
                 style = Some(value);
                 any = true;
                 continue
             }
         }
         if width.is_none() {
-            if let Ok(value) = input.try(|i| BorderWidth::parse(context, i)) {
+            if let Ok(value) = input.try(|i| BorderSideWidth::parse(context, i)) {
                 width = Some(value);
                 any = true;
                 continue
             }
         }
         break
     }
     if any {
         Ok((color.unwrap_or_else(|| CSSColor::currentcolor()),
             style.unwrap_or(BorderStyle::none),
-            width.unwrap_or(BorderWidth::Medium)))
+            width.unwrap_or(BorderSideWidth::Medium)))
     } else {
         Err(())
     }
 }
 
 % for side, logical in ALL_SIDES:
     <%
         spec = "https://drafts.csswg.org/css-backgrounds/#border-%s" % side
--- a/servo/components/style/properties/shorthand/font.mako.rs
+++ b/servo/components/style/properties/shorthand/font.mako.rs
@@ -13,20 +13,22 @@
                                     ${'font-variant-alternates' if product == 'gecko' or data.testing else ''}
                                     ${'font-variant-east-asian' if product == 'gecko' or data.testing else ''}
                                     ${'font-variant-ligatures' if product == 'gecko' or data.testing else ''}
                                     ${'font-variant-numeric' if product == 'gecko' or data.testing else ''}
                                     ${'font-variant-position' if product == 'gecko' or data.testing else ''}
                                     ${'font-language-override' if product == 'gecko' or data.testing else ''}
                                     ${'font-feature-settings' if product == 'gecko' or data.testing else ''}"
                     spec="https://drafts.csswg.org/css-fonts-3/#propdef-font">
+    use parser::Parse;
     use properties::longhands::{font_family, font_style, font_weight, font_stretch};
-    use properties::longhands::{font_size, line_height, font_variant_caps};
+    use properties::longhands::{font_size, font_variant_caps};
     #[cfg(feature = "gecko")]
     use properties::longhands::system_font::SystemFont;
+    use values::specified::text::LineHeight;
 
     <%
         gecko_sub_properties = "kerning language_override size_adjust \
                                 variant_alternates variant_east_asian \
                                 variant_ligatures variant_numeric \
                                 variant_position feature_settings".split()
     %>
     % if product == "gecko" or data.testing:
@@ -45,17 +47,17 @@
         let size;
         % if product == "gecko":
             if let Ok(sys) = input.try(SystemFont::parse) {
                 return Ok(expanded! {
                      % for name in SYSTEM_FONT_LONGHANDS:
                          ${name}: ${name}::SpecifiedValue::system_font(sys),
                      % endfor
                      // line-height is just reset to initial
-                     line_height: line_height::get_initial_specified_value(),
+                     line_height: LineHeight::normal(),
                  })
             }
         % endif
         loop {
             // Special-case 'normal' because it is valid in each of
             // font-style, font-weight, font-variant and font-stretch.
             // Leaves the values to None, 'normal' is the initial value for each of them.
             if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
@@ -93,26 +95,26 @@
         fn count<T>(opt: &Option<T>) -> u8 {
             if opt.is_some() { 1 } else { 0 }
         }
         if size.is_none() ||
            (count(&style) + count(&weight) + count(&variant_caps) + count(&stretch) + nb_normals) > 4 {
             return Err(())
         }
         let line_height = if input.try(|input| input.expect_delim('/')).is_ok() {
-            Some(try!(line_height::parse(context, input)))
+            Some(try!(LineHeight::parse(context, input)))
         } else {
             None
         };
         let family = FontFamily::parse(input)?;
         Ok(expanded! {
             % for name in "style weight stretch size variant_caps".split():
                 font_${name}: unwrap_or_initial!(font_${name}, ${name}),
             % endfor
-            line_height: unwrap_or_initial!(line_height),
+            line_height: line_height.unwrap_or(LineHeight::normal()),
             font_family: family,
             % if product == "gecko" or data.testing:
                 % for name in gecko_sub_properties:
                     font_${name}: font_${name}::get_initial_specified_value(),
                 % endfor
             % endif
         })
     }
@@ -164,22 +166,19 @@
                 if needs_this_property {
                     self.font_${name}.to_css(dest)?;
                     dest.write_str(" ")?;
                 }
             % endfor
 
             self.font_size.to_css(dest)?;
 
-            match *self.line_height {
-                line_height::SpecifiedValue::Normal => {},
-                _ => {
-                    dest.write_str("/")?;
-                    self.line_height.to_css(dest)?;
-                }
+            if *self.line_height != LineHeight::normal() {
+                dest.write_str("/")?;
+                self.line_height.to_css(dest)?;
             }
 
             dest.write_str(" ")?;
             self.font_family.to_css(dest)?;
 
             Ok(())
         }
 
@@ -192,17 +191,17 @@
                 % for prop in SYSTEM_FONT_LONGHANDS:
                     if let Some(s) = self.${prop}.get_system() {
                         debug_assert!(sys.is_none() || s == sys.unwrap());
                         sys = Some(s);
                     } else {
                         all = false;
                     }
                 % endfor
-                if self.line_height != &line_height::get_initial_specified_value() {
+                if self.line_height != &LineHeight::normal() {
                     all = false
                 }
                 if all {
                     CheckSystemResult::AllSystem(sys.unwrap())
                 } else if sys.is_some() {
                     CheckSystemResult::SomeSystem
                 } else {
                     CheckSystemResult::None
--- a/servo/components/style/values/computed/border.rs
+++ b/servo/components/style/values/computed/border.rs
@@ -2,35 +2,35 @@
  * 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 CSS values related to borders.
 
 use values::computed::{Number, NumberOrPercentage};
 use values::computed::length::LengthOrPercentage;
 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::BorderImageWidthSide as GenericBorderImageWidthSide;
 use values::generics::border::BorderRadius as GenericBorderRadius;
 use values::generics::rect::Rect;
 
 /// A computed value for the `border-image-width` property.
-pub type BorderImageWidth = Rect<BorderImageWidthSide>;
+pub type BorderImageWidth = Rect<BorderImageSideWidth>;
 
 /// A computed value for a single side of a `border-image-width` property.
-pub type BorderImageWidthSide = GenericBorderImageWidthSide<LengthOrPercentage, Number>;
+pub type BorderImageSideWidth = GenericBorderImageSideWidth<LengthOrPercentage, Number>;
 
 /// A computed value for the `border-image-slice` property.
 pub type BorderImageSlice = GenericBorderImageSlice<NumberOrPercentage>;
 
 /// A computed value for the `border-radius` property.
 pub type BorderRadius = GenericBorderRadius<LengthOrPercentage>;
 
 /// A computed value for the `border-*-radius` longhand properties.
 pub type BorderCornerRadius = GenericBorderCornerRadius<LengthOrPercentage>;
 
-impl BorderImageWidthSide {
+impl BorderImageSideWidth {
     /// Returns `1`.
     #[inline]
     pub fn one() -> Self {
-        GenericBorderImageWidthSide::Number(1.)
+        GenericBorderImageSideWidth::Number(1.)
     }
 }
--- a/servo/components/style/values/computed/length.rs
+++ b/servo/components/style/values/computed/length.rs
@@ -217,16 +217,23 @@ impl ToComputedValue for specified::Calc
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub enum LengthOrPercentage {
     Length(Au),
     Percentage(CSSFloat),
     Calc(CalcLengthOrPercentage),
 }
 
+impl From<Au> for LengthOrPercentage {
+    #[inline]
+    fn from(length: Au) -> Self {
+        LengthOrPercentage::Length(length)
+    }
+}
+
 impl LengthOrPercentage {
     #[inline]
     #[allow(missing_docs)]
     pub fn zero() -> LengthOrPercentage {
         LengthOrPercentage::Length(Au(0))
     }
 
     #[inline]
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -19,39 +19,41 @@ use style_traits::ToCss;
 use super::{CSSFloat, CSSInteger, RGBA};
 use super::generics::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize};
 use super::generics::grid::TrackList as GenericTrackList;
 use super::specified;
 
 pub use app_units::Au;
 pub use cssparser::Color as CSSColor;
 pub use self::background::BackgroundSize;
-pub use self::border::{BorderImageSlice, BorderImageWidth, BorderImageWidthSide};
+pub use self::border::{BorderImageSlice, BorderImageWidth, BorderImageSideWidth};
 pub use self::border::{BorderRadius, BorderCornerRadius};
 pub use self::image::{Gradient, GradientItem, ImageLayer, LineDirection, Image, ImageRect};
 pub use self::rect::LengthOrNumberRect;
 pub use super::{Auto, Either, None_};
 #[cfg(feature = "gecko")]
 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::transform::TransformOrigin;
 
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod image;
 pub mod length;
 pub mod position;
 pub mod rect;
+pub mod text;
 pub mod transform;
 
 /// A `Context` is all the data a specified value could ever need to compute
 /// itself and be transformed to a computed value.
 pub struct Context<'a> {
     /// Whether the current element is the root element.
     pub is_root_element: bool,
 
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/computed/text.rs
@@ -0,0 +1,58 @@
+/* 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::computed::length::{Length, LengthOrPercentage};
+use values::generics::text::{LineHeight as GenericLineHeight, Spacing};
+
+/// 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.
+pub type LineHeight = GenericLineHeight<CSSFloat, Au>;
+
+impl Animatable for LineHeight {
+    #[inline]
+    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
+        match (*self, *other) {
+            (GenericLineHeight::Length(ref this), GenericLineHeight::Length(ref other)) => {
+                this.add_weighted(other, self_portion, other_portion).map(GenericLineHeight::Length)
+            },
+            (GenericLineHeight::Number(ref this), GenericLineHeight::Number(ref other)) => {
+                this.add_weighted(other, self_portion, other_portion).map(GenericLineHeight::Number)
+            },
+            (GenericLineHeight::Normal, GenericLineHeight::Normal) => {
+                Ok(GenericLineHeight::Normal)
+            },
+            #[cfg(feature = "gecko")]
+            (GenericLineHeight::MozBlockHeight, GenericLineHeight::MozBlockHeight) => {
+                Ok(GenericLineHeight::MozBlockHeight)
+            },
+            _ => Err(()),
+        }
+    }
+
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        match (*self, *other) {
+            (GenericLineHeight::Length(ref this), GenericLineHeight::Length(ref other)) => {
+                this.compute_distance(other)
+            },
+            (GenericLineHeight::Number(ref this), GenericLineHeight::Number(ref other)) => {
+                this.compute_distance(other)
+            },
+            (GenericLineHeight::Normal, GenericLineHeight::Normal) => Ok(0.),
+            #[cfg(feature = "gecko")]
+            (GenericLineHeight::MozBlockHeight, GenericLineHeight::MozBlockHeight) => Ok(0.),
+            _ => Err(()),
+        }
+    }
+}
--- a/servo/components/style/values/generics/border.rs
+++ b/servo/components/style/values/generics/border.rs
@@ -7,17 +7,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)]
-pub enum BorderImageWidthSide<LengthOrPercentage, Number> {
+pub enum BorderImageSideWidth<LengthOrPercentage, Number> {
     /// `<length-or-percentage>`
     Length(LengthOrPercentage),
     /// `<number>`
     Number(Number),
     /// `auto`
     Auto,
 }
 
@@ -47,26 +47,26 @@ 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 BorderImageWidthSide<L, N>
+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 {
-            BorderImageWidthSide::Length(ref length) => length.to_css(dest),
-            BorderImageWidthSide::Number(ref number) => number.to_css(dest),
-            BorderImageWidthSide::Auto => dest.write_str("auto"),
+            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]
--- a/servo/components/style/values/generics/mod.rs
+++ b/servo/components/style/values/generics/mod.rs
@@ -14,16 +14,17 @@ use super::CustomIdent;
 
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod grid;
 pub mod image;
 pub mod position;
 pub mod rect;
+pub mod text;
 pub mod transform;
 
 // https://drafts.csswg.org/css-counter-styles/#typedef-symbols-type
 define_css_keyword_enum! { SymbolsType:
     "cyclic" => Cyclic,
     "numeric" => Numeric,
     "alphabetic" => Alphabetic,
     "symbolic" => Symbolic,
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/generics/text.rs
@@ -0,0 +1,129 @@
+/* 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 text properties.
+
+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
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)]
+pub enum Spacing<Value> {
+    /// `normal`
+    Normal,
+    /// `<value>`
+    Value(Value),
+}
+
+impl<Value> Spacing<Value> {
+    /// Returns `normal`.
+    #[inline]
+    pub fn normal() -> Self {
+        Spacing::Normal
+    }
+
+    /// Parses.
+    #[inline]
+    pub fn parse_with<F>(
+        context: &ParserContext,
+        input: &mut Parser,
+        parse: F)
+        -> Result<Self, ()>
+        where F: FnOnce(&ParserContext, &mut Parser) -> Result<Value, ()>
+    {
+        if input.try(|i| i.expect_ident_matching("normal")).is_ok() {
+            return Ok(Spacing::Normal);
+        }
+        parse(context, input).map(Spacing::Value)
+    }
+
+    /// Returns the spacing value, if not `normal`.
+    #[inline]
+    pub fn value(&self) -> Option<&Value> {
+        match *self {
+            Spacing::Normal => None,
+            Spacing::Value(ref value) => Some(value),
+        }
+    }
+}
+
+impl<Value> Animatable for Spacing<Value>
+    where Value: Animatable + From<Au>,
+{
+    #[inline]
+    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
+        if let (&Spacing::Normal, &Spacing::Normal) = (self, other) {
+            return Ok(Spacing::Normal);
+        }
+        let zero = Value::from(Au(0));
+        let this = self.value().unwrap_or(&zero);
+        let other = other.value().unwrap_or(&zero);
+        this.add_weighted(other, self_portion, other_portion).map(Spacing::Value)
+    }
+
+    #[inline]
+    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)]
+pub enum LineHeight<Number, LengthOrPercentage> {
+    /// `normal`
+    Normal,
+    /// `-moz-block-height`
+    #[cfg(feature = "gecko")]
+    MozBlockHeight,
+    /// `<number>`
+    Number(Number),
+    /// `<length-or-percentage>`
+    Length(LengthOrPercentage),
+}
+
+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
@@ -1,59 +1,136 @@
 /* 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/. */
 
 //! 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::BorderImageWidthSide as GenericBorderImageWidthSide;
 use values::generics::border::BorderRadius as GenericBorderRadius;
 use values::generics::rect::Rect;
-use values::specified::{Number, NumberOrPercentage};
-use values::specified::length::LengthOrPercentage;
+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)]
+pub enum BorderSideWidth {
+    /// `thin`
+    Thin,
+    /// `medium`
+    Medium,
+    /// `thick`
+    Thick,
+    /// `<length>`
+    Length(Length),
+}
 
 /// A specified value for the `border-image-width` property.
-pub type BorderImageWidth = Rect<BorderImageWidthSide>;
+pub type BorderImageWidth = Rect<BorderImageSideWidth>;
 
 /// A specified value for a single side of a `border-image-width` property.
-pub type BorderImageWidthSide = GenericBorderImageWidthSide<LengthOrPercentage, Number>;
+pub type BorderImageSideWidth = GenericBorderImageSideWidth<LengthOrPercentage, Number>;
 
 /// A specified value for the `border-image-slice` property.
 pub type BorderImageSlice = GenericBorderImageSlice<NumberOrPercentage>;
 
 /// A specified value for the `border-radius` property.
 pub type BorderRadius = GenericBorderRadius<LengthOrPercentage>;
 
 /// A specified value for the `border-*-radius` longhand properties.
 pub type BorderCornerRadius = GenericBorderCornerRadius<LengthOrPercentage>;
 
-impl BorderImageWidthSide {
-    /// Returns `1`.
-    #[inline]
-    pub fn one() -> Self {
-        GenericBorderImageWidthSide::Number(Number::new(1.))
+impl BorderSideWidth {
+    /// Parses, with quirks.
+    pub fn parse_quirky(
+        context: &ParserContext,
+        input: &mut Parser,
+        allow_quirks: AllowQuirks)
+        -> Result<Self, ()>
+    {
+        if let Ok(length) = input.try(|i| Length::parse_non_negative_quirky(context, i, allow_quirks)) {
+            return Ok(BorderSideWidth::Length(length));
+        }
+        match_ignore_ascii_case! { &input.expect_ident()?,
+            "thin" => Ok(BorderSideWidth::Thin),
+            "medium" => Ok(BorderSideWidth::Medium),
+            "thick" => Ok(BorderSideWidth::Thick),
+            _ => Err(())
+        }
+    }
+}
+
+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 Parse for BorderImageWidthSide {
+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
+        match *self {
+            BorderSideWidth::Thin => Length::from_px(1.).to_computed_value(context),
+            BorderSideWidth::Medium => Length::from_px(3.).to_computed_value(context),
+            BorderSideWidth::Thick => Length::from_px(5.).to_computed_value(context),
+            BorderSideWidth::Length(ref length) => length.to_computed_value(context)
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        BorderSideWidth::Length(ToComputedValue::from_computed_value(computed))
+    }
+}
+
+impl BorderImageSideWidth {
+    /// Returns `1`.
+    #[inline]
+    pub fn one() -> Self {
+        GenericBorderImageSideWidth::Number(Number::new(1.))
+    }
+}
+
+impl Parse for BorderImageSideWidth {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         if input.try(|i| i.expect_ident_matching("auto")).is_ok() {
-            return Ok(GenericBorderImageWidthSide::Auto);
+            return Ok(GenericBorderImageSideWidth::Auto);
         }
 
         if let Ok(len) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) {
-            return Ok(GenericBorderImageWidthSide::Length(len));
+            return Ok(GenericBorderImageSideWidth::Length(len));
         }
 
         let num = Number::parse_non_negative(context, input)?;
-        Ok(GenericBorderImageWidthSide::Number(num))
+        Ok(GenericBorderImageSideWidth::Number(num))
     }
 }
 
 impl Parse for BorderImageSlice {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         let mut fill = input.try(|i| i.expect_ident_matching("fill")).is_ok();
         let offsets = Rect::parse_with(context, input, NumberOrPercentage::parse_non_negative)?;
         if !fill {
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -2,17 +2,16 @@
  * 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 values.
 //!
 //! TODO(emilio): Enhance docs.
 
 use Namespace;
-use app_units::Au;
 use context::QuirksMode;
 use cssparser::{self, Parser, Token, serialize_identifier};
 use itoa;
 use parser::{ParserContext, Parse};
 use self::grid::TrackSizeOrRepeat;
 use self::url::SpecifiedUrl;
 use std::ascii::AsciiExt;
 use std::f32;
@@ -27,42 +26,44 @@ use super::generics::grid::{TrackBreadth
 use super::generics::grid::TrackList as GenericTrackList;
 use values::computed::ComputedValueAsSpecified;
 use values::specified::calc::CalcNode;
 
 #[cfg(feature = "gecko")]
 pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use self::background::BackgroundSize;
 pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth};
-pub use self::border::{BorderImageWidthSide, BorderRadius};
+pub use self::border::{BorderImageSideWidth, BorderRadius, BorderSideWidth};
 pub use self::color::Color;
 pub use self::rect::LengthOrNumberRect;
 pub use super::generics::grid::GridLine;
 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::transform::TransformOrigin;
 
 #[cfg(feature = "gecko")]
 pub mod align;
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod calc;
 pub mod color;
 pub mod grid;
 pub mod image;
 pub mod length;
 pub mod position;
 pub mod rect;
+pub mod text;
 pub mod transform;
 
 /// Common handling for the specified value CSS url() values.
 pub mod url {
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use values::computed::ComputedValueAsSpecified;
 
@@ -427,102 +428,16 @@ impl Angle {
             Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
                 input.parse_nested_block(|i| CalcNode::parse_angle(context, i))
             }
             _ => Err(())
         }
     }
 }
 
-#[allow(missing_docs)]
-pub fn parse_border_width(context: &ParserContext, input: &mut Parser) -> Result<Length, ()> {
-    input.try(|i| Length::parse_non_negative(context, i)).or_else(|()| {
-        match_ignore_ascii_case! { &try!(input.expect_ident()),
-            "thin" => Ok(Length::from_px(1.)),
-            "medium" => Ok(Length::from_px(3.)),
-            "thick" => Ok(Length::from_px(5.)),
-            _ => Err(())
-        }
-    })
-}
-
-#[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum BorderWidth {
-    Thin,
-    Medium,
-    Thick,
-    Width(Length),
-}
-
-impl Parse for BorderWidth {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<BorderWidth, ()> {
-        Self::parse_quirky(context, input, AllowQuirks::No)
-    }
-}
-
-impl BorderWidth {
-    /// Parses a border width, allowing quirks.
-    pub fn parse_quirky(context: &ParserContext,
-                        input: &mut Parser,
-                        allow_quirks: AllowQuirks)
-                        -> Result<BorderWidth, ()> {
-        match input.try(|i| Length::parse_non_negative_quirky(context, i, allow_quirks)) {
-            Ok(length) => Ok(BorderWidth::Width(length)),
-            Err(_) => match_ignore_ascii_case! { &try!(input.expect_ident()),
-               "thin" => Ok(BorderWidth::Thin),
-               "medium" => Ok(BorderWidth::Medium),
-               "thick" => Ok(BorderWidth::Thick),
-               _ => Err(())
-            }
-        }
-    }
-}
-
-impl BorderWidth {
-    #[allow(missing_docs)]
-    pub fn from_length(length: Length) -> Self {
-        BorderWidth::Width(length)
-    }
-}
-
-impl ToCss for BorderWidth {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            BorderWidth::Thin => dest.write_str("thin"),
-            BorderWidth::Medium => dest.write_str("medium"),
-            BorderWidth::Thick => dest.write_str("thick"),
-            BorderWidth::Width(ref length) => length.to_css(dest)
-        }
-    }
-}
-
-impl ToComputedValue for BorderWidth {
-    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
-        match *self {
-            BorderWidth::Thin => Length::from_px(1.).to_computed_value(context),
-            BorderWidth::Medium => Length::from_px(3.).to_computed_value(context),
-            BorderWidth::Thick => Length::from_px(5.).to_computed_value(context),
-            BorderWidth::Width(ref length) => length.to_computed_value(context)
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
-        BorderWidth::Width(ToComputedValue::from_computed_value(computed))
-    }
-}
-
 // The integer values here correspond to the border conflict resolution rules in CSS 2.1 §
 // 17.6.2.1. Higher values override lower values.
 define_numbered_css_keyword_enum! { BorderStyle:
     "none" => none = -1,
     "solid" => solid = 6,
     "double" => double = 7,
     "dotted" => dotted = 4,
     "dashed" => dashed = 5,
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/specified/text.rs
@@ -0,0 +1,119 @@
+/* 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/. */
+
+//! 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::specified::length::{FontRelativeLength, Length, LengthOrPercentage, NoCalcLength};
+
+/// 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 LetterSpacing {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        Spacing::parse_with(context, input, |c, i| {
+            Length::parse_quirky(c, i, AllowQuirks::Yes)
+        })
+    }
+}
+
+impl Parse for WordSpacing {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        Spacing::parse_with(context, input, |c, i| {
+            LengthOrPercentage::parse_quirky(c, i, AllowQuirks::Yes)
+        })
+    }
+}
+
+impl Parse for LineHeight {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        if let Ok(number) = input.try(|i| Number::parse_non_negative(context, i)) {
+            return Ok(GenericLineHeight::Number(number))
+        }
+        if let Ok(lop) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) {
+            return Ok(GenericLineHeight::Length(lop))
+        }
+        match &input.expect_ident()? {
+            ident if ident.eq_ignore_ascii_case("normal") => {
+                Ok(GenericLineHeight::Normal)
+            },
+            #[cfg(feature = "gecko")]
+            ident if ident.eq_ignore_ascii_case("-moz-block-height") => {
+                Ok(GenericLineHeight::MozBlockHeight)
+            },
+            _ => Err(()),
+        }
+    }
+}
+
+impl ToComputedValue for LineHeight {
+    type ComputedValue = ComputedLineHeight;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            GenericLineHeight::Normal => {
+                GenericLineHeight::Normal
+            },
+            #[cfg(feature = "gecko")]
+            GenericLineHeight::MozBlockHeight => {
+                GenericLineHeight::MozBlockHeight
+            },
+            GenericLineHeight::Number(number) => {
+                GenericLineHeight::Number(number.to_computed_value(context))
+            },
+            GenericLineHeight::Length(LengthOrPercentage::Length(ref length)) => {
+                GenericLineHeight::Length(length.to_computed_value(context))
+            },
+            GenericLineHeight::Length(LengthOrPercentage::Percentage(p)) => {
+                let font_relative_length =
+                    Length::NoCalc(NoCalcLength::FontRelative(FontRelativeLength::Em(p.0)));
+                GenericLineHeight::Length(font_relative_length.to_computed_value(context))
+            },
+            GenericLineHeight::Length(LengthOrPercentage::Calc(ref calc)) => {
+                let computed_calc = calc.to_computed_value(context);
+                let font_relative_length =
+                    Length::NoCalc(NoCalcLength::FontRelative(FontRelativeLength::Em(computed_calc.percentage())));
+                let absolute_length = computed_calc.unclamped_length();
+                let computed_length = computed_calc.clamping_mode.clamp(
+                    absolute_length + font_relative_length.to_computed_value(context)
+                );
+                GenericLineHeight::Length(computed_length)
+            },
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            GenericLineHeight::Normal => {
+                GenericLineHeight::Normal
+            },
+            #[cfg(feature = "gecko")]
+            GenericLineHeight::MozBlockHeight => {
+                GenericLineHeight::MozBlockHeight
+            },
+            GenericLineHeight::Number(ref number) => {
+                GenericLineHeight::Number(Number::from_computed_value(number))
+            },
+            GenericLineHeight::Length(ref length) => {
+                GenericLineHeight::Length(LengthOrPercentage::Length(
+                    NoCalcLength::from_computed_value(length)
+                ))
+            }
+        }
+    }
+}
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -1941,30 +1941,30 @@ pub extern "C" fn Servo_DeclarationBlock
 pub extern "C" fn Servo_DeclarationBlock_SetPixelValue(declarations:
                                                        RawServoDeclarationBlockBorrowed,
                                                        property: nsCSSPropertyID,
                                                        value: f32) {
     use style::properties::{PropertyDeclaration, LonghandId};
     use style::properties::longhands::border_spacing::SpecifiedValue as BorderSpacing;
     use style::properties::longhands::height::SpecifiedValue as Height;
     use style::properties::longhands::width::SpecifiedValue as Width;
-    use style::values::specified::BorderWidth;
+    use style::values::specified::BorderSideWidth;
     use style::values::specified::MozLength;
     use style::values::specified::length::{NoCalcLength, LengthOrPercentage};
 
     let long = get_longhand_from_id!(property);
     let nocalc = NoCalcLength::from_px(value);
 
     let prop = match_wrap_declared! { long,
         Height => Height(MozLength::LengthOrPercentageOrAuto(nocalc.into())),
         Width => Width(MozLength::LengthOrPercentageOrAuto(nocalc.into())),
-        BorderTopWidth => BorderWidth::Width(nocalc.into()),
-        BorderRightWidth => BorderWidth::Width(nocalc.into()),
-        BorderBottomWidth => BorderWidth::Width(nocalc.into()),
-        BorderLeftWidth => BorderWidth::Width(nocalc.into()),
+        BorderTopWidth => BorderSideWidth::Length(nocalc.into()),
+        BorderRightWidth => BorderSideWidth::Length(nocalc.into()),
+        BorderBottomWidth => BorderSideWidth::Length(nocalc.into()),
+        BorderLeftWidth => BorderSideWidth::Length(nocalc.into()),
         MarginTop => nocalc.into(),
         MarginRight => nocalc.into(),
         MarginBottom => nocalc.into(),
         MarginLeft => nocalc.into(),
         PaddingTop => nocalc.into(),
         PaddingRight => nocalc.into(),
         PaddingBottom => nocalc.into(),
         PaddingLeft => nocalc.into(),
--- a/servo/tests/unit/style/parsing/inherited_text.rs
+++ b/servo/tests/unit/style/parsing/inherited_text.rs
@@ -1,34 +1,34 @@
 /* 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/. */
 
 use parsing::parse;
+use style::values::generics::text::Spacing;
 
 #[test]
 fn negative_letter_spacing_should_parse_properly() {
     use style::properties::longhands::letter_spacing;
-    use style::properties::longhands::letter_spacing::SpecifiedValue;
     use style::values::specified::length::{Length, NoCalcLength, FontRelativeLength};
 
     let negative_value = parse_longhand!(letter_spacing, "-0.5em");
-    let expected = SpecifiedValue::Specified(Length::NoCalc(NoCalcLength::FontRelative(FontRelativeLength::Em(-0.5))));
+    let expected = Spacing::Value(Length::NoCalc(NoCalcLength::FontRelative(FontRelativeLength::Em(-0.5))));
     assert_eq!(negative_value, expected);
 }
 
 #[test]
 fn negative_word_spacing_should_parse_properly() {
     use style::properties::longhands::word_spacing;
-    use style::properties::longhands::word_spacing::SpecifiedValue;
     use style::values::specified::length::{NoCalcLength, LengthOrPercentage, FontRelativeLength};
 
     let negative_value = parse_longhand!(word_spacing, "-0.5em");
-    let expected = SpecifiedValue::Specified(LengthOrPercentage::Length(NoCalcLength::FontRelative(
-                                             FontRelativeLength::Em(-0.5))));
+    let expected = Spacing::Value(LengthOrPercentage::Length(
+        NoCalcLength::FontRelative(FontRelativeLength::Em(-0.5))
+    ));
     assert_eq!(negative_value, expected);
 }
 
 #[test]
 fn text_emphasis_style_longhand_should_parse_properly() {
     use style::properties::longhands::text_emphasis_style;
     use style::properties::longhands::text_emphasis_style::{ShapeKeyword, SpecifiedValue, KeywordValue};
 
--- a/servo/tests/unit/style/properties/serialization.rs
+++ b/servo/tests/unit/style/properties/serialization.rs
@@ -4,17 +4,17 @@
 
 use properties::parse;
 use style::computed_values::display::T::inline_block;
 use style::properties::{PropertyDeclaration, Importance, PropertyId};
 use style::properties::longhands::outline_color::computed_value::T as ComputedColor;
 use style::properties::parse_property_declaration_list;
 use style::values::{RGBA, Auto};
 use style::values::CustomIdent;
-use style::values::specified::{BorderStyle, BorderWidth, CSSColor, Length, LengthOrPercentage};
+use style::values::specified::{BorderStyle, BorderSideWidth, CSSColor, Length, LengthOrPercentage};
 use style::values::specified::{LengthOrPercentageOrAuto, LengthOrPercentageOrAutoOrContent};
 use style::values::specified::{NoCalcLength, PositionComponent};
 use style::values::specified::position::Y;
 use style::values::specified::url::SpecifiedUrl;
 use style_traits::ToCss;
 use stylesheets::block_from;
 
 #[test]
@@ -216,18 +216,18 @@ mod shorthand_serialization {
 
           let solid = BorderStyle::solid;
 
           properties.push(PropertyDeclaration::BorderTopStyle(solid.clone()));
           properties.push(PropertyDeclaration::BorderRightStyle(solid.clone()));
           properties.push(PropertyDeclaration::BorderBottomStyle(solid.clone()));
           properties.push(PropertyDeclaration::BorderLeftStyle(solid.clone()));
 
-          let px_30 = BorderWidth::from_length(Length::from_px(30f32));
-          let px_10 = BorderWidth::from_length(Length::from_px(10f32));
+          let px_30 = BorderSideWidth::Length(Length::from_px(30f32));
+          let px_10 = BorderSideWidth::Length(Length::from_px(10f32));
 
           properties.push(PropertyDeclaration::BorderTopWidth(px_30.clone()));
           properties.push(PropertyDeclaration::BorderRightWidth(px_30.clone()));
           properties.push(PropertyDeclaration::BorderBottomWidth(px_30.clone()));
           properties.push(PropertyDeclaration::BorderLeftWidth(px_10.clone()));
 
           let blue = CSSColor {
               parsed: ComputedColor::RGBA(RGBA::new(0, 0, 255, 255)),
@@ -250,17 +250,17 @@ mod shorthand_serialization {
 
           let solid = BorderStyle::solid;
 
           properties.push(PropertyDeclaration::BorderTopStyle(solid.clone()));
           properties.push(PropertyDeclaration::BorderRightStyle(solid.clone()));
           properties.push(PropertyDeclaration::BorderBottomStyle(solid.clone()));
           properties.push(PropertyDeclaration::BorderLeftStyle(solid.clone()));
 
-          let px_30 = BorderWidth::from_length(Length::from_px(30f32));
+          let px_30 = BorderSideWidth::Length(Length::from_px(30f32));
 
           properties.push(PropertyDeclaration::BorderTopWidth(px_30.clone()));
           properties.push(PropertyDeclaration::BorderRightWidth(px_30.clone()));
           properties.push(PropertyDeclaration::BorderBottomWidth(px_30.clone()));
           properties.push(PropertyDeclaration::BorderLeftWidth(px_30.clone()));
 
           let blue = CSSColor {
               parsed: ComputedColor::RGBA(RGBA::new(0, 0, 255, 255)),
@@ -290,39 +290,39 @@ mod shorthand_serialization {
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "padding: 10px 15px;");
         }
 
         #[test]
         fn border_width_should_serialize_correctly() {
             let mut properties = Vec::new();
 
-            let top_px = BorderWidth::from_length(Length::from_px(10f32));
-            let bottom_px = BorderWidth::from_length(Length::from_px(10f32));
+            let top_px = BorderSideWidth::Length(Length::from_px(10f32));
+            let bottom_px = BorderSideWidth::Length(Length::from_px(10f32));
 
-            let right_px = BorderWidth::from_length(Length::from_px(15f32));
-            let left_px = BorderWidth::from_length(Length::from_px(15f32));
+            let right_px = BorderSideWidth::Length(Length::from_px(15f32));
+            let left_px = BorderSideWidth::Length(Length::from_px(15f32));
 
             properties.push(PropertyDeclaration::BorderTopWidth(top_px));
             properties.push(PropertyDeclaration::BorderRightWidth(right_px));
             properties.push(PropertyDeclaration::BorderBottomWidth(bottom_px));
             properties.push(PropertyDeclaration::BorderLeftWidth(left_px));
 
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "border-width: 10px 15px;");
         }
 
         #[test]
         fn border_width_with_keywords_should_serialize_correctly() {
             let mut properties = Vec::new();
 
-            let top_px = BorderWidth::Thin;
-            let right_px = BorderWidth::Medium;
-            let bottom_px = BorderWidth::Thick;
-            let left_px = BorderWidth::from_length(Length::from_px(15f32));
+            let top_px = BorderSideWidth::Thin;
+            let right_px = BorderSideWidth::Medium;
+            let bottom_px = BorderSideWidth::Thick;
+            let left_px = BorderSideWidth::Length(Length::from_px(15f32));
 
             properties.push(PropertyDeclaration::BorderTopWidth(top_px));
             properties.push(PropertyDeclaration::BorderRightWidth(right_px));
             properties.push(PropertyDeclaration::BorderBottomWidth(bottom_px));
             properties.push(PropertyDeclaration::BorderLeftWidth(left_px));
 
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "border-width: thin medium thick 15px;");
@@ -398,33 +398,33 @@ mod shorthand_serialization {
 
         // we can use border-top as a base to test out the different combinations
         // but afterwards, we only need to to one test per "directional border shorthand"
 
         #[test]
         fn directional_border_should_show_all_properties_when_values_are_set() {
             let mut properties = Vec::new();
 
-            let width = BorderWidth::from_length(Length::from_px(4f32));
+            let width = BorderSideWidth::Length(Length::from_px(4f32));
             let style = BorderStyle::solid;
             let color = CSSColor {
                 parsed: ComputedColor::RGBA(RGBA::new(255, 0, 0, 255)),
                 authored: None
             };
 
             properties.push(PropertyDeclaration::BorderTopWidth(width));
             properties.push(PropertyDeclaration::BorderTopStyle(style));
             properties.push(PropertyDeclaration::BorderTopColor(color));
 
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "border-top: 4px solid rgb(255, 0, 0);");
         }
 
-        fn get_border_property_values() -> (BorderWidth, BorderStyle, CSSColor) {
-            (BorderWidth::from_length(Length::from_px(4f32)),
+        fn get_border_property_values() -> (BorderSideWidth, BorderStyle, CSSColor) {
+            (BorderSideWidth::Length(Length::from_px(4f32)),
              BorderStyle::solid,
              CSSColor::currentcolor())
         }
 
         #[test]
         fn border_top_should_serialize_correctly() {
             let mut properties = Vec::new();
             let (width, style, color) = get_border_property_values();
@@ -518,25 +518,24 @@ mod shorthand_serialization {
             properties.push(PropertyDeclaration::ListStyleType(style_type));
 
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "list-style: inside url(\"http://servo/test.png\") disc;");
         }
     }
 
     mod outline {
-        use style::properties::longhands::outline_width::SpecifiedValue as WidthContainer;
         use style::values::Either;
         use super::*;
 
         #[test]
         fn outline_should_show_all_properties_when_set() {
             let mut properties = Vec::new();
 
-            let width = WidthContainer(Length::from_px(4f32));
+            let width = BorderSideWidth::Length(Length::from_px(4f32));
             let style = Either::Second(BorderStyle::solid);
             let color = CSSColor {
                 parsed: ComputedColor::RGBA(RGBA::new(255, 0, 0, 255)),
                 authored: None
             };
 
             properties.push(PropertyDeclaration::OutlineWidth(width));
             properties.push(PropertyDeclaration::OutlineStyle(style));
@@ -545,17 +544,17 @@ mod shorthand_serialization {
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "outline: 4px solid rgb(255, 0, 0);");
         }
 
         #[test]
         fn outline_should_serialize_correctly_when_style_is_auto() {
             let mut properties = Vec::new();
 
-            let width = WidthContainer(Length::from_px(4f32));
+            let width = BorderSideWidth::Length(Length::from_px(4f32));
             let style = Either::First(Auto);
             let color = CSSColor {
                 parsed: ComputedColor::RGBA(RGBA::new(255, 0, 0, 255)),
                 authored: None
             };
             properties.push(PropertyDeclaration::OutlineWidth(width));
             properties.push(PropertyDeclaration::OutlineStyle(style));
             properties.push(PropertyDeclaration::OutlineColor(color));
--- a/servo/tests/unit/style/properties/viewport.rs
+++ b/servo/tests/unit/style/properties/viewport.rs
@@ -1,27 +1,27 @@
 /* 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/. */
 
 use app_units::Au;
 use style::properties::PropertyDeclaration;
-use style::properties::longhands::border_top_width;
 use style::values::specified::{AbsoluteLength, Length, NoCalcLength, ViewportPercentageLength};
+use style::values::specified::border::BorderSideWidth;
 use style_traits::HasViewportPercentage;
 
 #[test]
 fn has_viewport_percentage_for_specified_value() {
     //TODO: test all specified value with a HasViewportPercentage impl
     let pvw = PropertyDeclaration::BorderTopWidth(
-        border_top_width::SpecifiedValue::from_length(
+        BorderSideWidth::Length(
             Length::NoCalc(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(100.)))
         )
     );
     assert!(pvw.has_viewport_percentage());
 
     let pabs = PropertyDeclaration::BorderTopWidth(
-        border_top_width::SpecifiedValue::from_length(
+        BorderSideWidth::Length(
             Length::NoCalc(NoCalcLength::Absolute(AbsoluteLength::Px(Au(100).to_f32_px())))
         )
     );
     assert!(!pabs.has_viewport_percentage());
 }