servo: Merge #17058 - stylo: Support remaning longhands (from Manishearth:stylo-randomprops); r=emilio
authorManish Goregaokar <manishearth@gmail.com>
Sat, 27 May 2017 13:37:59 -0500
changeset 409118 e156625e27c9e51f1e648c57d5932d2772679141
parent 409117 50861508d55945e642a3803dcdf0c95084dfee66
child 409119 48e940ef1476609aaf544358a04cd8de6a334938
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #17058 - stylo: Support remaning longhands (from Manishearth:stylo-randomprops); r=emilio r=xidorn https://bugzilla.mozilla.org/show_bug.cgi?id=1367275 Source-Repo: https://github.com/servo/servo Source-Revision: 369d5cf1242632efeb56278e18a5ea25a3c933a5
servo/components/style/gecko/generated/bindings.rs
servo/components/style/gecko/generated/structs_debug.rs
servo/components/style/gecko/generated/structs_release.rs
servo/components/style/gecko/rules.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhand/column.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/components/style/properties/longhand/inherited_svg.mako.rs
servo/components/style/properties/longhand/ui.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/generics/mod.rs
servo/components/style/values/specified/length.rs
servo/tests/unit/style/parsing/font.rs
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -1129,16 +1129,24 @@ extern "C" {
     pub fn Gecko_nsStyleSVG_SetDashArrayLength(svg: *mut nsStyleSVG,
                                                len: u32);
 }
 extern "C" {
     pub fn Gecko_nsStyleSVG_CopyDashArray(dst: *mut nsStyleSVG,
                                           src: *const nsStyleSVG);
 }
 extern "C" {
+    pub fn Gecko_nsStyleSVG_SetContextPropertiesLength(svg: *mut nsStyleSVG,
+                                                       len: u32);
+}
+extern "C" {
+    pub fn Gecko_nsStyleSVG_CopyContextProperties(dst: *mut nsStyleSVG,
+                                                  src: *const nsStyleSVG);
+}
+extern "C" {
     pub fn Gecko_NewURLValue(uri: ServoBundledURI) -> *mut URLValue;
 }
 extern "C" {
     pub fn Gecko_AddRefCSSURLValueArbitraryThread(aPtr: *mut URLValue);
 }
 extern "C" {
     pub fn Gecko_ReleaseCSSURLValueArbitraryThread(aPtr: *mut URLValue);
 }
@@ -1303,16 +1311,21 @@ extern "C" {
                                           aSource: *const nsStyleFont);
 }
 extern "C" {
     pub fn Gecko_nsStyleFont_FixupNoneGeneric(font: *mut nsStyleFont,
                                               pres_context:
                                                   RawGeckoPresContextBorrowed);
 }
 extern "C" {
+    pub fn Gecko_nsStyleFont_FixupMinFontSize(font: *mut nsStyleFont,
+                                              pres_context:
+                                                  RawGeckoPresContextBorrowed);
+}
+extern "C" {
     pub fn Gecko_GetBaseSize(lang: *mut nsIAtom) -> FontSizePrefs;
 }
 extern "C" {
     pub fn Gecko_GetFontMetrics(pres_context: RawGeckoPresContextBorrowed,
                                 is_vertical: bool, font: *const nsStyleFont,
                                 font_size: nscoord, use_user_font_set: bool)
      -> GeckoFontMetrics;
 }
