Bug 1454883: Update font-stretch to css-fonts-4. r=xidorn
authorEmilio Cobos Álvarez <emilio@crisal.io>
Tue, 17 Apr 2018 16:44:17 +0200
changeset 468635 87886520c59971868b003fb1a21f1329e3a53b37
parent 468634 d1479b21a2848f1ed2b73fb54202b5ba5e042ef3
child 468636 6f1885763b299bd414b4e6fd87395323f024e66d
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersxidorn
bugs1454883, 1436048
milestone61.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
Bug 1454883: Update font-stretch to css-fonts-4. r=xidorn These won't "just work", pending changes from bug 1436048 to use a floating point representation for those. MozReview-Commit-ID: Bi5iTdFreMA
servo/components/style/font_face.rs
servo/components/style/gecko/rules.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/components/style/values/computed/font.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/computed/percentage.rs
servo/components/style/values/specified/font.rs
servo/components/style/values/specified/mod.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/style/font_face.rs
+++ b/servo/components/style/font_face.rs
@@ -4,17 +4,17 @@
 
 //! The [`@font-face`][ff] at-rule.
 //!
 //! [ff]: https://drafts.csswg.org/css-fonts/#at-font-face-rule
 
 #![deny(missing_docs)]
 
 #[cfg(feature = "gecko")]
-use computed_values::{font_stretch, font_style};
+use computed_values::font_style;
 use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser};
 use cssparser::{CowRcStr, SourceLocation};
 #[cfg(feature = "gecko")]
 use cssparser::UnicodeRange;
 use error_reporting::{ContextualParseError, ParseErrorReporter};
 use parser::{Parse, ParserContext, ParserErrorContext};
 #[cfg(feature = "gecko")]
 use properties::longhands::font_language_override;
@@ -22,17 +22,19 @@ use selectors::parser::SelectorParseErro
 use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
 use std::fmt::{self, Write};
 use str::CssStringWriter;
 use style_traits::{Comma, CssWriter, OneOrMoreSeparated, ParseError};
 use style_traits::{StyleParseErrorKind, ToCss};
 use style_traits::values::SequenceWriter;
 use values::computed::font::FamilyName;
 #[cfg(feature = "gecko")]
-use values::specified::font::{AbsoluteFontWeight, SpecifiedFontFeatureSettings, SpecifiedFontVariationSettings};
+use values::specified::font::{AbsoluteFontWeight, FontStretch as SpecifiedFontStretch};
+#[cfg(feature = "gecko")]
+use values::specified::font::{SpecifiedFontFeatureSettings, SpecifiedFontVariationSettings};
 use values::specified::url::SpecifiedUrl;
 
 /// A source for a font-face rule.
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 #[derive(Clone, Debug, Eq, PartialEq, ToCss)]
 pub enum Source {
     /// A `url()` source.
     Url(UrlSource),
@@ -105,16 +107,34 @@ impl Parse for FontWeight {
     ) -> Result<Self, ParseError<'i>> {
         let first = AbsoluteFontWeight::parse(context, input)?;
         let second =
             input.try(|input| AbsoluteFontWeight::parse(context, input)).ok();
         Ok(FontWeight(first, second))
     }
 }
 