--- a/servo/components/style/gecko/generated/structs_debug.rs
+++ b/servo/components/style/gecko/generated/structs_debug.rs
@@ -801,16 +801,21 @@ pub mod root {
     pub const NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE: ::std::os::raw::c_uint =
         2;
     pub const NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER: ::std::os::raw::c_uint =
         3;
     pub const NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE: ::std::os::raw::c_uint =
         4;
     pub const NS_STYLE_CONTEXT_PROPERTY_FILL: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_CONTEXT_PROPERTY_STROKE: ::std::os::raw::c_uint = 2;
+    pub const NS_STYLE_WINDOW_SHADOW_NONE: ::std::os::raw::c_uint = 0;
+    pub const NS_STYLE_WINDOW_SHADOW_DEFAULT: ::std::os::raw::c_uint = 1;
+    pub const NS_STYLE_WINDOW_SHADOW_MENU: ::std::os::raw::c_uint = 2;
+    pub const NS_STYLE_WINDOW_SHADOW_TOOLTIP: ::std::os::raw::c_uint = 3;
+    pub const NS_STYLE_WINDOW_SHADOW_SHEET: ::std::os::raw::c_uint = 4;
     pub const NS_STYLE_DOMINANT_BASELINE_AUTO: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_DOMINANT_BASELINE_USE_SCRIPT: ::std::os::raw::c_uint =
         1;
     pub const NS_STYLE_DOMINANT_BASELINE_NO_CHANGE: ::std::os::raw::c_uint =
         2;
     pub const NS_STYLE_DOMINANT_BASELINE_RESET_SIZE: ::std::os::raw::c_uint =
         3;
     pub const NS_STYLE_DOMINANT_BASELINE_IDEOGRAPHIC: ::std::os::raw::c_uint =
@@ -2186,27 +2191,39 @@ pub mod root {
             }
             pub type IntRectTyped_Super = u8;
             pub type IntRectTyped_Self = u8;
             pub type IntRectTyped_ToInt = u32;
             pub type IntRect = [u32; 4usize];
             #[repr(C)]
             #[derive(Debug, Copy)]
             pub struct FontVariation {
-                pub _bindgen_opaque_blob: [u32; 2usize],
+                pub mTag: u32,
+                pub mValue: f32,
             }
             #[test]
             fn bindgen_test_layout_FontVariation() {
                 assert_eq!(::std::mem::size_of::<FontVariation>() , 8usize ,
                            concat ! (
                            "Size of: " , stringify ! ( FontVariation ) ));
                 assert_eq! (::std::mem::align_of::<FontVariation>() , 4usize ,
                             concat ! (
                             "Alignment of " , stringify ! ( FontVariation )
                             ));
+                assert_eq! (unsafe {
+                            & ( * ( 0 as * const FontVariation ) ) . mTag as *
+                            const _ as usize } , 0usize , concat ! (
+                            "Alignment of field: " , stringify ! (
+                            FontVariation ) , "::" , stringify ! ( mTag ) ));
+                assert_eq! (unsafe {
+                            & ( * ( 0 as * const FontVariation ) ) . mValue as
+                            * const _ as usize } , 4usize , concat ! (
+                            "Alignment of field: " , stringify ! (
+                            FontVariation ) , "::" , stringify ! ( mValue )
+                            ));
             }
             impl Clone for FontVariation {
                 fn clone(&self) -> Self { *self }
             }
             pub type Matrix4x4 = [u32; 16usize];
             #[repr(C)]
             #[derive(Debug, Copy, Clone)]
             pub struct ScaleFactor {
--- a/servo/components/style/gecko/generated/structs_release.rs
+++ b/servo/components/style/gecko/generated/structs_release.rs
@@ -801,16 +801,21 @@ pub mod root {
     pub const NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE: ::std::os::raw::c_uint =
         2;
     pub const NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER: ::std::os::raw::c_uint =
         3;
     pub const NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE: ::std::os::raw::c_uint =
         4;
     pub const NS_STYLE_CONTEXT_PROPERTY_FILL: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_CONTEXT_PROPERTY_STROKE: ::std::os::raw::c_uint = 2;
+    pub const NS_STYLE_WINDOW_SHADOW_NONE: ::std::os::raw::c_uint = 0;
+    pub const NS_STYLE_WINDOW_SHADOW_DEFAULT: ::std::os::raw::c_uint = 1;
+    pub const NS_STYLE_WINDOW_SHADOW_MENU: ::std::os::raw::c_uint = 2;
+    pub const NS_STYLE_WINDOW_SHADOW_TOOLTIP: ::std::os::raw::c_uint = 3;
+    pub const NS_STYLE_WINDOW_SHADOW_SHEET: ::std::os::raw::c_uint = 4;
     pub const NS_STYLE_DOMINANT_BASELINE_AUTO: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_DOMINANT_BASELINE_USE_SCRIPT: ::std::os::raw::c_uint =
         1;
     pub const NS_STYLE_DOMINANT_BASELINE_NO_CHANGE: ::std::os::raw::c_uint =
         2;
     pub const NS_STYLE_DOMINANT_BASELINE_RESET_SIZE: ::std::os::raw::c_uint =
         3;
     pub const NS_STYLE_DOMINANT_BASELINE_IDEOGRAPHIC: ::std::os::raw::c_uint =
@@ -2092,27 +2097,39 @@ pub mod root {
             }
             pub type IntRectTyped_Super = u8;
             pub type IntRectTyped_Self = u8;
             pub type IntRectTyped_ToInt = u32;
             pub type IntRect = [u32; 4usize];
             #[repr(C)]
             #[derive(Debug, Copy)]
             pub struct FontVariation {
-                pub _bindgen_opaque_blob: [u32; 2usize],
+                pub mTag: u32,
+                pub mValue: f32,
             }
             #[test]
             fn bindgen_test_layout_FontVariation() {
                 assert_eq!(::std::mem::size_of::<FontVariation>() , 8usize ,
                            concat ! (
                            "Size of: " , stringify ! ( FontVariation ) ));
                 assert_eq! (::std::mem::align_of::<FontVariation>() , 4usize ,
                             concat ! (
                             "Alignment of " , stringify ! ( FontVariation )
                             ));
+                assert_eq! (unsafe {
+                            & ( * ( 0 as * const FontVariation ) ) . mTag as *
+                            const _ as usize } , 0usize , concat ! (
+                            "Alignment of field: " , stringify ! (
+                            FontVariation ) , "::" , stringify ! ( mTag ) ));
+                assert_eq! (unsafe {
+                            & ( * ( 0 as * const FontVariation ) ) . mValue as
+                            * const _ as usize } , 4usize , concat ! (
+                            "Alignment of field: " , stringify ! (
+                            FontVariation ) , "::" , stringify ! ( mValue )
+                            ));
             }
             impl Clone for FontVariation {
                 fn clone(&self) -> Self { *self }
             }
             pub type Matrix4x4 = [u32; 16usize];
             #[repr(C)]
             #[derive(Debug, Copy, Clone)]
             pub struct ScaleFactor {
--- a/servo/components/style/gecko/rules.rs
+++ b/servo/components/style/gecko/rules.rs
@@ -12,16 +12,17 @@ use cssparser::UnicodeRange;
 use font_face::{FontFaceRuleData, Source, FontDisplay};
 use gecko_bindings::bindings;
 use gecko_bindings::structs::{self, nsCSSFontFaceRule, nsCSSValue};
 use gecko_bindings::structs::{nsCSSCounterDesc, nsCSSCounterStyleRule};
 use gecko_bindings::sugar::ns_css_value::ToNsCssValue;
 use gecko_bindings::sugar::refptr::{RefPtr, UniqueRefPtr};
 use shared_lock::{ToCssWithGuard, SharedRwLockReadGuard};
 use std::{fmt, str};
+use values::generics::FontSettings;
 
 /// A @font-face rule
 pub type FontFaceRule = RefPtr<nsCSSFontFaceRule>;
 
 impl ToNsCssValue for FamilyName {
     fn convert(self, nscssvalue: &mut nsCSSValue) {
         nscssvalue.set_string_from_atom(&self.name)
     }
@@ -31,26 +32,26 @@ impl ToNsCssValue for font_weight::T {
     fn convert(self, nscssvalue: &mut nsCSSValue) {
         nscssvalue.set_integer(self as i32)
     }
 }
 
 impl ToNsCssValue for font_feature_settings::T {
     fn convert(self, nscssvalue: &mut nsCSSValue) {
         match self {
-            font_feature_settings::T::Normal => nscssvalue.set_normal(),
-            font_feature_settings::T::Tag(tags) => {
+            FontSettings::Normal => nscssvalue.set_normal(),
+            FontSettings::Tag(tags) => {
                 nscssvalue.set_pair_list(tags.into_iter().map(|entry| {
                     let mut feature = nsCSSValue::null();
                     let mut raw = [0u8; 4];
                     (&mut raw[..]).write_u32::<BigEndian>(entry.tag).unwrap();
                     feature.set_string(str::from_utf8(&raw).unwrap());
 
                     let mut index = nsCSSValue::null();
-                    index.set_integer(entry.value as i32);
+                    index.set_integer(entry.value.0 as i32);
 
                     (feature, index)
                 }))
             }
         }
     }
 }
 
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -1481,37 +1481,38 @@ fn static_assert() {
     }
 </%self:impl_trait>
 
 <%
     skip_font_longhands = """font-family font-size font-size-adjust font-weight
                              font-synthesis -x-lang font-variant-alternates
                              font-variant-east-asian font-variant-ligatures
                              font-variant-numeric font-language-override
-                             font-feature-settings"""
+                             font-feature-settings font-variation-settings
+                             -moz-min-font-size-ratio"""
 %>
 <%self:impl_trait style_struct_name="Font"
     skip_longhands="${skip_font_longhands}"
     skip_additionals="*">
 
     pub fn set_font_feature_settings(&mut self, v: longhands::font_feature_settings::computed_value::T) {
-        use properties::longhands::font_feature_settings::computed_value::T;
+        use values::generics::FontSettings;
 
         let current_settings = &mut self.gecko.mFont.fontFeatureSettings;
         current_settings.clear_pod();
 
         match v {
-            T::Normal => unsafe { current_settings.set_len_pod(0) },
-
-            T::Tag(feature_settings) => {
+            FontSettings::Normal => (), // do nothing, length is already 0
+
+            FontSettings::Tag(feature_settings) => {
                 unsafe { current_settings.set_len_pod(feature_settings.len() as u32) };
 
                 for (current, feature) in current_settings.iter_mut().zip(feature_settings) {
                     current.mTag = feature.tag;
-                    current.mValue = feature.value;
+                    current.mValue = feature.value.0;
                 }
             }
         };
     }
 
     pub fn copy_font_feature_settings_from(&mut self, other: &Self ) {
         let current_settings = &mut self.gecko.mFont.fontFeatureSettings;
         let feature_settings = &other.gecko.mFont.fontFeatureSettings;
@@ -1521,16 +1522,50 @@ fn static_assert() {
         unsafe { current_settings.set_len_pod(settings_length) };
 
         for (current, feature) in current_settings.iter_mut().zip(feature_settings.iter()) {
             current.mTag = feature.mTag;
             current.mValue = feature.mValue;
         }
     }
 
+    pub fn set_font_variation_settings(&mut self, v: longhands::font_variation_settings::computed_value::T) {
+        use values::generics::FontSettings;
+
+        let current_settings = &mut self.gecko.mFont.fontVariationSettings;
+        current_settings.clear_pod();
+
+        match v {
+            FontSettings::Normal => (), // do nothing, length is already 0
+
+            FontSettings::Tag(feature_settings) => {
+                unsafe { current_settings.set_len_pod(feature_settings.len() as u32) };
+
+                for (current, feature) in current_settings.iter_mut().zip(feature_settings) {
+                    current.mTag = feature.tag;
+                    current.mValue = feature.value.0;
+                }
+            }
+        };
+    }
+
+    pub fn copy_font_variation_settings_from(&mut self, other: &Self ) {
+        let current_settings = &mut self.gecko.mFont.fontVariationSettings;
+        let feature_settings = &other.gecko.mFont.fontVariationSettings;
+        let settings_length = feature_settings.len() as u32;
+
+        current_settings.clear_pod();
+        unsafe { current_settings.set_len_pod(settings_length) };
+
+        for (current, feature) in current_settings.iter_mut().zip(feature_settings.iter()) {
+            current.mTag = feature.mTag;
+            current.mValue = feature.mValue;
+        }
+    }
+
     pub fn fixup_none_generic(&mut self, device: &Device) {
         unsafe {
             bindings::Gecko_nsStyleFont_FixupNoneGeneric(&mut self.gecko, &*device.pres_context)
         }
     }
 
     pub fn set_font_family(&mut self, v: longhands::font_family::computed_value::T) {
         use properties::longhands::font_family::computed_value::FontFamily;
@@ -1590,39 +1625,44 @@ fn static_assert() {
         unsafe { Gecko_CopyFontFamilyFrom(&mut self.gecko.mFont, &other.gecko.mFont); }
         self.gecko.mGenericID = other.gecko.mGenericID;
     }
 
     // FIXME(bholley): Gecko has two different sizes, one of which (mSize) is the
     // actual computed size, and the other of which (mFont.size) is the 'display
     // size' which takes font zooming into account. We don't handle font zooming yet.
     pub fn set_font_size(&mut self, v: longhands::font_size::computed_value::T) {
-        self.gecko.mFont.size = v.0;
         self.gecko.mSize = v.0;
         self.gecko.mScriptUnconstrainedSize = v.0;
     }
 
     /// Set font size, taking into account scriptminsize and scriptlevel
     /// Returns Some(size) if we have to recompute the script unconstrained size
     pub fn apply_font_size(&mut self, v: longhands::font_size::computed_value::T,
-                           parent: &Self) -> Option<Au> {
+                           parent: &Self,
+                           device: &Device) -> Option<Au> {
         let (adjusted_size, adjusted_unconstrained_size)
             = self.calculate_script_level_size(parent);
         // In this case, we have been unaffected by scriptminsize, ignore it
         if parent.gecko.mSize == parent.gecko.mScriptUnconstrainedSize &&
            adjusted_size == adjusted_unconstrained_size {
             self.set_font_size(v);
+            self.fixup_font_min_size(device);
             None
         } else {
-            self.gecko.mFont.size = v.0;
             self.gecko.mSize = v.0;
+            self.fixup_font_min_size(device);
             Some(Au(parent.gecko.mScriptUnconstrainedSize))
         }
     }
 
+    pub fn fixup_font_min_size(&mut self, device: &Device) {
+        unsafe { bindings::Gecko_nsStyleFont_FixupMinFontSize(&mut self.gecko, &*device.pres_context) }
+    }
+
     pub fn apply_unconstrained_font_size(&mut self, v: Au) {
         self.gecko.mScriptUnconstrainedSize = v.0;
     }
 
     /// Calculates the constrained and unconstrained font sizes to be inherited
     /// from the parent.
     ///
     /// See ComputeScriptLevelSize in Gecko's nsRuleNode.cpp
@@ -1721,17 +1761,18 @@ fn static_assert() {
     }
 
     /// This function will also handle scriptminsize and scriptlevel
     /// so should not be called when you just want the font sizes to be copied.
     /// Hence the different name.
     ///
     /// Returns true if the inherited keyword size was actually used
     pub fn inherit_font_size_from(&mut self, parent: &Self,
-                                  kw_inherited_size: Option<Au>) -> bool {
+                                  kw_inherited_size: Option<Au>,
+                                  device: &Device) -> bool {
         let (adjusted_size, adjusted_unconstrained_size)
             = self.calculate_script_level_size(parent);
         if adjusted_size.0 != parent.gecko.mSize ||
            adjusted_unconstrained_size.0 != parent.gecko.mScriptUnconstrainedSize {
             // This is incorrect. When there is both a keyword size being inherited
             // and a scriptlevel change, we must handle the keyword size the same
             // way we handle em units. This complicates things because we now have
             // to keep track of the adjusted and unadjusted ratios in the kw font size.
@@ -1740,33 +1781,33 @@ fn static_assert() {
             // If we were to fix this I would prefer doing it by removing the
             // ruletree walk on the Gecko side in nsRuleNode::SetGenericFont
             // and instead using extra bookkeeping in the mSize and mScriptUnconstrainedSize
             // values, and reusing those instead of font_size_keyword.
 
 
             // In the case that MathML has given us an adjusted size, apply it.
             // Keep track of the unconstrained adjusted size.
-            self.gecko.mFont.size = adjusted_size.0;
             self.gecko.mSize = adjusted_size.0;
             self.gecko.mScriptUnconstrainedSize = adjusted_unconstrained_size.0;
+            self.fixup_font_min_size(device);
             false
         } else if let Some(size) = kw_inherited_size {
             // Parent element was a keyword-derived size.
-            self.gecko.mFont.size = size.0;
             self.gecko.mSize = size.0;
             // MathML constraints didn't apply here, so we can ignore this.
             self.gecko.mScriptUnconstrainedSize = size.0;
+            self.fixup_font_min_size(device);
             true
         } else {
             // MathML isn't affecting us, and our parent element does not
             // have a keyword-derived size. Set things normally.
-            self.gecko.mFont.size = parent.gecko.mFont.size;
             self.gecko.mSize = parent.gecko.mSize;
             self.gecko.mScriptUnconstrainedSize = parent.gecko.mScriptUnconstrainedSize;
+            self.fixup_font_min_size(device);
             false
         }
     }
 
     pub fn clone_font_size(&self) -> longhands::font_size::computed_value::T {
         Au(self.gecko.mSize)
     }
 
@@ -1859,16 +1900,31 @@ fn static_assert() {
 
     ${impl_simple_copy('font_variant_east_asian', 'mFont.variantEastAsian')}
 
     pub fn set_font_variant_numeric(&mut self, v: longhands::font_variant_numeric::computed_value::T) {
         self.gecko.mFont.variantNumeric = v.to_gecko_keyword()
     }
 
     ${impl_simple_copy('font_variant_numeric', 'mFont.variantNumeric')}
+
+    #[allow(non_snake_case)]
+    pub fn set__moz_min_font_size_ratio(&mut self, v: longhands::_moz_min_font_size_ratio::computed_value::T) {
+        let percentage = if v.0 > 255. {
+            255.
+        } else if v.0 < 0. {
+            0.
+        } else {
+            v.0
+        };
+
+        self.gecko.mMinFontSizeRatio = percentage as u8;
+    }
+
+    ${impl_simple_copy('_moz_min_font_size_ratio', 'mMinFontSizeRatio')}
 </%self:impl_trait>
 
 <%def name="impl_copy_animation_or_transition_value(type, ident, gecko_ffi_name)">
     #[allow(non_snake_case)]
     pub fn copy_${type}_${ident}_from(&mut self, other: &Self) {
         unsafe { self.gecko.m${type.capitalize()}s.ensure_len(other.gecko.m${type.capitalize()}s.len()) };
 
         let count = other.gecko.m${type.capitalize()}${gecko_ffi_name}Count;
@@ -3998,17 +4054,17 @@ clip-path
             T::exclude => structs::NS_STYLE_MASK_COMPOSITE_EXCLUDE as u8,
         }
     </%self:simple_image_array_property>
 
     <% impl_shape_source("clip_path", "mClipPath") %>
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="InheritedSVG"
-                  skip_longhands="paint-order stroke-dasharray stroke-dashoffset stroke-width"
+                  skip_longhands="paint-order stroke-dasharray stroke-dashoffset stroke-width -moz-context-properties"
                   skip_additionals="*">
     pub fn set_paint_order(&mut self, v: longhands::paint_order::computed_value::T) {
         use self::longhands::paint_order;
 
         if v.0 == 0 {
             self.gecko.mPaintOrder = structs::NS_STYLE_PAINT_ORDER_NORMAL as u8;
         } else {
             let mut order = 0;
@@ -4106,16 +4162,44 @@ clip-path
         match self.gecko.mStrokeWidth.as_value() {
             CoordDataValue::Factor(number) => Either::First(number),
             CoordDataValue::Coord(coord) => Either::Second(LengthOrPercentage::Length(Au(coord))),
             CoordDataValue::Percent(p) => Either::Second(LengthOrPercentage::Percentage(p)),
             CoordDataValue::Calc(calc) => Either::Second(LengthOrPercentage::Calc(calc.into())),
             _ => unreachable!(),
         }
     }
+
+    #[allow(non_snake_case)]
+    pub fn set__moz_context_properties<I>(&mut self, v: I)
+        where I: IntoIterator<Item = longhands::_moz_context_properties::computed_value::single_value::T>,
+              I::IntoIter: ExactSizeIterator
+    {
+        let v = v.into_iter();
+        unsafe {
+            bindings::Gecko_nsStyleSVG_SetContextPropertiesLength(&mut self.gecko, v.len() as u32);
+        }
+
+        self.gecko.mContextPropsBits = 0;
+        for (mut gecko, servo) in self.gecko.mContextProps.iter_mut().zip(v) {
+            if servo.0 == atom!("fill") {
+                self.gecko.mContextPropsBits |= structs::NS_STYLE_CONTEXT_PROPERTY_FILL as u8;
+            } else if servo.0 == atom!("stroke") {
+                self.gecko.mContextPropsBits |= structs::NS_STYLE_CONTEXT_PROPERTY_STROKE as u8;
+            }
+            unsafe { gecko.set_raw_from_addrefed::<structs::nsIAtom>(servo.0.into_addrefed()) }
+        }
+    }
+
+    #[allow(non_snake_case)]
+    pub fn copy__moz_context_properties_from(&mut self, other: &Self) {
+        unsafe {
+            bindings::Gecko_nsStyleSVG_CopyContextProperties(&mut self.gecko, &other.gecko);
+        }
+    }
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="Color"
                   skip_longhands="*">
     pub fn set_color(&mut self, v: longhands::color::computed_value::T) {
         let result = convert_rgba_to_nscolor(&v);
         ${set_gecko_property("mColor", "result")}
     }
--- a/servo/components/style/properties/longhand/column.mako.rs
+++ b/servo/components/style/properties/longhand/column.mako.rs
@@ -49,19 +49,18 @@
 // 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,
                           spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-color")}
 
-// It's not implemented in servo or gecko yet.
 ${helpers.single_keyword("column-span", "none all",
-                         products="none", animation_value_type="none",
+                         products="gecko", animation_value_type="none",
                          spec="https://drafts.csswg.org/css-multicol/#propdef-column-span")}
 
 ${helpers.single_keyword("column-rule-style",
                          "none hidden dotted dashed solid double groove ridge inset outset",
                          products="gecko", extra_prefixes="moz",
                          gecko_constant_prefix="NS_STYLE_BORDER_STYLE",
                          animation_value_type="none",
                          spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-style")}
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -920,20 +920,22 @@
                context.style().get_font().gecko().mGenericID !=
                context.inherited_style().get_font().gecko().mGenericID {
                 if let Some((kw, ratio)) = context.style().font_size_keyword {
                     computed = kw.to_computed_value(context).scale_by(ratio);
                 }
             }
         % endif
 
-        let parent_unconstrained = context.mutate_style()
-                                       .mutate_font()
-                                       .apply_font_size(computed,
-                                                        parent);
+        let parent_unconstrained = {
+            let (style, device) = context.mutate_style_with_device();
+
+            style.mutate_font().apply_font_size(computed, parent, device)
+        };
+
 
         if let Some(parent) = parent_unconstrained {
             let new_unconstrained = specified_value
                         .to_computed_value_against(context, FontBaseSize::Custom(parent));
             context.mutate_style()
                    .mutate_font()
                    .apply_unconstrained_font_size(new_unconstrained);
         }
@@ -941,34 +943,38 @@
 
     pub fn cascade_inherit_font_size(context: &mut Context, parent: &Font) {
         // If inheriting, we must recompute font-size in case of language changes
         // using the font_size_keyword. We also need to do this to handle
         // mathml scriptlevel changes
         let kw_inherited_size = context.style().font_size_keyword.map(|(kw, ratio)| {
             SpecifiedValue::Keyword(kw, ratio).to_computed_value(context)
         });
-        let used_kw = context.mutate_style().mutate_font()
-               .inherit_font_size_from(parent, kw_inherited_size);
+        let parent_kw = context.inherited_style.font_computation_data.font_size_keyword;
+        let (style, device) = context.mutate_style_with_device();
+        let used_kw = style.mutate_font()
+               .inherit_font_size_from(parent, kw_inherited_size, device);
         if used_kw {
-            context.mutate_style().font_size_keyword =
-                context.inherited_style.font_computation_data.font_size_keyword;
+            style.font_size_keyword = parent_kw;
         } else {
-            context.mutate_style().font_size_keyword = None;
+            style.font_size_keyword = None;
         }
     }
 
     pub fn cascade_initial_font_size(context: &mut Context) {
         // font-size's default ("medium") does not always
         // compute to the same value and depends on the font
         let computed = longhands::font_size::get_initial_specified_value()
                             .to_computed_value(context);
-        context.mutate_style().mutate_${data.current_style_struct.name_lower}()
-               .set_font_size(computed);
-        context.mutate_style().font_size_keyword = Some((Default::default(), 1.));
+        let (style, _device) = context.mutate_style_with_device();
+        style.mutate_font().set_font_size(computed);
+        % if product == "gecko":
+            style.mutate_font().fixup_font_min_size(_device);
+        % endif
+        style.font_size_keyword = Some((Default::default(), 1.));
     }
 </%helpers:longhand>
 
 <%helpers:longhand products="gecko" name="font-size-adjust" animation_value_type="ComputedValue"
                    spec="https://drafts.csswg.org/css-fonts/#propdef-font-size-adjust">
     use properties::longhands::system_font::SystemFont;
     use std::fmt;
     use style_traits::ToCss;
@@ -1769,149 +1775,82 @@ macro_rules! exclusive_value {
                                 animation_value_type="none")}
 
 <%helpers:longhand name="font-feature-settings" products="gecko" animation_value_type="none"
                    extra_prefixes="moz" boxed="True"
                    spec="https://drafts.csswg.org/css-fonts/#propdef-font-feature-settings">
     use properties::longhands::system_font::SystemFont;
     use std::fmt;
     use style_traits::ToCss;
+    use values::generics::FontSettings;
 
     #[derive(Debug, Clone, PartialEq)]
     pub enum SpecifiedValue {
         Value(computed_value::T),
         System(SystemFont)
     }
     no_viewport_percentage!(SpecifiedValue);
 
     <%self:simple_system_boilerplate name="font_feature_settings"></%self:simple_system_boilerplate>
 
     pub mod computed_value {
-        use cssparser::Parser;
-        use parser::{Parse, ParserContext};
-        use std::fmt;
-        use style_traits::ToCss;
-
-        #[derive(Clone, Debug, Eq, PartialEq)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub enum T {
-            Normal,
-            Tag(Vec<FeatureTagValue>)
-        }
-
-        #[derive(Clone, Debug, Eq, PartialEq)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub struct FeatureTagValue {
-            pub tag: u32,
-            pub value: u32
-        }
-
-        impl ToCss for T {
-            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-                match *self {
-                    T::Normal => dest.write_str("normal"),
-                    T::Tag(ref ftvs) => {
-                        let mut iter = ftvs.iter();
-                        // handle head element
-                        try!(iter.next().unwrap().to_css(dest));
-                        // handle tail, precede each with a delimiter
-                        for ftv in iter {
-                            try!(dest.write_str(", "));
-                            try!(ftv.to_css(dest));
-                        }
-                        Ok(())
-                    }
-                }
-            }
-        }
-
-        impl Parse for T {
-            /// https://www.w3.org/TR/css-fonts-3/#propdef-font-feature-settings
-            fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-                if input.try(|i| i.expect_ident_matching("normal")).is_ok() {
-                    return Ok(T::Normal);
-                }
-                input.parse_comma_separated(|i| FeatureTagValue::parse(context, i)).map(T::Tag)
-            }
-        }
-
-        impl ToCss for FeatureTagValue {
-            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-                use std::str;
-                use byteorder::{WriteBytesExt, BigEndian};
-                use cssparser::serialize_string;
-
-                let mut raw: Vec<u8> = vec!();
-                raw.write_u32::<BigEndian>(self.tag).unwrap();
-                serialize_string(str::from_utf8(&raw).unwrap_or_default(), dest)?;
-
-                match self.value {
-                    1 => Ok(()),
-                    0 => dest.write_str(" off"),
-                    x => write!(dest, " {}", x)
-                }
-            }
-        }
-
-        impl Parse for FeatureTagValue {
-            /// https://www.w3.org/TR/css-fonts-3/#propdef-font-feature-settings
-            /// <string> [ on | off | <integer> ]
-            fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-                use std::io::Cursor;
-                use byteorder::{ReadBytesExt, BigEndian};
-
-                let tag = try!(input.expect_string());
-
-                // allowed strings of length 4 containing chars: <U+20, U+7E>
-                if tag.len() != 4 ||
-                   tag.chars().any(|c| c < ' ' || c > '~')
-                {
-                    return Err(())
-                }
-
-                let mut raw = Cursor::new(tag.as_bytes());
-                let u_tag = raw.read_u32::<BigEndian>().unwrap();
-
-                if let Ok(value) = input.try(|input| input.expect_integer()) {
-                    // handle integer, throw if it is negative
-                    if value >= 0 {
-                        Ok(FeatureTagValue { tag: u_tag, value: value as u32 })
-                    } else {
-                        Err(())
-                    }
-                } else if let Ok(_) = input.try(|input| input.expect_ident_matching("on")) {
-                    // on is an alias for '1'
-                    Ok(FeatureTagValue { tag: u_tag, value: 1 })
-                } else if let Ok(_) = input.try(|input| input.expect_ident_matching("off")) {
-                    // off is an alias for '0'
-                    Ok(FeatureTagValue { tag: u_tag, value: 0 })
-                } else {
-                    // empty value is an alias for '1'
-                    Ok(FeatureTagValue { tag: u_tag, value: 1 })
-                }
-            }
-        }
+        use values::generics::{FontSettings, FontSettingTagInt};
+        pub type T = FontSettings<FontSettingTagInt>;
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
-        computed_value::T::Normal
+        FontSettings::Normal
     }
 
     #[inline]
     pub fn get_initial_specified_value() -> SpecifiedValue {
-        SpecifiedValue::Value(computed_value::T::Normal)
+        SpecifiedValue::Value(FontSettings::Normal)
     }
 
     /// normal | <feature-tag-value>#
     pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
         computed_value::T::parse(context, input).map(SpecifiedValue::Value)
     }
 </%helpers:longhand>
 
+<%
+# This spec link is too long to fit elsewhere
+variation_spec = """\
+https://drafts.csswg.org/css-fonts-4/#low-level-font-variation-settings-control-the-font-variation-settings-property\
+"""
+%>
+<%helpers:longhand name="font-variation-settings" products="gecko" animation_value_type="none"
+                   spec="${variation_spec}">
+    use values::computed::ComputedValueAsSpecified;
+    use values::generics::FontSettings;
+
+    impl ComputedValueAsSpecified for SpecifiedValue {}
+
+    pub type SpecifiedValue = computed_value::T;
+
+    no_viewport_percentage!(SpecifiedValue);
+
+
+    pub mod computed_value {
+        use values::generics::{FontSettings, FontSettingTagFloat};
+        pub type T = FontSettings<FontSettingTagFloat>;
+    }
+
+    #[inline]
+    pub fn get_initial_value() -> computed_value::T {
+        FontSettings::Normal
+    }
+
+    /// normal | <feature-tag-value>#
+    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+        computed_value::T::parse(context, input)
+    }
+</%helpers:longhand>
+
 <%helpers:longhand name="font-language-override" products="gecko" animation_value_type="none"
                    extra_prefixes="moz" boxed="True"
                    spec="https://drafts.csswg.org/css-fonts-3/#propdef-font-language-override">
     use properties::longhands::system_font::SystemFont;
     use std::fmt;
     use style_traits::ToCss;
     use byteorder::{BigEndian, ByteOrder};
     no_viewport_percentage!(SpecifiedValue);
@@ -2448,8 +2387,16 @@ macro_rules! exclusive_value {
 ${helpers.single_keyword("-moz-osx-font-smoothing",
                          "auto grayscale",
                          gecko_constant_prefix="NS_FONT_SMOOTHING",
                          gecko_ffi_name="mFont.smoothing",
                          products="gecko",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/font-smooth)",
                          animation_value_type="none",
                          need_clone=True)}
+
+${helpers.predefined_type("-moz-min-font-size-ratio",
+                          "Percentage",
+                          "computed::Percentage::hundred()",
+                          animation_value_type="none",
+                          products="gecko",
+                          internal=True,
+                          spec="Nonstandard (Internal-only)")}
--- a/servo/components/style/properties/longhand/inherited_svg.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_svg.mako.rs
@@ -263,8 +263,33 @@
         }
     }
 
     no_viewport_percentage!(SpecifiedValue);
 
     impl ComputedValueAsSpecified for SpecifiedValue { }
 </%helpers:longhand>
 
+<%helpers:vector_longhand name="-moz-context-properties"
+                   animation_value_type="none"
+                   products="gecko"
+                   spec="Nonstandard (Internal-only)"
+                   internal="True"
+                   allow_empty="True">
+    use values::CustomIdent;
+    use values::computed::ComputedValueAsSpecified;
+
+    no_viewport_percentage!(SpecifiedValue);
+
+    impl ComputedValueAsSpecified for SpecifiedValue { }
+
+    pub type SpecifiedValue = CustomIdent;
+
+    pub mod computed_value {
+        pub type T = super::SpecifiedValue;
+    }
+
+
+    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+        let i = input.expect_ident()?;
+        CustomIdent::from_ident(i, &["all", "none", "auto"])
+    }
+</%helpers:vector_longhand>
--- a/servo/components/style/properties/longhand/ui.mako.rs
+++ b/servo/components/style/properties/longhand/ui.mako.rs
@@ -26,16 +26,23 @@
                          spec="https://drafts.csswg.org/css-ui-4/#propdef-user-select")}
 
 ${helpers.single_keyword("-moz-window-dragging", "default drag no-drag", products="gecko",
                          gecko_ffi_name="mWindowDragging",
                          gecko_enum_prefix="StyleWindowDragging",
                          animation_value_type="none",
                          spec="None (Nonstandard Firefox-only property)")}
 
+${helpers.single_keyword("-moz-window-shadow", "none default menu tooltip sheet", products="gecko",
+                         gecko_ffi_name="mWindowShadow",
+                         gecko_constant_prefix="NS_STYLE_WINDOW_SHADOW",
+                         animation_value_type="none",
+                         internal=True,
+                         spec="None (Nonstandard internal property)")}
+
 <%helpers:longhand name="-moz-force-broken-image-icon"
                    products="gecko"
                    animation_value_type="none"
                    spec="None (Nonstandard Firefox-only property)">
     use std::fmt;
     use style_traits::ToCss;
     use values::computed::ComputedValueAsSpecified;
 
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -602,16 +602,17 @@ impl LonghandId {
     fn is_early_property(&self) -> bool {
         matches!(*self,
             % if product == 'gecko':
             LonghandId::TextOrientation |
             LonghandId::AnimationName |
             LonghandId::TransitionProperty |
             LonghandId::XLang |
             LonghandId::MozScriptLevel |
+            LonghandId::MozMinFontSizeRatio |
             % endif
             LonghandId::FontSize |
             LonghandId::FontFamily |
             LonghandId::Color |
             LonghandId::TextDecorationLine |
             LonghandId::WritingMode |
             LonghandId::Direction
         )
@@ -1519,16 +1520,17 @@ pub use gecko_properties::style_structs;
 /// The module where all the style structs are defined.
 #[cfg(feature = "servo")]
 pub mod style_structs {
     use app_units::Au;
     use fnv::FnvHasher;
     use super::longhands;
     use std::hash::{Hash, Hasher};
     use logical_geometry::WritingMode;
+    use media_queries::Device;
 
     % for style_struct in data.active_style_structs():
         % if style_struct.name == "Font":
         #[derive(Clone, Debug)]
         % else:
         #[derive(PartialEq, Clone, Debug)]
         % endif
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
@@ -1629,24 +1631,25 @@ pub mod style_structs {
                     hasher.write_u16(self.font_weight as u16);
                     self.font_stretch.hash(&mut hasher);
                     self.font_family.hash(&mut hasher);
                     self.hash = hasher.finish()
                 }
 
                 /// (Servo does not handle MathML, so this just calls copy_font_size_from)
                 pub fn inherit_font_size_from(&mut self, parent: &Self,
-                                              _: Option<Au>) -> bool {
+                                              _: Option<Au>, _: &Device) -> bool {
                     self.copy_font_size_from(parent);
                     false
                 }
                 /// (Servo does not handle MathML, so this just calls set_font_size)
                 pub fn apply_font_size(&mut self,
                                        v: longhands::font_size::computed_value::T,
-                                       _: &Self) -> Option<Au> {
+                                       _: &Self,
+                                       _: &Device) -> Option<Au> {
                     self.set_font_size(v);
                     None
                 }
                 /// (Servo does not handle MathML, so this does nothing)
                 pub fn apply_unconstrained_font_size(&mut self, _: Au) {
                 }
 
             % elif style_struct.name == "Outline":
@@ -2623,27 +2626,16 @@ pub fn apply_declarations<'a, F, I>(devi
             // Only a few properties are allowed to depend on the visited state
             // of links.  When cascading visited styles, we can save time by
             // only processing these properties.
             if flags.contains(VISITED_DEPENDENT_ONLY) &&
                !longhand_id.is_visited_dependent() {
                 continue
             }
 
-            // The computed value of some properties depends on the
-            // (sometimes computed) value of *other* properties.
-            //
-            // So we classify properties into "early" and "other", such that
-            // the only dependencies can be from "other" to "early".
-            //
-            // We iterate applicable_declarations twice, first cascading
-            // "early" properties then "other".
-            //
-            // Unfortunately, it’s not easy to check that this
-            // classification is correct.
             if
                 % if category_to_cascade_now == "early":
                     !
                 % endif
                 longhand_id.is_early_property()
             {
                 continue
             }
@@ -2713,16 +2705,17 @@ pub fn apply_declarations<'a, F, I>(devi
                                                  &mut cacheable,
                                                  &mut cascade_info,
                                                  error_reporter);
             % if product == "gecko":
             // Font size must be explicitly inherited to handle lang changes and
             // scriptlevel changes.
             } else if seen.contains(LonghandId::XLang) ||
                       seen.contains(LonghandId::MozScriptLevel) ||
+                      seen.contains(LonghandId::MozMinFontSizeRatio) ||
                       font_family.is_some() {
                 let discriminant = LonghandId::FontSize as usize;
                 let size = PropertyDeclaration::CSSWideKeyword(
                     LonghandId::FontSize, CSSWideKeyword::Inherit);
 
                 (CASCADE_PROPERTY[discriminant])(&size,
                                                  inherited_style,
                                                  default_style,
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -101,16 +101,18 @@ impl<'a> Context<'a> {
     pub fn viewport_size(&self) -> Size2D<Au> { self.device.au_viewport_size() }
     /// The style we're inheriting from.
     pub fn inherited_style(&self) -> &ComputedValues { &self.inherited_style }
     /// The current style. Note that only "eager" properties should be accessed
     /// from here, see the comment in the member.
     pub fn style(&self) -> &StyleBuilder { &self.style }
     /// A mutable reference to the current style.
     pub fn mutate_style(&mut self) -> &mut StyleBuilder<'a> { &mut self.style }
+    /// Get a mutable reference to the current style as well as the device
+    pub fn mutate_style_with_device(&mut self) -> (&mut StyleBuilder<'a>, &Device) { (&mut self.style, &self.device) }
 }
 
 /// An iterator over a slice of computed values
 #[derive(Clone)]
 pub struct ComputedVecIter<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> {
     cx: &'cx Context<'cx_a>,
     values: &'a [S],
 }
--- a/servo/components/style/values/generics/mod.rs
+++ b/servo/components/style/values/generics/mod.rs
@@ -5,17 +5,17 @@
 //! Generic types that share their serialization implementations
 //! for both specified and computed values.
 
 use counter_style::{Symbols, parse_counter_style_name};
 use cssparser::Parser;
 use euclid::size::Size2D;
 use parser::{Parse, ParserContext};
 use std::fmt;
-use style_traits::{HasViewportPercentage, ToCss};
+use style_traits::{HasViewportPercentage, OneOrMoreCommaSeparated, ToCss};
 use super::CustomIdent;
 
 pub use self::basic_shape::serialize_radius_values;
 
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod grid;
@@ -158,8 +158,157 @@ impl ToCss for CounterStyleOrNone {
                 symbols_type.to_css(dest)?;
                 dest.write_str(" ")?;
                 symbols.to_css(dest)?;
                 dest.write_str(")")
             }
         }
     }
 }
+
+/// A settings tag, defined by a four-character tag and a setting value
+///
+/// For font-feature-settings, this is a tag and an integer,
+/// for font-variation-settings this is a tag and a float
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct FontSettingTag<T> {
+    /// A four-character tag, packed into a u32 (one byte per character)
+    pub tag: u32,
+    /// The value
+    pub value: T,
+}
+
+impl<T> OneOrMoreCommaSeparated for FontSettingTag<T> {}
+
+impl<T: ToCss> ToCss for FontSettingTag<T> {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        use byteorder::{WriteBytesExt, BigEndian};
+        use cssparser::serialize_string;
+        use std::str;
+
+        let mut raw: Vec<u8> = vec!();
+        raw.write_u32::<BigEndian>(self.tag).unwrap();
+        serialize_string(str::from_utf8(&raw).unwrap_or_default(), dest)?;
+
+        self.value.to_css(dest)
+    }
+}
+
+impl<T: Parse> Parse for FontSettingTag<T> {
+    /// https://www.w3.org/TR/css-fonts-3/#propdef-font-feature-settings
+    /// https://drafts.csswg.org/css-fonts-4/#low-level-font-variation-
+    /// settings-control-the-font-variation-settings-property
+    /// <string> [ on | off | <integer> ]
+    /// <string> <number>
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        use byteorder::{ReadBytesExt, BigEndian};
+        use std::io::Cursor;
+
+        let tag = try!(input.expect_string());
+
+        // allowed strings of length 4 containing chars: <U+20, U+7E>
+        if tag.len() != 4 ||
+           tag.chars().any(|c| c < ' ' || c > '~')
+        {
+            return Err(())
+        }
+
+        let mut raw = Cursor::new(tag.as_bytes());
+        let u_tag = raw.read_u32::<BigEndian>().unwrap();
+
+        Ok(FontSettingTag { tag: u_tag, value: T::parse(context, input)? })
+    }
+}
+
+
+/// A font settings value for font-variation-settings or font-feature-settings
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum FontSettings<T> {
+    /// No settings (default)
+    Normal,
+    /// Set of settings
+    Tag(Vec<FontSettingTag<T>>)
+}
+
+impl<T: ToCss> ToCss for FontSettings<T> {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            FontSettings::Normal => dest.write_str("normal"),
+            FontSettings::Tag(ref ftvs) => ftvs.to_css(dest)
+        }
+    }
+}
+
+impl<T: Parse> Parse for FontSettings<T> {
+    /// https://www.w3.org/TR/css-fonts-3/#propdef-font-feature-settings
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        if input.try(|i| i.expect_ident_matching("normal")).is_ok() {
+            return Ok(FontSettings::Normal);
+        }
+        Vec::parse(context, input).map(FontSettings::Tag)
+    }
+}
+
+/// An integer that can also parse "on" and "off",
+/// for font-feature-settings
+///
+/// Do not use this type anywhere except within FontSettings
+/// because it serializes with the preceding space
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct FontSettingTagInt(pub u32);
+/// A number value to be used for font-variation-settings
+///
+/// Do not use this type anywhere except within FontSettings
+/// because it serializes with the preceding space
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct FontSettingTagFloat(pub f32);
+
+impl ToCss for FontSettingTagInt {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match self.0 {
+            1 => Ok(()),
+            0 => dest.write_str(" off"),
+            x => write!(dest, " {}", x)
+        }
+    }
+}
+
+impl Parse for FontSettingTagInt {
+    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        if let Ok(value) = input.try(|input| input.expect_integer()) {
+            // handle integer, throw if it is negative
+            if value >= 0 {
+                Ok(FontSettingTagInt(value as u32))
+            } else {
+                Err(())
+            }
+        } else if let Ok(_) = input.try(|input| input.expect_ident_matching("on")) {
+            // on is an alias for '1'
+            Ok(FontSettingTagInt(1))
+        } else if let Ok(_) = input.try(|input| input.expect_ident_matching("off")) {
+            // off is an alias for '0'
+            Ok(FontSettingTagInt(0))
+        } else {
+            // empty value is an alias for '1'
+            Ok(FontSettingTagInt(1))
+        }
+    }
+}
+
+
+impl Parse for FontSettingTagFloat {
+    fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        input.expect_number().map(FontSettingTagFloat)
+    }
+}
+
+impl ToCss for FontSettingTagFloat {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        dest.write_str(" ")?;
+        self.0.to_css(dest)
+    }
+}
+
+
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -729,16 +729,22 @@ impl Percentage {
             _ => Err(())
         }
     }
 
     /// Parses a percentage token, but rejects it if it's negative.
     pub fn parse_non_negative(input: &mut Parser) -> Result<Self, ()> {
         Self::parse_with_clamping_mode(input, AllowedNumericType::NonNegative)
     }
+
+    /// 100%
+    #[inline]
+    pub fn hundred() -> Self {
+        Percentage(1.)
+    }
 }
 
 impl Parse for Percentage {
     #[inline]
     fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         Self::parse_with_clamping_mode(input, AllowedNumericType::All)
     }
 }
--- a/servo/tests/unit/style/parsing/font.rs
+++ b/servo/tests/unit/style/parsing/font.rs
@@ -1,62 +1,61 @@
 /* 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::properties::longhands::{font_feature_settings, font_weight};
 use style::properties::longhands::font_feature_settings::SpecifiedValue;
-use style::properties::longhands::font_feature_settings::computed_value;
-use style::properties::longhands::font_feature_settings::computed_value::FeatureTagValue;
+use style::values::generics::{FontSettings, FontSettingTag, FontSettingTagInt};
 use style_traits::ToCss;
 
 #[test]
 fn font_feature_settings_should_parse_properly() {
     use byteorder::{ReadBytesExt, BigEndian};
     use std::io::Cursor;
 
     let normal = parse_longhand!(font_feature_settings, "normal");
-    let normal_computed = SpecifiedValue::Value(computed_value::T::Normal);
+    let normal_computed = SpecifiedValue::Value(FontSettings::Normal);
     assert_eq!(normal, normal_computed);
 
     let mut a_d_bytes = Cursor::new(b"abcd");
     let mut e_h_bytes = Cursor::new(b"efgh");
 
     let abcd = a_d_bytes.read_u32::<BigEndian>().unwrap();
     let efgh = e_h_bytes.read_u32::<BigEndian>().unwrap();
 
     let on = parse_longhand!(font_feature_settings, "\"abcd\" on");
-    let on_computed = SpecifiedValue::Value(computed_value::T::Tag(vec![
-        FeatureTagValue { tag: abcd, value: 1 }
+    let on_computed = SpecifiedValue::Value(FontSettings::Tag(vec![
+        FontSettingTag { tag: abcd, value: FontSettingTagInt(1) }
     ]));
     assert_eq!(on, on_computed);
 
     let off = parse_longhand!(font_feature_settings, "\"abcd\" off");
-    let off_computed = SpecifiedValue::Value(computed_value::T::Tag(vec![
-        FeatureTagValue { tag: abcd, value: 0 }
+    let off_computed = SpecifiedValue::Value(FontSettings::Tag(vec![
+        FontSettingTag { tag: abcd, value: FontSettingTagInt(0) }
     ]));
     assert_eq!(off, off_computed);
 
     let no_value = parse_longhand!(font_feature_settings, "\"abcd\"");
-    let no_value_computed = SpecifiedValue::Value(computed_value::T::Tag(vec![
-        FeatureTagValue { tag: abcd, value: 1 }
+    let no_value_computed = SpecifiedValue::Value(FontSettings::Tag(vec![
+        FontSettingTag { tag: abcd, value: FontSettingTagInt(1) }
     ]));
     assert_eq!(no_value, no_value_computed);
 
     let pos_integer = parse_longhand!(font_feature_settings, "\"abcd\" 100");
-    let pos_integer_computed = SpecifiedValue::Value(computed_value::T::Tag(vec![
-        FeatureTagValue { tag: abcd, value: 100 }
+    let pos_integer_computed = SpecifiedValue::Value(FontSettings::Tag(vec![
+        FontSettingTag { tag: abcd, value: FontSettingTagInt(100) }
     ]));
     assert_eq!(pos_integer, pos_integer_computed);
 
     let multiple = parse_longhand!(font_feature_settings, "\"abcd\" off, \"efgh\"");
-    let multiple_computed = SpecifiedValue::Value(computed_value::T::Tag(vec![
-        FeatureTagValue { tag: abcd, value: 0 },
-        FeatureTagValue { tag: efgh, value: 1 }
+    let multiple_computed = SpecifiedValue::Value(FontSettings::Tag(vec![
+        FontSettingTag { tag: abcd, value: FontSettingTagInt(0) },
+        FontSettingTag { tag: efgh, value: FontSettingTagInt(1) }
     ]));
     assert_eq!(multiple, multiple_computed);
 }
 
 #[test]
 fn font_feature_settings_should_throw_on_bad_input() {
     assert!(parse(font_feature_settings::parse, "").is_err());
     assert!(parse(font_feature_settings::parse, "\"abcd\" -1").is_err());