+/// The font-stretch descriptor:
+///
+/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-stretch
+#[derive(Clone, Debug, PartialEq, ToCss)]
+pub struct FontStretch(pub SpecifiedFontStretch, pub Option<SpecifiedFontStretch>);
+
+impl Parse for FontStretch {
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        let first = SpecifiedFontStretch::parse(context, input)?;
+        let second =
+            input.try(|input| SpecifiedFontStretch::parse(context, input)).ok();
+        Ok(FontStretch(first, second))
+    }
+}
+
 /// Parse the block inside a `@font-face` rule.
 ///
 /// Note that the prelude parsing code lives in the `stylesheets` module.
 pub fn parse_font_face_block<R>(
     context: &ParserContext,
     error_context: &ParserErrorContext<R>,
     input: &mut Parser,
     location: SourceLocation,
@@ -381,26 +401,26 @@ font_face_descriptors! {
     mandatory descriptors = [
         /// The name of this font face
         "font-family" family / mFamily: FamilyName,
 
         /// The alternative sources for this font face.
         "src" sources / mSrc: Vec<Source>,
     ]
     optional descriptors = [
-        /// The style of this font face
+        /// The style of this font face.
         "font-style" style / mStyle: font_style::T,
 
-        /// The weight of this font face
+        /// The weight of this font face.
         "font-weight" weight / mWeight: FontWeight,
 
-        /// The stretch of this font face
-        "font-stretch" stretch / mStretch: font_stretch::T,
+        /// The stretch of this font face.
+        "font-stretch" stretch / mStretch: FontStretch,
 
-        /// The display of this font face
+        /// The display of this font face.
         "font-display" display / mDisplay: FontDisplay,
 
         /// The ranges of code points outside of which this font face should not be used.
         "unicode-range" unicode_range / mUnicodeRange: Vec<UnicodeRange>,
 
         /// The feature settings of this font face.
         "font-feature-settings" feature_settings / mFontFeatureSettings: SpecifiedFontFeatureSettings,
 
--- a/servo/components/style/gecko/rules.rs
+++ b/servo/components/style/gecko/rules.rs
@@ -1,34 +1,52 @@
 /* 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/. */
 
 //! Bindings for CSS Rule objects
 
 use byteorder::{BigEndian, WriteBytesExt};
-use computed_values::{font_stretch, font_style};
+use computed_values::font_style::T as ComputedFontStyle;
 use counter_style::{self, CounterBound};
 use cssparser::UnicodeRange;
-use font_face::{FontDisplay, FontWeight, Source};
+use font_face::{FontDisplay, FontWeight, FontStretch, Source};
 use gecko_bindings::structs::{self, nsCSSValue};
 use gecko_bindings::sugar::ns_css_value::ToNsCssValue;
 use properties::longhands::font_language_override;
 use std::str;
 use values::computed::font::FamilyName;
 use values::generics::font::FontTag;
-use values::specified::font::AbsoluteFontWeight;
+use values::specified::font::{AbsoluteFontWeight, FontStretch as SpecifiedFontStretch};
 use values::specified::font::{SpecifiedFontFeatureSettings, SpecifiedFontVariationSettings};
 
 impl<'a> ToNsCssValue for &'a FamilyName {
     fn convert(self, nscssvalue: &mut nsCSSValue) {
         nscssvalue.set_string_from_atom(&self.name)
     }
 }
 
+impl<'a> ToNsCssValue for &'a SpecifiedFontStretch {
+    fn convert(self, nscssvalue: &mut nsCSSValue) {
+        use values::specified::font::FontStretchKeyword;
+        match *self {
+            SpecifiedFontStretch::Stretch(ref p) => nscssvalue.set_number(p.get()),
+            SpecifiedFontStretch::Keyword(ref kw) => {
+                // TODO(emilio): Use this branch instead.
+                if false {
+                    nscssvalue.set_number(kw.compute().0)
+                } else {
+                    nscssvalue.set_enum(FontStretchKeyword::gecko_keyword(kw.compute().0) as i32)
+                }
+            }
+            SpecifiedFontStretch::System(..) => unreachable!(),
+        }
+    }
+}
+
 impl<'a> ToNsCssValue for &'a AbsoluteFontWeight {
     fn convert(self, nscssvalue: &mut nsCSSValue) {
         nscssvalue.set_font_weight(self.compute().0)
     }
 }
 
 impl ToNsCssValue for FontTag {
     fn convert(self, nscssvalue: &mut nsCSSValue) {
@@ -63,89 +81,83 @@ impl<'a> ToNsCssValue for &'a SpecifiedF
         nscssvalue.set_pair_list(self.0.iter().map(|entry| {
             let mut value = nsCSSValue::null();
             value.set_number(entry.value.into());
             (entry.tag.into(), value)
         }))
     }
 }
 
-impl<'a> ToNsCssValue for &'a FontWeight {
-    fn convert(self, nscssvalue: &mut nsCSSValue) {
-        let FontWeight(ref first, ref second) = *self;
+macro_rules! descriptor_range_conversion {
+    ($name:ident) => {
+        impl<'a> ToNsCssValue for &'a $name {
+            fn convert(self, nscssvalue: &mut nsCSSValue) {
+                let $name(ref first, ref second) = *self;
+                let second = match *second {
+                    None => {
+                        nscssvalue.set_from(first);
+                        return;
+                    }
+                    Some(ref second) => second,
+                };
 
-        let second = match *second {
-            None => {
-                nscssvalue.set_from(first);
-                return;
+                let mut a = nsCSSValue::null();
+                let mut b = nsCSSValue::null();
+
+                a.set_from(first);
+                b.set_from(second);
+
+                nscssvalue.set_pair(&a, &b);
             }
-            Some(ref second) => second,
-        };
-
-        let mut a = nsCSSValue::null();
-        let mut b = nsCSSValue::null();
-
-        a.set_from(first);
-        b.set_from(second);
-
-        nscssvalue.set_pair(&a, &b);
+        }
     }
 }
 
+descriptor_range_conversion!(FontWeight);
+descriptor_range_conversion!(FontStretch);
+
 impl<'a> ToNsCssValue for &'a font_language_override::SpecifiedValue {
     fn convert(self, nscssvalue: &mut nsCSSValue) {
         match *self {
             font_language_override::SpecifiedValue::Normal => nscssvalue.set_normal(),
             font_language_override::SpecifiedValue::Override(ref lang) => {
                 nscssvalue.set_string(&*lang)
             },
             // This path is unreachable because the descriptor is only specified by the user.
             font_language_override::SpecifiedValue::System(_) => unreachable!(),
         }
     }
 }
 
 macro_rules! map_enum {
     (
         $(
-            $prop:ident {
+            $ty:ident {
                 $($servo:ident => $gecko:ident,)+
             }
         )+
     ) => {
         $(
-            impl<'a> ToNsCssValue for &'a $prop::T {
+            impl<'a> ToNsCssValue for &'a $ty {
                 fn convert(self, nscssvalue: &mut nsCSSValue) {
                     nscssvalue.set_enum(match *self {
-                        $( $prop::T::$servo => structs::$gecko as i32, )+
+                        $( $ty::$servo => structs::$gecko as i32, )+
                     })
                 }
             }
         )+
     }
 }
 
 map_enum! {
-    font_style {
+    ComputedFontStyle {
         Normal => NS_FONT_STYLE_NORMAL,
         Italic => NS_FONT_STYLE_ITALIC,
         Oblique => NS_FONT_STYLE_OBLIQUE,
     }
-
-    font_stretch {
-        Normal          => NS_FONT_STRETCH_NORMAL,
-        UltraCondensed  => NS_FONT_STRETCH_ULTRA_CONDENSED,
-        ExtraCondensed  => NS_FONT_STRETCH_EXTRA_CONDENSED,
-        Condensed       => NS_FONT_STRETCH_CONDENSED,
-        SemiCondensed   => NS_FONT_STRETCH_SEMI_CONDENSED,
-        SemiExpanded    => NS_FONT_STRETCH_SEMI_EXPANDED,
-        Expanded        => NS_FONT_STRETCH_EXPANDED,
-        ExtraExpanded   => NS_FONT_STRETCH_EXTRA_EXPANDED,
-        UltraExpanded   => NS_FONT_STRETCH_ULTRA_EXPANDED,
-    }
 }
 
 impl<'a> ToNsCssValue for &'a Vec<Source> {
     fn convert(self, nscssvalue: &mut nsCSSValue) {
         let src_len = self.iter().fold(0, |acc, src| {
             acc + match *src {
                 // Each format hint takes one position in the array of mSrc.
                 Source::Url(ref url) => url.format_hints.len() + 1,
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -2596,25 +2596,40 @@ fn static_assert() {
                 kw: kw,
                 factor: self.gecko.mFontSizeFactor,
                 offset: Au(self.gecko.mFontSizeOffset).into()
             })
         }
     }
 
     pub fn set_font_weight(&mut self, v: longhands::font_weight::computed_value::T) {
-        unsafe { Gecko_FontWeight_SetFloat(&mut self.gecko.mFont.weight, v.0 as f32) };
+        unsafe { Gecko_FontWeight_SetFloat(&mut self.gecko.mFont.weight, v.0) };
     }
     ${impl_simple_copy('font_weight', 'mFont.weight')}
 
     pub fn clone_font_weight(&self) -> longhands::font_weight::computed_value::T {
         let weight: f32 = unsafe { Gecko_FontWeight_ToFloat(self.gecko.mFont.weight) };
         longhands::font_weight::computed_value::T(weight)
     }
 
+    pub fn set_font_stretch(&mut self, v: longhands::font_stretch::computed_value::T) {
+        unsafe { bindings::Gecko_FontStretch_SetFloat(&mut self.gecko.mFont.stretch, (v.0).0) };
+    }
+    ${impl_simple_copy('font_stretch', 'mFont.stretch')}
+
+    pub fn clone_font_stretch(&self) -> longhands::font_stretch::computed_value::T {
+        use values::computed::Percentage;
+        use values::generics::NonNegative;
+
+        let stretch = self.gecko.mFont.stretch as f32 / 100.;
+        debug_assert!(stretch >= 0.);
+
+        NonNegative(Percentage(stretch))
+    }
+
     ${impl_simple_type_with_conversion("font_synthesis", "mFont.synthesis")}
 
     pub fn set_font_size_adjust(&mut self, v: longhands::font_size_adjust::computed_value::T) {
         use properties::longhands::font_size_adjust::computed_value::T;
         match v {
             T::None => self.gecko.mFont.sizeAdjust = -1.0 as f32,
             T::Number(n) => self.gecko.mFont.sizeAdjust = n,
         }
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -14,17 +14,16 @@ use cssparser::Parser;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::RawGeckoGfxMatrix4x4;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID;
 #[cfg(feature = "gecko")] use gecko_bindings::sugar::ownership::{HasFFI, HasSimpleFFI};
 use itertools::{EitherOrBoth, Itertools};
 use num_traits::Zero;
 use properties::{CSSWideKeyword, PropertyDeclaration};
 use properties::longhands;
 use properties::longhands::font_weight::computed_value::T as FontWeight;
-use properties::longhands::font_stretch::computed_value::T as FontStretch;
 use properties::longhands::visibility::computed_value::T as Visibility;
 use properties::PropertyId;
 use properties::{LonghandId, ShorthandId};
 use servo_arc::Arc;
 use smallvec::SmallVec;
 use std::{cmp, ptr};
 use std::mem::{self, ManuallyDrop};
 #[cfg(feature = "gecko")] use hash::FnvHashMap;
@@ -875,71 +874,16 @@ impl ToAnimatedZero for MaxLength {
 
 impl ToAnimatedZero for FontWeight {
     #[inline]
     fn to_animated_zero(&self) -> Result<Self, ()> {
         Ok(FontWeight::normal())
     }
 }
 
-/// <https://drafts.csswg.org/css-fonts/#font-stretch-prop>
-impl Animate for FontStretch {
-    #[inline]
-    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
-        let from = f64::from(*self);
-        let to = f64::from(*other);
-        let normal = f64::from(FontStretch::Normal);
-        let (this_weight, other_weight) = procedure.weights();
-        let result = (from - normal) * this_weight + (to - normal) * other_weight + normal;
-        Ok(result.into())
-    }
-}
-
-impl ComputeSquaredDistance for FontStretch {
-    #[inline]
-    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
-        f64::from(*self).compute_squared_distance(&(*other).into())
-    }
-}
-
-impl ToAnimatedZero for FontStretch {
-    #[inline]
-    fn to_animated_zero(&self) -> Result<Self, ()> { Err(()) }
-}
-
-/// We should treat font stretch as real number in order to interpolate this property.
-/// <https://drafts.csswg.org/css-fonts-3/#font-stretch-animation>
-impl From<FontStretch> for f64 {
-    fn from(stretch: FontStretch) -> f64 {
-        use self::FontStretch::*;
-        match stretch {
-            UltraCondensed => 1.0,
-            ExtraCondensed => 2.0,
-            Condensed => 3.0,
-            SemiCondensed => 4.0,
-            Normal => 5.0,
-            SemiExpanded => 6.0,
-            Expanded => 7.0,
-            ExtraExpanded => 8.0,
-            UltraExpanded => 9.0,
-        }
-    }
-}
-
-impl Into<FontStretch> for f64 {
-    fn into(self) -> FontStretch {
-        use properties::longhands::font_stretch::computed_value::T::*;
-        let index = (self + 0.5).floor().min(9.0).max(1.0);
-        static FONT_STRETCH_ENUM_MAP: [FontStretch; 9] =
-            [ UltraCondensed, ExtraCondensed, Condensed, SemiCondensed, Normal,
-              SemiExpanded, Expanded, ExtraExpanded, UltraExpanded ];
-        FONT_STRETCH_ENUM_MAP[(index - 1.0) as usize]
-    }
-}
-
 /// <https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def>
 impl Animate for FontVariationSettings {
     #[inline]
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         FontSettingTagIter::new(self, other)?
             .map(|r| r.and_then(|(st, ot)| st.animate(&ot, procedure)))
             .collect::<Result<Vec<ComputedVariationValue>, ()>>()
             .map(|v| GenericFontSettings(v.into_boxed_slice()))
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -76,27 +76,26 @@
 ${helpers.predefined_type("font-synthesis",
                           "FontSynthesis",
                           products="gecko",
                           initial_value="specified::FontSynthesis::get_initial_value()",
                           animation_value_type="discrete",
                           flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
                           spec="https://drafts.csswg.org/css-fonts/#propdef-font-synthesis")}
 
-${helpers.single_keyword_system("font-stretch",
-                                "normal ultra-condensed extra-condensed condensed \
-                                 semi-condensed semi-expanded expanded extra-expanded \
-                                 ultra-expanded",
-                                gecko_ffi_name="mFont.stretch",
-                                gecko_constant_prefix="NS_FONT_STRETCH",
-                                cast_type='i16',
-                                spec="https://drafts.csswg.org/css-fonts/#propdef-font-stretch",
-                                flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
-                                animation_value_type="ComputedValue",
-                                servo_restyle_damage="rebuild_and_reflow")}
+${helpers.predefined_type(
+    "font-stretch",
+    "FontStretch",
+    initial_value="computed::NonNegativePercentage::hundred()",
+    initial_specified_value="specified::FontStretch::normal()",
+    animation_value_type="Percentage",
+    flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
+    spec="https://drafts.csswg.org/css-fonts/#propdef-font-stretch",
+    servo_restyle_damage="rebuild_and_reflow",
+)}
 
 ${helpers.single_keyword_system("font-kerning",
                                 "auto none normal",
                                 products="gecko",
                                 gecko_ffi_name="mFont.kerning",
                                 gecko_constant_prefix="NS_FONT_KERNING",
                                 spec="https://drafts.csswg.org/css-fonts/#propdef-font-kerning",
                                 flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
@@ -285,21 +284,21 @@ https://drafts.csswg.org/css-fonts-4/#lo
         use style_traits::ParseError;
         use values::computed::{ToComputedValue, Context};
 
         <%
             system_fonts = """caption icon menu message-box small-caption status-bar
                               -moz-window -moz-document -moz-workspace -moz-desktop
                               -moz-info -moz-dialog -moz-button -moz-pull-down-menu
                               -moz-list -moz-field""".split()
-            kw_font_props = """font_style font_variant_caps font_stretch
+            kw_font_props = """font_style font_variant_caps
                                font_kerning font_variant_position font_variant_ligatures
                                font_variant_east_asian font_variant_numeric
                                font_optical_sizing""".split()
-            kw_cast = """font_style font_variant_caps font_stretch
+            kw_cast = """font_style font_variant_caps
                          font_kerning font_variant_position
                          font_optical_sizing""".split()
         %>
         #[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToCss)]
         pub enum SystemFont {
             % for font in system_fonts:
                 ${to_camel_case(font)},
             % endfor
@@ -325,17 +324,19 @@ https://drafts.csswg.org/css-fonts-4/#lo
 
         impl ToComputedValue for SystemFont {
             type ComputedValue = ComputedSystemFont;
 
             fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
                 use gecko_bindings::bindings;
                 use gecko_bindings::structs::{LookAndFeel_FontID, nsFont};
                 use std::mem;
+                use values::computed::Percentage;
                 use values::computed::font::{FontSize, FontFamilyList};
+                use values::generics::NonNegative;
 
                 let id = match *self {
                     % for font in system_fonts:
                         SystemFont::${to_camel_case(font)} => {
                             LookAndFeel_FontID::eFont_${to_camel_case(font.replace("-moz-", ""))}
                         }
                     % endfor
                 };
@@ -344,28 +345,30 @@ https://drafts.csswg.org/css-fonts-4/#lo
                 unsafe {
                     bindings::Gecko_nsFont_InitSystem(
                         &mut system,
                         id as i32,
                         cx.style().get_font().gecko(),
                         cx.device().pres_context()
                     )
                 }
-                let weight = longhands::font_weight::computed_value::T::from_gecko_weight(system.weight);
+                let font_weight = longhands::font_weight::computed_value::T::from_gecko_weight(system.weight);
+                let font_stretch = NonNegative(Percentage(system.stretch as f32));
                 let ret = ComputedSystemFont {
                     font_family: longhands::font_family::computed_value::T(
                         FontFamilyList(
                             unsafe { system.fontlist.mFontlist.mBasePtr.to_safe() }
                         )
                     ),
                     font_size: FontSize {
                             size: Au(system.size).into(),
                             keyword_info: None
                     },
-                    font_weight: weight,
+                    font_weight,
+                    font_stretch,
                     font_size_adjust: longhands::font_size_adjust::computed_value
                                                ::T::from_gecko_adjust(system.sizeAdjust),
                     % for kwprop in kw_font_props:
                         ${kwprop}: longhands::${kwprop}::computed_value::T::from_gecko_keyword(
                             system.${to_camel_case_lower(kwprop.replace('font_', ''))}
                             % if kwprop in kw_cast:
                                 as u32
                             % endif
--- a/servo/components/style/values/computed/font.rs
+++ b/servo/components/style/values/computed/font.rs
@@ -25,16 +25,17 @@ use values::animated::{ToAnimatedValue, 
 use values::computed::{Context, Integer, NonNegativeLength, Number, ToComputedValue};
 use values::generics::font::{FeatureTagValue, FontSettings};
 use values::generics::font::{KeywordInfo as GenericKeywordInfo, VariationValue};
 use values::specified::font::{self as specified, MIN_FONT_WEIGHT, MAX_FONT_WEIGHT};
 use values::specified::length::{FontBaseSize, NoCalcLength};
 
 pub use values::computed::Length as MozScriptMinSize;
 pub use values::specified::font::{FontSynthesis, MozScriptSizeMultiplier, XLang, XTextZoom};
+pub use values::computed::NonNegativePercentage as FontStretch;
 
 /// A value for the font-weight property per:
 ///
 /// https://drafts.csswg.org/css-fonts-4/#propdef-font-weight
 ///
 /// This is effectively just a `Number`.
 #[derive(Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq,
          ToCss)]
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -34,17 +34,17 @@ pub use properties::animated_properties:
 #[cfg(feature = "gecko")]
 pub use self::align::{AlignContent, AlignItems, JustifyContent, JustifyItems, SelfAlignment};
 #[cfg(feature = "gecko")]
 pub use self::align::{AlignSelf, JustifySelf};
 pub use self::angle::Angle;
 pub use self::background::{BackgroundRepeat, BackgroundSize};
 pub use self::border::{BorderImageRepeat, BorderImageSideWidth, BorderImageSlice, BorderImageWidth};
 pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing};
-pub use self::font::{FontSize, FontSizeAdjust, FontSynthesis, FontVariantAlternates, FontWeight};
+pub use self::font::{FontSize, FontSizeAdjust, FontStretch, FontSynthesis, FontVariantAlternates, FontWeight};
 pub use self::font::{FontFamily, FontLanguageOverride, FontVariantEastAsian, FontVariationSettings};
 pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumeric};
 pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
 pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display};
 pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective};
 pub use self::box_::{ScrollSnapType, TouchAction, VerticalAlign, WillChange};
 pub use self::color::{Color, ColorPropertyValue, RGBAColor};
 pub use self::column::ColumnCount;
@@ -61,17 +61,17 @@ pub use super::specified::{BorderStyle, 
 pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNumber, LengthOrPercentage};
 pub use self::length::{LengthOrPercentageOrAuto, LengthOrPercentageOrNone, MaxLength, MozLength};
 pub use self::length::{CSSPixelLength, ExtremumLength, NonNegativeLength};
 pub use self::length::{NonNegativeLengthOrPercentage, NonNegativeLengthOrPercentageOrAuto};
 pub use self::list::Quotes;
 #[cfg(feature = "gecko")]
 pub use self::list::ListStyleType;
 pub use self::outline::OutlineStyle;
-pub use self::percentage::Percentage;
+pub use self::percentage::{Percentage, NonNegativePercentage};
 pub use self::pointing::{CaretColor, Cursor};
 #[cfg(feature = "gecko")]
 pub use self::pointing::CursorImage;
 pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, ZIndex};
 pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind};
 pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth};
 pub use self::svg::MozContextProperties;
 pub use self::table::XSpan;
--- a/servo/components/style/values/computed/percentage.rs
+++ b/servo/components/style/values/computed/percentage.rs
@@ -1,17 +1,19 @@
 /* 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 percentages.
 
 use std::fmt;
 use style_traits::{CssWriter, ToCss};
+use values::animated::ToAnimatedValue;
 use values::{serialize_percentage, CSSFloat};
+use values::generics::NonNegative;
 
 /// A computed percentage.
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, Default, MallocSizeOf, PartialEq,
          PartialOrd, ToAnimatedZero, ToComputedValue)]
 pub struct Percentage(pub CSSFloat);
 
 impl Percentage {
@@ -43,8 +45,39 @@ impl Percentage {
 impl ToCss for Percentage {
     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
     where
         W: fmt::Write,
     {
         serialize_percentage(self.0, dest)
     }
 }
+
+/// A wrapper over a `Percentage`, whose value should be clamped to 0.
+pub type NonNegativePercentage = NonNegative<Percentage>;
+
+impl NonNegativePercentage {
+    /// 0%
+    #[inline]
+    pub fn zero() -> Self {
+        NonNegative(Percentage::zero())
+    }
+
+    /// 100%
+    #[inline]
+    pub fn hundred() -> Self {
+        NonNegative(Percentage::hundred())
+    }
+}
+
+impl ToAnimatedValue for NonNegativePercentage {
+    type AnimatedValue = Percentage;
+
+    #[inline]
+    fn to_animated_value(self) -> Self::AnimatedValue {
+        self.0
+    }
+
+    #[inline]
+    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
+        NonNegative(animated.clamp_to_non_negative())
+    }
+}
--- a/servo/components/style/values/specified/font.rs
+++ b/servo/components/style/values/specified/font.rs
@@ -12,21 +12,23 @@ use cssparser::{Parser, Token};
 use gecko_bindings::bindings;
 #[cfg(feature = "gecko")]
 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
 use parser::{Parse, ParserContext};
 use properties::longhands::system_font::SystemFont;
 use std::fmt::{self, Write};
 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
 use values::CustomIdent;
+use values::computed::Percentage as ComputedPercentage;
 use values::computed::{font as computed, Context, Length, NonNegativeLength, ToComputedValue};
 use values::computed::font::{FamilyName, FontFamilyList, SingleFontFamily};
+use values::generics::NonNegative;
 use values::generics::font::{FeatureTagValue, FontSettings, FontTag};
 use values::generics::font::{KeywordInfo as GenericKeywordInfo, KeywordSize, VariationValue};
-use values::specified::{AllowQuirks, Integer, LengthOrPercentage, NoCalcLength, Number};
+use values::specified::{AllowQuirks, Integer, LengthOrPercentage, NoCalcLength, Number, Percentage};
 use values::specified::length::{FontBaseSize, AU_PER_PT, AU_PER_PX};
 
 const DEFAULT_SCRIPT_MIN_SIZE_PT: u32 = 8;
 const DEFAULT_SCRIPT_SIZE_MULTIPLIER: f64 = 0.71;
 
 /// The minimum font-weight value per:
 ///
 /// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values
@@ -184,16 +186,130 @@ impl Parse for AbsoluteFontWeight {
 
         Ok(try_match_ident_ignore_ascii_case! { input,
             "normal" => AbsoluteFontWeight::Normal,
             "bold" => AbsoluteFontWeight::Bold,
         })
     }
 }
 
+/// A value for the `font-stretch` property.
+///
+/// https://drafts.csswg.org/css-fonts-4/#font-stretch-prop
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss)]
+pub enum FontStretch {
+    Stretch(Percentage),
+    Keyword(FontStretchKeyword),
+    System(SystemFont),
+}
+
+/// A keyword value for `font-stretch`.
+#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss)]
+#[allow(missing_docs)]
+pub enum FontStretchKeyword {
+    Normal,
+    Condensed,
+    UltraCondensed,
+    ExtraCondensed,
+    SemiCondensed,
+    SemiExpanded,
+    Expanded,
+    ExtraExpanded,
+    UltraExpanded,
+}
+
+impl FontStretchKeyword {
+    /// Resolves the value of the keyword as specified in:
+    ///
+    /// https://drafts.csswg.org/css-fonts-4/#font-stretch-prop
+    pub fn compute(&self) -> ComputedPercentage {
+        use self::FontStretchKeyword::*;
+        ComputedPercentage(match *self {
+            UltraCondensed => 0.5,
+            ExtraCondensed => 0.625,
+            Condensed => 0.75,
+            SemiCondensed => 0.875,
+            Normal => 1.,
+            SemiExpanded => 1.125,
+            Expanded => 1.25,
+            ExtraExpanded => 1.5,
+            UltraExpanded => 2.,
+        })
+    }
+}
+
+impl FontStretch {
+    /// `normal`.
+    pub fn normal() -> Self {
+        FontStretch::Keyword(FontStretchKeyword::Normal)
+    }
+
+    /// Get a specified FontStretch from a SystemFont.
+    ///
+    /// FIXME(emilio): All this system font stuff is copy-pasta. :(
+    pub fn system_font(f: SystemFont) -> Self {
+        FontStretch::System(f)
+    }
+
+    /// Retreive a SystemFont from FontStretch.
+    pub fn get_system(&self) -> Option<SystemFont> {
+        if let FontStretch::System(s) = *self {
+            Some(s)
+        } else {
+            None
+        }
+    }
+}
+
+impl Parse for FontStretch {
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        // From https://drafts.csswg.org/css-fonts-4/#font-stretch-prop:
+        //
+        //    Values less than 0% are not allowed and are treated as parse
+        //    errors.
+        if let Ok(percentage) = input.try(|input| Percentage::parse_non_negative(context, input)) {
+            return Ok(FontStretch::Stretch(percentage));
+        }
+
+        Ok(FontStretch::Keyword(FontStretchKeyword::parse(input)?))
+    }
+}
+
+impl ToComputedValue for FontStretch {
+    type ComputedValue = NonNegative<ComputedPercentage>;
+
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            FontStretch::Stretch(ref percentage) => {
+                NonNegative(percentage.to_computed_value(context))
+            },
+            FontStretch::Keyword(ref kw) => {
+                NonNegative(kw.compute())
+            },
+            #[cfg(feature = "gecko")]
+            FontStretch::System(_) => context
+                .cached_system_font
+                .as_ref()
+                .unwrap()
+                .font_stretch
+                .clone(),
+            #[cfg(not(feature = "gecko"))]
+            FontStretch::System(_) => unreachable!(),
+        }
+    }
+
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        FontStretch::Stretch(Percentage::from_computed_value(&computed.0))
+    }
+}
+
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
 /// A specified font-size value
 pub enum FontSize {
     /// A length; e.g. 10px.
     Length(LengthOrPercentage),
     /// A keyword value, along with a ratio and absolute offset.
     /// The ratio in any specified keyword value
     /// will be 1 (with offset 0), but we cascade keywordness even
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -29,17 +29,17 @@ pub use self::angle::Angle;
 pub use self::align::{AlignContent, AlignItems, AlignSelf, ContentDistribution};
 #[cfg(feature = "gecko")]
 pub use self::align::{JustifyContent, JustifyItems, JustifySelf, SelfAlignment};
 pub use self::background::{BackgroundRepeat, BackgroundSize};
 pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth};
 pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
 pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing};
 pub use self::column::ColumnCount;
-pub use self::font::{FontSize, FontSizeAdjust, FontSynthesis, FontVariantAlternates, FontWeight};
+pub use self::font::{FontSize, FontSizeAdjust, FontStretch, FontSynthesis, FontVariantAlternates, FontWeight};
 pub use self::font::{FontFamily, FontLanguageOverride, FontVariantEastAsian, FontVariationSettings};
 pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumeric};
 pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
 pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display};
 pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective};
 pub use self::box_::{ScrollSnapType, TouchAction, VerticalAlign, WillChange};
 pub use self::color::{Color, ColorPropertyValue, RGBAColor};
 pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset};
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -5402,17 +5402,17 @@ pub extern "C" fn Servo_ParseTransformIn
 pub extern "C" fn Servo_ParseFontShorthandForMatching(
     value: *const nsAString,
     data: *mut URLExtraData,
     family: *mut structs::RefPtr<structs::SharedFontList>,
     style: nsCSSValueBorrowedMut,
     stretch: nsCSSValueBorrowedMut,
     weight: nsCSSValueBorrowedMut
 ) -> bool {
-    use style::properties::longhands::{font_stretch, font_style};
+    use style::properties::longhands::font_style;
     use style::properties::shorthands::font;
     use style::values::specified::font::{FontFamily, FontWeight};
 
     let string = unsafe { (*value).to_string() };
     let mut input = ParserInput::new(&string);
     let mut parser = Parser::new(&mut input);
     let url_data = unsafe { RefPtr::from_ptr_ref(&data) };
     let context = ParserContext::new(
@@ -5433,20 +5433,21 @@ pub extern "C" fn Servo_ParseFontShortha
     match font.font_family {
         FontFamily::Values(list) => family.set_move(list.0),
         FontFamily::System(_) => return false,
     }
     style.set_from(match font.font_style {
         font_style::SpecifiedValue::Keyword(ref kw) => kw,
         font_style::SpecifiedValue::System(_) => return false,
     });
-    stretch.set_from(match font.font_stretch {
-        font_stretch::SpecifiedValue::Keyword(ref kw) => kw,
-        font_stretch::SpecifiedValue::System(_) => return false,
-    });
+
+    if font.font_stretch.get_system().is_some() {
+        return false;
+    }
+    stretch.set_from(&font.font_stretch);
 
     match font.font_weight {
         FontWeight::Absolute(w) => weight.set_font_weight(w.compute().0),
         // Resolve relative font weights against the initial of font-weight
         // (normal, which is equivalent to 400).
         FontWeight::Bolder => weight.set_enum(structs::NS_FONT_WEIGHT_BOLD as i32),
         FontWeight::Lighter => weight.set_enum(structs::NS_FONT_WEIGHT_THIN as i32),
         FontWeight::System(_) => return false,