merge autoland to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sun, 24 Sep 2017 23:50:59 +0200
changeset 382646 d9cfaad3b11ef5cde2a55782ad67aa7eb5fd55e4
parent 382640 877c02446745576ac97f181dd9eeec78ef605451 (current diff)
parent 382645 f51e7af7a2bca24cb91488003d5f76c093b5929a (diff)
child 382647 b44e80e0590e6eed86a1902365424673c8c2eaec
push id95377
push userarchaeopteryx@coole-files.de
push dateSun, 24 Sep 2017 21:55:15 +0000
treeherdermozilla-inbound@ad95f3757337 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone58.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
merge autoland to mozilla-central. r=merge a=merge MozReview-Commit-ID: 3EvlUvJIymk
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -347,17 +347,16 @@ opaque-types = [
     "mozilla::dom::.*Callback", # Pulls in ErrorResult and other things that
                                 # don't align properly on Linux 32-bit
 ]
 mapped-generic-types = [
     { generic = true, gecko = "mozilla::ServoUnsafeCell", servo = "::std::cell::UnsafeCell" },
     { generic = true, gecko = "mozilla::ServoCell", servo = "::std::cell::Cell" },
     { generic = false, gecko = "ServoNodeData", servo = "AtomicRefCell<ElementData>" },
     { generic = false, gecko = "mozilla::ServoWritingMode", servo = "::logical_geometry::WritingMode" },
-    { generic = false, gecko = "mozilla::ServoFontComputationData", servo = "::properties::FontComputationData" },
     { generic = false, gecko = "mozilla::ServoCustomPropertiesMap", servo = "Option<::servo_arc::Arc<::custom_properties::CustomPropertiesMap>>" },
     { generic = false, gecko = "mozilla::ServoRuleNode", servo = "Option<::rule_tree::StrongRuleNode>" },
     { generic = false, gecko = "mozilla::ServoVisitedStyle", servo = "Option<::servo_arc::RawOffsetArc<::properties::ComputedValues>>" },
     { generic = false, gecko = "mozilla::ServoComputedValueFlags", servo = "::properties::computed_value_flags::ComputedValueFlags" },
     { generic = true, gecko = "mozilla::ServoRawOffsetArc", servo = "::servo_arc::RawOffsetArc" },
     { generic = false, gecko = "ServoStyleContextStrong", servo = "::gecko_bindings::sugar::ownership::Strong<::properties::ComputedValues>" },
 ]
 fixups = [
--- a/layout/style/ServoTypes.h
+++ b/layout/style/ServoTypes.h
@@ -130,44 +130,16 @@ enum class InheritTarget {
   // We're requesting a style for a placeholder frame.
   PlaceholderFrame,
 };
 
 struct ServoWritingMode {
   uint8_t mBits;
 };
 
-// Don't attempt to read from this
-// (see comment on ServoFontComputationData
-enum ServoKeywordSize {
-  Empty, // when the Option is None
-  XXSmall,
-  XSmall,
-  Small,
-  Medium,
-  Large,
-  XLarge,
-  XXLarge,
-  XXXLarge,
-};
-
-// Don't attempt to read from this. We can't
-// always guarantee that the interior representation
-// of this is correct (the mKeyword field may have a different padding),
-// but the entire struct should
-// have the same size and alignment as the Rust version.
-// Ensure layout tests get run if touching either side.
-struct ServoFontComputationData {
-  ServoKeywordSize mKeyword;
-  float/*32_t*/ mRatio;
-  int32_t mAbsolute;
-
-  static_assert(sizeof(float) == 4, "float should be 32 bit");
-};
-
 struct ServoCustomPropertiesMap {
   uintptr_t mPtr;
 };
 
 struct ServoRuleNode {
   uintptr_t mPtr;
 };
 
@@ -265,24 +237,16 @@ private:
   /// node.  Can be None for default values and text nodes.  This is
   /// essentially an optimization to avoid referencing the root rule node.
   mozilla::ServoRuleNode rules;
   /// The element's computed values if visited, only computed if there's a
   /// relevant link for this element. A element's "relevant link" is the
   /// element being matched if it is a link or the nearest ancestor link.
   mozilla::ServoVisitedStyle visited_style;
 
-  // this is the last member because most of the other members
-  // are pointer sized. This makes it easier to deal with the
-  // alignment of the fields when replacing things via bindgen
-  //
-  // This is opaque, please don't read from it from C++
-  // (see comment on ServoFontComputationData)
-  mozilla::ServoFontComputationData font_computation_data;
-
   // C++ just sees this struct as a bucket of bits, and will
   // do the wrong thing if we let it use the default copy ctor/assignment
   // operator. Remove them so that there is no footgun.
   //
   // We remove the move ctor/assignment operator as well, because
   // moves in C++ don't prevent destructors from being called,
   // which will lead to double frees.
   ServoComputedData& operator=(const ServoComputedData&) = delete;
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -583,16 +583,17 @@ enum class StyleDisplay : uint8_t {
 #define NS_STYLE_FONT_SIZE_SMALL                2
 #define NS_STYLE_FONT_SIZE_MEDIUM               3
 #define NS_STYLE_FONT_SIZE_LARGE                4
 #define NS_STYLE_FONT_SIZE_XLARGE               5
 #define NS_STYLE_FONT_SIZE_XXLARGE              6
 #define NS_STYLE_FONT_SIZE_XXXLARGE             7  // Only used by <font size="7">. Not specifiable in CSS.
 #define NS_STYLE_FONT_SIZE_LARGER               8
 #define NS_STYLE_FONT_SIZE_SMALLER              9
+#define NS_STYLE_FONT_SIZE_NO_KEYWORD          10 // Used by Servo to track the "no keyword" case
 
 // See nsStyleFont
 // We should eventually stop using the NS_STYLE_* variants here.
 #define NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED   NS_FONT_STRETCH_ULTRA_CONDENSED
 #define NS_STYLE_FONT_STRETCH_EXTRA_CONDENSED   NS_FONT_STRETCH_EXTRA_CONDENSED
 #define NS_STYLE_FONT_STRETCH_CONDENSED         NS_FONT_STRETCH_CONDENSED
 #define NS_STYLE_FONT_STRETCH_SEMI_CONDENSED    NS_FONT_STRETCH_SEMI_CONDENSED
 #define NS_STYLE_FONT_STRETCH_NORMAL            NS_FONT_STRETCH_NORMAL
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -106,16 +106,19 @@ safe_strcmp(const char16_t* a, const cha
 static bool AreShadowArraysEqual(nsCSSShadowArray* lhs, nsCSSShadowArray* rhs);
 
 // --------------------
 // nsStyleFont
 //
 nsStyleFont::nsStyleFont(const nsFont& aFont, const nsPresContext* aContext)
   : mFont(aFont)
   , mSize(nsStyleFont::ZoomText(aContext, mFont.size))
+  , mFontSizeFactor(1.0)
+  , mFontSizeOffset(0)
+  , mFontSizeKeyword(NS_STYLE_FONT_SIZE_MEDIUM)
   , mGenericID(kGenericFont_NONE)
   , mScriptLevel(0)
   , mMathVariant(NS_MATHML_MATHVARIANT_NONE)
   , mMathDisplay(NS_MATHML_DISPLAYSTYLE_INLINE)
   , mMinFontSizeRatio(100) // 100%
   , mExplicitLanguage(false)
   , mAllowZoom(true)
   , mScriptUnconstrainedSize(mSize)
@@ -126,16 +129,19 @@ nsStyleFont::nsStyleFont(const nsFont& a
 {
   MOZ_COUNT_CTOR(nsStyleFont);
   mFont.size = mSize;
 }
 
 nsStyleFont::nsStyleFont(const nsStyleFont& aSrc)
   : mFont(aSrc.mFont)
   , mSize(aSrc.mSize)
+  , mFontSizeFactor(aSrc.mFontSizeFactor)
+  , mFontSizeOffset(aSrc.mFontSizeOffset)
+  , mFontSizeKeyword(aSrc.mFontSizeKeyword)
   , mGenericID(aSrc.mGenericID)
   , mScriptLevel(aSrc.mScriptLevel)
   , mMathVariant(aSrc.mMathVariant)
   , mMathDisplay(aSrc.mMathDisplay)
   , mMinFontSizeRatio(aSrc.mMinFontSizeRatio)
   , mExplicitLanguage(aSrc.mExplicitLanguage)
   , mAllowZoom(aSrc.mAllowZoom)
   , mScriptUnconstrainedSize(aSrc.mScriptUnconstrainedSize)
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -193,16 +193,24 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
 
   nsFont  mFont;        // [inherited]
   nscoord mSize;        // [inherited] Our "computed size". Can be different
                         // from mFont.size which is our "actual size" and is
                         // enforced to be >= the user's preferred min-size.
                         // mFont.size should be used for display purposes
                         // while mSize is the value to return in
                         // getComputedStyle() for example.
+
+  // In stylo these three track whether the size is keyword-derived
+  // and if so if it has been modified by a factor/offset
+  float mFontSizeFactor;
+  nscoord mFontSizeOffset;
+  uint8_t mFontSizeKeyword; // NS_STYLE_FONT_SIZE_*, is NS_STYLE_FONT_SIZE_NO_KEYWORD
+                            // when not keyword-derived
+
   uint8_t mGenericID;   // [inherited] generic CSS font family, if any;
                         // value is a kGenericFont_* constant, see nsFont.h.
 
   // MathML scriptlevel support
   int8_t  mScriptLevel;          // [inherited]
   // MathML  mathvariant support
   uint8_t mMathVariant;          // [inherited]
   // MathML displaystyle support
--- a/servo/components/gfx/font_context.rs
+++ b/servo/components/gfx/font_context.rs
@@ -113,17 +113,17 @@ impl FontContext {
     /// a cached font if this font instance has already been used by
     /// this context.
     pub fn layout_font_group_for_style(&mut self, style: ServoArc<style_structs::Font>)
                                        -> Rc<FontGroup> {
         self.expire_font_caches_if_necessary();
 
         let layout_font_group_cache_key = LayoutFontGroupCacheKey {
             pointer: style.clone(),
-            size: Au::from(style.font_size),
+            size: style.font_size.size(),
         };
         if let Some(ref cached_font_group) = self.layout_font_group_cache.get(
                 &layout_font_group_cache_key) {
             return (*cached_font_group).clone()
         }
 
         // TODO: The font context holds a strong ref to the cached fonts
         // so they will never be released. Find out a good time to drop them.
@@ -143,17 +143,17 @@ impl FontContext {
                     match cached_font_entry.font {
                         None => {
                             cache_hit = true;
                             break;
                         }
                         Some(ref cached_font_ref) => {
                             let cached_font = (*cached_font_ref).borrow();
                             if cached_font.descriptor == desc &&
-                               cached_font.requested_pt_size == Au::from(style.font_size) &&
+                               cached_font.requested_pt_size == style.font_size.size() &&
                                cached_font.variant == style.font_variant_caps {
                                 fonts.push((*cached_font_ref).clone());
                                 cache_hit = true;
                                 break;
                             }
                         }
                     }
                 }
@@ -161,17 +161,17 @@ impl FontContext {
 
             if !cache_hit {
                 let template_info = self.font_cache_thread.find_font_template(family.clone(),
                                                                              desc.clone());
                 match template_info {
                     Some(template_info) => {
                         let layout_font = self.create_layout_font(template_info.font_template,
                                                                   desc.clone(),
-                                                                  Au::from(style.font_size),
+                                                                  style.font_size.size(),
                                                                   style.font_variant_caps,
                                                                   template_info.font_key);
                         let font = match layout_font {
                             Ok(layout_font) => {
                                 let layout_font = Rc::new(RefCell::new(layout_font));
                                 fonts.push(layout_font.clone());
 
                                 Some(layout_font)
@@ -194,29 +194,29 @@ impl FontContext {
             }
         }
 
         // Add a last resort font as a fallback option.
         let mut cache_hit = false;
         for cached_font_entry in &self.fallback_font_cache {
             let cached_font = cached_font_entry.font.borrow();
             if cached_font.descriptor == desc &&
-                        cached_font.requested_pt_size == Au::from(style.font_size) &&
+                        cached_font.requested_pt_size == style.font_size.size() &&
                         cached_font.variant == style.font_variant_caps {
                 fonts.push(cached_font_entry.font.clone());
                 cache_hit = true;
                 break;
             }
         }
 
         if !cache_hit {
             let template_info = self.font_cache_thread.last_resort_font_template(desc.clone());
             let layout_font = self.create_layout_font(template_info.font_template,
                                                       desc.clone(),
-                                                      Au::from(style.font_size),
+                                                      style.font_size.size(),
                                                       style.font_variant_caps,
                                                       template_info.font_key);
             match layout_font {
                 Ok(layout_font) => {
                     let layout_font = Rc::new(RefCell::new(layout_font));
                     self.fallback_font_cache.push(FallbackFontCacheEntry {
                         font: layout_font.clone(),
                     });
--- a/servo/components/layout/multicol.rs
+++ b/servo/components/layout/multicol.rs
@@ -92,20 +92,20 @@ impl Flow for MulticolFlow {
         self.block_flow.assign_inline_sizes(layout_context);
         let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
         let content_inline_size =
             self.block_flow.fragment.border_box.size.inline - padding_and_borders;
         let column_width;
         {
             let column_style = self.block_flow.fragment.style.get_column();
 
-            let column_gap = Au::from(match column_style.column_gap {
-                Either::First(len) => len,
-                Either::Second(_normal) => self.block_flow.fragment.style.get_font().font_size,
-            });
+            let column_gap = match column_style.column_gap {
+                Either::First(len) => len.into(),
+                Either::Second(_normal) => self.block_flow.fragment.style.get_font().font_size.size(),
+            };
 
             let mut column_count;
             if let Either::First(column_width) = column_style.column_width {
                 let column_width = Au::from(column_width);
                 column_count =
                     max(1, (content_inline_size + column_gap).0 / (column_width + column_gap).0);
                 if let Either::First(specified_column_count) = column_style.column_count {
                     column_count = min(column_count, specified_column_count.0 as i32);
--- a/servo/components/layout/text.rs
+++ b/servo/components/layout/text.rs
@@ -442,17 +442,17 @@ pub fn font_metrics_for_style(font_conte
     let fontgroup = font_context.layout_font_group_for_style(font_style);
     // FIXME(https://github.com/rust-lang/rust/issues/23338)
     let font = fontgroup.fonts[0].borrow();
     font.metrics.clone()
 }
 
 /// Returns the line block-size needed by the given computed style and font size.
 pub fn line_height_from_style(style: &ComputedValues, metrics: &FontMetrics) -> Au {
-    let font_size = Au::from(style.get_font().font_size);
+    let font_size = style.get_font().font_size.size();
     match style.get_inheritedtext().line_height {
         LineHeight::Normal => Au::from(metrics.line_gap),
         LineHeight::Number(l) => font_size.scale_by(l.0),
         LineHeight::Length(l) => Au::from(l)
     }
 }
 
 fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragment>) {
--- a/servo/components/script/document_loader.rs
+++ b/servo/components/script/document_loader.rs
@@ -125,17 +125,17 @@ impl DocumentLoader {
                                   fetch_target: IpcSender<FetchResponseMsg>) {
         self.resource_threads.sender().send(CoreResourceMsg::Fetch(request, fetch_target)).unwrap();
     }
 
     /// Mark an in-progress network request complete.
     pub fn finish_load(&mut self, load: &LoadType) {
         debug!("Removing blocking load {:?} ({}).", load, self.blocking_loads.len());
         let idx = self.blocking_loads.iter().position(|unfinished| *unfinished == *load);
-        self.blocking_loads.remove(idx.expect(&format!("unknown completed load {:?}", load)));
+        self.blocking_loads.remove(idx.unwrap_or_else(|| panic!("unknown completed load {:?}", load)));
     }
 
     pub fn is_blocked(&self) -> bool {
         // TODO: Ensure that we report blocked if parsing is still ongoing.
         !self.blocking_loads.is_empty()
     }
 
     pub fn inhibit_events(&mut self) {
--- a/servo/components/style/gecko/generated/structs_debug.rs
+++ b/servo/components/style/gecko/generated/structs_debug.rs
@@ -5,17 +5,16 @@ pub use self::root::mozilla::*;
 pub use self::root::mozilla::css::*;
 pub use self::root::mozilla::dom::*;
 use atomic_refcell::AtomicRefCell;
 use data::ElementData;
 pub type ServoUnsafeCell<T> = ::std::cell::UnsafeCell<T>;
 pub type ServoCell<T> = ::std::cell::Cell<T>;
 pub type ServoNodeData = AtomicRefCell<ElementData>;
 pub type ServoWritingMode = ::logical_geometry::WritingMode;
-pub type ServoFontComputationData = ::properties::FontComputationData;
 pub type ServoCustomPropertiesMap = Option<::servo_arc::Arc<::custom_properties::CustomPropertiesMap>>;
 pub type ServoRuleNode = Option<::rule_tree::StrongRuleNode>;
 pub type ServoVisitedStyle = Option<::servo_arc::RawOffsetArc<::properties::ComputedValues>>;
 pub type ServoComputedValueFlags = ::properties::computed_value_flags::ComputedValueFlags;
 pub type ServoRawOffsetArc<T> = ::servo_arc::RawOffsetArc<T>;
 pub type ServoStyleContextStrong = ::gecko_bindings::sugar::ownership::Strong<::properties::ComputedValues>;
 
 #[allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
@@ -435,16 +434,17 @@ pub mod root {
     pub const NS_STYLE_FONT_SIZE_SMALL: ::std::os::raw::c_uint = 2;
     pub const NS_STYLE_FONT_SIZE_MEDIUM: ::std::os::raw::c_uint = 3;
     pub const NS_STYLE_FONT_SIZE_LARGE: ::std::os::raw::c_uint = 4;
     pub const NS_STYLE_FONT_SIZE_XLARGE: ::std::os::raw::c_uint = 5;
     pub const NS_STYLE_FONT_SIZE_XXLARGE: ::std::os::raw::c_uint = 6;
     pub const NS_STYLE_FONT_SIZE_XXXLARGE: ::std::os::raw::c_uint = 7;
     pub const NS_STYLE_FONT_SIZE_LARGER: ::std::os::raw::c_uint = 8;
     pub const NS_STYLE_FONT_SIZE_SMALLER: ::std::os::raw::c_uint = 9;
+    pub const NS_STYLE_FONT_SIZE_NO_KEYWORD: ::std::os::raw::c_uint = 10;
     pub const NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED: ::std::os::raw::c_int =
         -4;
     pub const NS_STYLE_FONT_STRETCH_EXTRA_CONDENSED: ::std::os::raw::c_int =
         -3;
     pub const NS_STYLE_FONT_STRETCH_CONDENSED: ::std::os::raw::c_int = -2;
     pub const NS_STYLE_FONT_STRETCH_SEMI_CONDENSED: ::std::os::raw::c_int =
         -1;
     pub const NS_STYLE_FONT_STRETCH_NORMAL: ::std::os::raw::c_uint = 0;
@@ -4457,41 +4457,28 @@ pub mod root {
         pub type ParsingMode = u8;
         #[repr(i32)]
         #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
         pub enum InheritTarget {
             Text = 0,
             FirstLetterContinuation = 1,
             PlaceholderFrame = 2,
         }
-        #[repr(u32)]
-        #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-        pub enum ServoKeywordSize {
-            Empty = 0,
-            XXSmall = 1,
-            XSmall = 2,
-            Small = 3,
-            Medium = 4,
-            Large = 5,
-            XLarge = 6,
-            XXLarge = 7,
-            XXXLarge = 8,
-        }
         #[repr(C)]
         #[derive(Debug)]
         pub struct ServoStyleContext {
             pub _base: root::nsStyleContext,
             pub mPresContext: *mut root::nsPresContext,
             pub mSource: root::ServoComputedData,
             pub mNextInheritingAnonBoxStyle: root::RefPtr<root::mozilla::ServoStyleContext>,
             pub mNextLazyPseudoStyle: root::RefPtr<root::mozilla::ServoStyleContext>,
         }
         #[test]
         fn bindgen_test_layout_ServoStyleContext() {
-            assert_eq!(::std::mem::size_of::<ServoStyleContext>() , 280usize ,
+            assert_eq!(::std::mem::size_of::<ServoStyleContext>() , 264usize ,
                        concat ! (
                        "Size of: " , stringify ! ( ServoStyleContext ) ));
             assert_eq! (::std::mem::align_of::<ServoStyleContext>() , 8usize ,
                         concat ! (
                         "Alignment of " , stringify ! ( ServoStyleContext )
                         ));
             assert_eq! (unsafe {
                         & ( * ( 0 as * const ServoStyleContext ) ) .
@@ -4504,36 +4491,36 @@ pub mod root {
                         & ( * ( 0 as * const ServoStyleContext ) ) . mSource
                         as * const _ as usize } , 32usize , concat ! (
                         "Alignment of field: " , stringify ! (
                         ServoStyleContext ) , "::" , stringify ! ( mSource )
                         ));
             assert_eq! (unsafe {
                         & ( * ( 0 as * const ServoStyleContext ) ) .
                         mNextInheritingAnonBoxStyle as * const _ as usize } ,
-                        264usize , concat ! (
+                        248usize , concat ! (
                         "Alignment of field: " , stringify ! (
                         ServoStyleContext ) , "::" , stringify ! (
                         mNextInheritingAnonBoxStyle ) ));
             assert_eq! (unsafe {
                         & ( * ( 0 as * const ServoStyleContext ) ) .
                         mNextLazyPseudoStyle as * const _ as usize } ,
-                        272usize , concat ! (
+                        256usize , concat ! (
                         "Alignment of field: " , stringify ! (
                         ServoStyleContext ) , "::" , stringify ! (
                         mNextLazyPseudoStyle ) ));
         }
         #[repr(C)]
         #[derive(Debug)]
         pub struct GeckoFont {
             pub gecko: root::nsStyleFont,
         }
         #[test]
         fn bindgen_test_layout_GeckoFont() {
-            assert_eq!(::std::mem::size_of::<GeckoFont>() , 120usize , concat
+            assert_eq!(::std::mem::size_of::<GeckoFont>() , 128usize , concat
                        ! ( "Size of: " , stringify ! ( GeckoFont ) ));
             assert_eq! (::std::mem::align_of::<GeckoFont>() , 8usize , concat
                         ! ( "Alignment of " , stringify ! ( GeckoFont ) ));
             assert_eq! (unsafe {
                         & ( * ( 0 as * const GeckoFont ) ) . gecko as * const
                         _ as usize } , 0usize , concat ! (
                         "Alignment of field: " , stringify ! ( GeckoFont ) ,
                         "::" , stringify ! ( gecko ) ));
@@ -12642,100 +12629,118 @@ pub mod root {
     /// RestyleManager::RestyleHintToString.
     #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
     pub struct nsRestyleHint(pub u32);
     #[repr(C)]
     #[derive(Debug)]
     pub struct nsStyleFont {
         pub mFont: root::nsFont,
         pub mSize: root::nscoord,
+        pub mFontSizeFactor: f32,
+        pub mFontSizeOffset: root::nscoord,
+        pub mFontSizeKeyword: u8,
         pub mGenericID: u8,
         pub mScriptLevel: i8,
         pub mMathVariant: u8,
         pub mMathDisplay: u8,
         pub mMinFontSizeRatio: u8,
         pub mExplicitLanguage: bool,
         pub mAllowZoom: bool,
         pub mScriptUnconstrainedSize: root::nscoord,
         pub mScriptMinSize: root::nscoord,
         pub mScriptSizeMultiplier: f32,
         pub mLanguage: root::nsCOMPtr<root::nsIAtom>,
     }
     pub const nsStyleFont_kHasFinishStyle: bool = false;
     #[test]
     fn bindgen_test_layout_nsStyleFont() {
-        assert_eq!(::std::mem::size_of::<nsStyleFont>() , 120usize , concat !
+        assert_eq!(::std::mem::size_of::<nsStyleFont>() , 128usize , concat !
                    ( "Size of: " , stringify ! ( nsStyleFont ) ));
         assert_eq! (::std::mem::align_of::<nsStyleFont>() , 8usize , concat !
                     ( "Alignment of " , stringify ! ( nsStyleFont ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mFont as * const _
                     as usize } , 0usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mFont ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mSize as * const _
                     as usize } , 88usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mSize ) ));
         assert_eq! (unsafe {
+                    & ( * ( 0 as * const nsStyleFont ) ) . mFontSizeFactor as
+                    * const _ as usize } , 92usize , concat ! (
+                    "Alignment of field: " , stringify ! ( nsStyleFont ) ,
+                    "::" , stringify ! ( mFontSizeFactor ) ));
+        assert_eq! (unsafe {
+                    & ( * ( 0 as * const nsStyleFont ) ) . mFontSizeOffset as
+                    * const _ as usize } , 96usize , concat ! (
+                    "Alignment of field: " , stringify ! ( nsStyleFont ) ,
+                    "::" , stringify ! ( mFontSizeOffset ) ));
+        assert_eq! (unsafe {
+                    & ( * ( 0 as * const nsStyleFont ) ) . mFontSizeKeyword as
+                    * const _ as usize } , 100usize , concat ! (
+                    "Alignment of field: " , stringify ! ( nsStyleFont ) ,
+                    "::" , stringify ! ( mFontSizeKeyword ) ));
+        assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mGenericID as *
-                    const _ as usize } , 92usize , concat ! (
+                    const _ as usize } , 101usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mGenericID ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mScriptLevel as *
-                    const _ as usize } , 93usize , concat ! (
+                    const _ as usize } , 102usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mScriptLevel ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mMathVariant as *
-                    const _ as usize } , 94usize , concat ! (
+                    const _ as usize } , 103usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mMathVariant ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mMathDisplay as *
-                    const _ as usize } , 95usize , concat ! (
+                    const _ as usize } , 104usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mMathDisplay ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mMinFontSizeRatio
-                    as * const _ as usize } , 96usize , concat ! (
+                    as * const _ as usize } , 105usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mMinFontSizeRatio ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mExplicitLanguage
-                    as * const _ as usize } , 97usize , concat ! (
+                    as * const _ as usize } , 106usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mExplicitLanguage ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mAllowZoom as *
-                    const _ as usize } , 98usize , concat ! (
+                    const _ as usize } , 107usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mAllowZoom ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) .
                     mScriptUnconstrainedSize as * const _ as usize } ,
-                    100usize , concat ! (
+                    108usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mScriptUnconstrainedSize ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mScriptMinSize as *
-                    const _ as usize } , 104usize , concat ! (
+                    const _ as usize } , 112usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mScriptMinSize ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) .
-                    mScriptSizeMultiplier as * const _ as usize } , 108usize ,
+                    mScriptSizeMultiplier as * const _ as usize } , 116usize ,
                     concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mScriptSizeMultiplier ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mLanguage as *
-                    const _ as usize } , 112usize , concat ! (
+                    const _ as usize } , 120usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mLanguage ) ));
     }
     #[repr(C)]
     #[derive(Debug)]
     pub struct nsStyleColor {
         pub mColor: root::nscolor,
     }
@@ -14804,21 +14809,20 @@ pub mod root {
         /// The rule node representing the ordered list of rules matched for this
         /// /// node.  Can be None for default values and text nodes.  This is
         /// /// essentially an optimization to avoid referencing the root rule node.
         pub rules: ::gecko_bindings::structs::ServoRuleNode,
         /// The element's computed values if visited, only computed if there's a
         /// /// relevant link for this element. A element's "relevant link" is the
         /// /// element being matched if it is a link or the nearest ancestor link.
         pub visited_style: ::gecko_bindings::structs::ServoVisitedStyle,
-        pub font_computation_data: ::gecko_bindings::structs::ServoFontComputationData,
     }
     #[test]
     fn bindgen_test_layout_ServoComputedData() {
-        assert_eq!(::std::mem::size_of::<ServoComputedData>() , 232usize ,
+        assert_eq!(::std::mem::size_of::<ServoComputedData>() , 216usize ,
                    concat ! ( "Size of: " , stringify ! ( ServoComputedData )
                    ));
         assert_eq! (::std::mem::align_of::<ServoComputedData>() , 8usize ,
                     concat ! (
                     "Alignment of " , stringify ! ( ServoComputedData ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const ServoComputedData ) ) . Font as *
                     const _ as usize } , 0usize , concat ! (
@@ -14955,22 +14959,16 @@ pub mod root {
                     const _ as usize } , 200usize , concat ! (
                     "Alignment of field: " , stringify ! ( ServoComputedData )
                     , "::" , stringify ! ( rules ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const ServoComputedData ) ) . visited_style
                     as * const _ as usize } , 208usize , concat ! (
                     "Alignment of field: " , stringify ! ( ServoComputedData )
                     , "::" , stringify ! ( visited_style ) ));
-        assert_eq! (unsafe {
-                    & ( * ( 0 as * const ServoComputedData ) ) .
-                    font_computation_data as * const _ as usize } , 216usize ,
-                    concat ! (
-                    "Alignment of field: " , stringify ! ( ServoComputedData )
-                    , "::" , stringify ! ( font_computation_data ) ));
     }
     #[repr(u32)]
     #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
     pub enum JSValueTag {
         JSVAL_TAG_MAX_DOUBLE = 131056,
         JSVAL_TAG_INT32 = 131057,
         JSVAL_TAG_UNDEFINED = 131058,
         JSVAL_TAG_NULL = 131059,
--- a/servo/components/style/gecko/generated/structs_release.rs
+++ b/servo/components/style/gecko/generated/structs_release.rs
@@ -5,17 +5,16 @@ pub use self::root::mozilla::*;
 pub use self::root::mozilla::css::*;
 pub use self::root::mozilla::dom::*;
 use atomic_refcell::AtomicRefCell;
 use data::ElementData;
 pub type ServoUnsafeCell<T> = ::std::cell::UnsafeCell<T>;
 pub type ServoCell<T> = ::std::cell::Cell<T>;
 pub type ServoNodeData = AtomicRefCell<ElementData>;
 pub type ServoWritingMode = ::logical_geometry::WritingMode;
-pub type ServoFontComputationData = ::properties::FontComputationData;
 pub type ServoCustomPropertiesMap = Option<::servo_arc::Arc<::custom_properties::CustomPropertiesMap>>;
 pub type ServoRuleNode = Option<::rule_tree::StrongRuleNode>;
 pub type ServoVisitedStyle = Option<::servo_arc::RawOffsetArc<::properties::ComputedValues>>;
 pub type ServoComputedValueFlags = ::properties::computed_value_flags::ComputedValueFlags;
 pub type ServoRawOffsetArc<T> = ::servo_arc::RawOffsetArc<T>;
 pub type ServoStyleContextStrong = ::gecko_bindings::sugar::ownership::Strong<::properties::ComputedValues>;
 
 #[allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
@@ -435,16 +434,17 @@ pub mod root {
     pub const NS_STYLE_FONT_SIZE_SMALL: ::std::os::raw::c_uint = 2;
     pub const NS_STYLE_FONT_SIZE_MEDIUM: ::std::os::raw::c_uint = 3;
     pub const NS_STYLE_FONT_SIZE_LARGE: ::std::os::raw::c_uint = 4;
     pub const NS_STYLE_FONT_SIZE_XLARGE: ::std::os::raw::c_uint = 5;
     pub const NS_STYLE_FONT_SIZE_XXLARGE: ::std::os::raw::c_uint = 6;
     pub const NS_STYLE_FONT_SIZE_XXXLARGE: ::std::os::raw::c_uint = 7;
     pub const NS_STYLE_FONT_SIZE_LARGER: ::std::os::raw::c_uint = 8;
     pub const NS_STYLE_FONT_SIZE_SMALLER: ::std::os::raw::c_uint = 9;
+    pub const NS_STYLE_FONT_SIZE_NO_KEYWORD: ::std::os::raw::c_uint = 10;
     pub const NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED: ::std::os::raw::c_int =
         -4;
     pub const NS_STYLE_FONT_STRETCH_EXTRA_CONDENSED: ::std::os::raw::c_int =
         -3;
     pub const NS_STYLE_FONT_STRETCH_CONDENSED: ::std::os::raw::c_int = -2;
     pub const NS_STYLE_FONT_STRETCH_SEMI_CONDENSED: ::std::os::raw::c_int =
         -1;
     pub const NS_STYLE_FONT_STRETCH_NORMAL: ::std::os::raw::c_uint = 0;
@@ -4377,41 +4377,28 @@ pub mod root {
         pub type ParsingMode = u8;
         #[repr(i32)]
         #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
         pub enum InheritTarget {
             Text = 0,
             FirstLetterContinuation = 1,
             PlaceholderFrame = 2,
         }
-        #[repr(u32)]
-        #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-        pub enum ServoKeywordSize {
-            Empty = 0,
-            XXSmall = 1,
-            XSmall = 2,
-            Small = 3,
-            Medium = 4,
-            Large = 5,
-            XLarge = 6,
-            XXLarge = 7,
-            XXXLarge = 8,
-        }
         #[repr(C)]
         #[derive(Debug)]
         pub struct ServoStyleContext {
             pub _base: root::nsStyleContext,
             pub mPresContext: *mut root::nsPresContext,
             pub mSource: root::ServoComputedData,
             pub mNextInheritingAnonBoxStyle: root::RefPtr<root::mozilla::ServoStyleContext>,
             pub mNextLazyPseudoStyle: root::RefPtr<root::mozilla::ServoStyleContext>,
         }
         #[test]
         fn bindgen_test_layout_ServoStyleContext() {
-            assert_eq!(::std::mem::size_of::<ServoStyleContext>() , 272usize ,
+            assert_eq!(::std::mem::size_of::<ServoStyleContext>() , 256usize ,
                        concat ! (
                        "Size of: " , stringify ! ( ServoStyleContext ) ));
             assert_eq! (::std::mem::align_of::<ServoStyleContext>() , 8usize ,
                         concat ! (
                         "Alignment of " , stringify ! ( ServoStyleContext )
                         ));
             assert_eq! (unsafe {
                         & ( * ( 0 as * const ServoStyleContext ) ) .
@@ -4424,36 +4411,36 @@ pub mod root {
                         & ( * ( 0 as * const ServoStyleContext ) ) . mSource
                         as * const _ as usize } , 24usize , concat ! (
                         "Alignment of field: " , stringify ! (
                         ServoStyleContext ) , "::" , stringify ! ( mSource )
                         ));
             assert_eq! (unsafe {
                         & ( * ( 0 as * const ServoStyleContext ) ) .
                         mNextInheritingAnonBoxStyle as * const _ as usize } ,
-                        256usize , concat ! (
+                        240usize , concat ! (
                         "Alignment of field: " , stringify ! (
                         ServoStyleContext ) , "::" , stringify ! (
                         mNextInheritingAnonBoxStyle ) ));
             assert_eq! (unsafe {
                         & ( * ( 0 as * const ServoStyleContext ) ) .
                         mNextLazyPseudoStyle as * const _ as usize } ,
-                        264usize , concat ! (
+                        248usize , concat ! (
                         "Alignment of field: " , stringify ! (
                         ServoStyleContext ) , "::" , stringify ! (
                         mNextLazyPseudoStyle ) ));
         }
         #[repr(C)]
         #[derive(Debug)]
         pub struct GeckoFont {
             pub gecko: root::nsStyleFont,
         }
         #[test]
         fn bindgen_test_layout_GeckoFont() {
-            assert_eq!(::std::mem::size_of::<GeckoFont>() , 120usize , concat
+            assert_eq!(::std::mem::size_of::<GeckoFont>() , 128usize , concat
                        ! ( "Size of: " , stringify ! ( GeckoFont ) ));
             assert_eq! (::std::mem::align_of::<GeckoFont>() , 8usize , concat
                         ! ( "Alignment of " , stringify ! ( GeckoFont ) ));
             assert_eq! (unsafe {
                         & ( * ( 0 as * const GeckoFont ) ) . gecko as * const
                         _ as usize } , 0usize , concat ! (
                         "Alignment of field: " , stringify ! ( GeckoFont ) ,
                         "::" , stringify ! ( gecko ) ));
@@ -12489,100 +12476,118 @@ pub mod root {
     /// RestyleManager::RestyleHintToString.
     #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
     pub struct nsRestyleHint(pub u32);
     #[repr(C)]
     #[derive(Debug)]
     pub struct nsStyleFont {
         pub mFont: root::nsFont,
         pub mSize: root::nscoord,
+        pub mFontSizeFactor: f32,
+        pub mFontSizeOffset: root::nscoord,
+        pub mFontSizeKeyword: u8,
         pub mGenericID: u8,
         pub mScriptLevel: i8,
         pub mMathVariant: u8,
         pub mMathDisplay: u8,
         pub mMinFontSizeRatio: u8,
         pub mExplicitLanguage: bool,
         pub mAllowZoom: bool,
         pub mScriptUnconstrainedSize: root::nscoord,
         pub mScriptMinSize: root::nscoord,
         pub mScriptSizeMultiplier: f32,
         pub mLanguage: root::nsCOMPtr,
     }
     pub const nsStyleFont_kHasFinishStyle: bool = false;
     #[test]
     fn bindgen_test_layout_nsStyleFont() {
-        assert_eq!(::std::mem::size_of::<nsStyleFont>() , 120usize , concat !
+        assert_eq!(::std::mem::size_of::<nsStyleFont>() , 128usize , concat !
                    ( "Size of: " , stringify ! ( nsStyleFont ) ));
         assert_eq! (::std::mem::align_of::<nsStyleFont>() , 8usize , concat !
                     ( "Alignment of " , stringify ! ( nsStyleFont ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mFont as * const _
                     as usize } , 0usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mFont ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mSize as * const _
                     as usize } , 88usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mSize ) ));
         assert_eq! (unsafe {
+                    & ( * ( 0 as * const nsStyleFont ) ) . mFontSizeFactor as
+                    * const _ as usize } , 92usize , concat ! (
+                    "Alignment of field: " , stringify ! ( nsStyleFont ) ,
+                    "::" , stringify ! ( mFontSizeFactor ) ));
+        assert_eq! (unsafe {
+                    & ( * ( 0 as * const nsStyleFont ) ) . mFontSizeOffset as
+                    * const _ as usize } , 96usize , concat ! (
+                    "Alignment of field: " , stringify ! ( nsStyleFont ) ,
+                    "::" , stringify ! ( mFontSizeOffset ) ));
+        assert_eq! (unsafe {
+                    & ( * ( 0 as * const nsStyleFont ) ) . mFontSizeKeyword as
+                    * const _ as usize } , 100usize , concat ! (
+                    "Alignment of field: " , stringify ! ( nsStyleFont ) ,
+                    "::" , stringify ! ( mFontSizeKeyword ) ));
+        assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mGenericID as *
-                    const _ as usize } , 92usize , concat ! (
+                    const _ as usize } , 101usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mGenericID ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mScriptLevel as *
-                    const _ as usize } , 93usize , concat ! (
+                    const _ as usize } , 102usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mScriptLevel ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mMathVariant as *
-                    const _ as usize } , 94usize , concat ! (
+                    const _ as usize } , 103usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mMathVariant ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mMathDisplay as *
-                    const _ as usize } , 95usize , concat ! (
+                    const _ as usize } , 104usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mMathDisplay ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mMinFontSizeRatio
-                    as * const _ as usize } , 96usize , concat ! (
+                    as * const _ as usize } , 105usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mMinFontSizeRatio ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mExplicitLanguage
-                    as * const _ as usize } , 97usize , concat ! (
+                    as * const _ as usize } , 106usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mExplicitLanguage ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mAllowZoom as *
-                    const _ as usize } , 98usize , concat ! (
+                    const _ as usize } , 107usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mAllowZoom ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) .
                     mScriptUnconstrainedSize as * const _ as usize } ,
-                    100usize , concat ! (
+                    108usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mScriptUnconstrainedSize ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mScriptMinSize as *
-                    const _ as usize } , 104usize , concat ! (
+                    const _ as usize } , 112usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mScriptMinSize ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) .
-                    mScriptSizeMultiplier as * const _ as usize } , 108usize ,
+                    mScriptSizeMultiplier as * const _ as usize } , 116usize ,
                     concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mScriptSizeMultiplier ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const nsStyleFont ) ) . mLanguage as *
-                    const _ as usize } , 112usize , concat ! (
+                    const _ as usize } , 120usize , concat ! (
                     "Alignment of field: " , stringify ! ( nsStyleFont ) ,
                     "::" , stringify ! ( mLanguage ) ));
     }
     #[repr(C)]
     #[derive(Debug)]
     pub struct nsStyleColor {
         pub mColor: root::nscolor,
     }
@@ -14651,21 +14656,20 @@ pub mod root {
         /// The rule node representing the ordered list of rules matched for this
         /// /// node.  Can be None for default values and text nodes.  This is
         /// /// essentially an optimization to avoid referencing the root rule node.
         pub rules: ::gecko_bindings::structs::ServoRuleNode,
         /// The element's computed values if visited, only computed if there's a
         /// /// relevant link for this element. A element's "relevant link" is the
         /// /// element being matched if it is a link or the nearest ancestor link.
         pub visited_style: ::gecko_bindings::structs::ServoVisitedStyle,
-        pub font_computation_data: ::gecko_bindings::structs::ServoFontComputationData,
     }
     #[test]
     fn bindgen_test_layout_ServoComputedData() {
-        assert_eq!(::std::mem::size_of::<ServoComputedData>() , 232usize ,
+        assert_eq!(::std::mem::size_of::<ServoComputedData>() , 216usize ,
                    concat ! ( "Size of: " , stringify ! ( ServoComputedData )
                    ));
         assert_eq! (::std::mem::align_of::<ServoComputedData>() , 8usize ,
                     concat ! (
                     "Alignment of " , stringify ! ( ServoComputedData ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const ServoComputedData ) ) . Font as *
                     const _ as usize } , 0usize , concat ! (
@@ -14802,22 +14806,16 @@ pub mod root {
                     const _ as usize } , 200usize , concat ! (
                     "Alignment of field: " , stringify ! ( ServoComputedData )
                     , "::" , stringify ! ( rules ) ));
         assert_eq! (unsafe {
                     & ( * ( 0 as * const ServoComputedData ) ) . visited_style
                     as * const _ as usize } , 208usize , concat ! (
                     "Alignment of field: " , stringify ! ( ServoComputedData )
                     , "::" , stringify ! ( visited_style ) ));
-        assert_eq! (unsafe {
-                    & ( * ( 0 as * const ServoComputedData ) ) .
-                    font_computation_data as * const _ as usize } , 216usize ,
-                    concat ! (
-                    "Alignment of field: " , stringify ! ( ServoComputedData )
-                    , "::" , stringify ! ( font_computation_data ) ));
     }
     #[repr(u32)]
     #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
     pub enum JSValueTag {
         JSVAL_TAG_MAX_DOUBLE = 131056,
         JSVAL_TAG_INT32 = 131057,
         JSVAL_TAG_UNDEFINED = 131058,
         JSVAL_TAG_NULL = 131059,
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -73,17 +73,17 @@ unsafe impl Send for Device {}
 impl Device {
     /// Trivially constructs a new `Device`.
     pub fn new(pres_context: RawGeckoPresContextOwned) -> Self {
         assert!(!pres_context.is_null());
         Device {
             pres_context: pres_context,
             default_values: ComputedValues::default_values(unsafe { &*pres_context }),
             // FIXME(bz): Seems dubious?
-            root_font_size: AtomicIsize::new(font_size::get_initial_value().0.to_i32_au() as isize),
+            root_font_size: AtomicIsize::new(font_size::get_initial_value().size().0 as isize),
             body_text_color: AtomicUsize::new(unsafe { &*pres_context }.mDefaultColor as usize),
             used_root_font_size: AtomicBool::new(false),
             used_viewport_size: AtomicBool::new(false),
         }
     }
 
     /// Tells the device that a new viewport rule has been found, and stores the
     /// relevant viewport constraints.
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -506,17 +506,16 @@ pub trait MatchMethods : TElement {
     /// damage.
     fn finish_restyle(
         &self,
         context: &mut StyleContext<Self>,
         data: &mut ElementData,
         mut new_styles: ResolvedElementStyles,
         important_rules_changed: bool,
     ) -> ChildCascadeRequirement {
-        use app_units::Au;
         use dom::TNode;
         use std::cmp;
 
         self.process_animations(
             context,
             &mut data.styles.primary,
             &mut new_styles.primary.style.0,
             data.hint,
@@ -548,17 +547,17 @@ pub trait MatchMethods : TElement {
 
         let mut cascade_requirement = ChildCascadeRequirement::CanSkipCascade;
         if self.is_root() && !self.is_native_anonymous() {
             let device = context.shared.stylist.device();
             let new_font_size = new_primary_style.get_font().clone_font_size();
 
             if old_styles.primary.as_ref().map_or(true, |s| s.get_font().clone_font_size() != new_font_size) {
                 debug_assert!(self.owner_doc_matches_for_testing(device));
-                device.set_root_font_size(Au::from(new_font_size));
+                device.set_root_font_size(new_font_size.size());
                 // If the root font-size changed since last time, and something
                 // in the document did use rem units, ensure we recascade the
                 // entire tree.
                 if device.used_root_font_size() {
                     cascade_requirement = ChildCascadeRequirement::MustCascadeDescendants;
                 }
             }
         }
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -48,17 +48,17 @@ use gecko_bindings::sugar::ns_style_coor
 use gecko::values::convert_nscolor_to_rgba;
 use gecko::values::convert_rgba_to_nscolor;
 use gecko::values::GeckoStyleCoordConvertible;
 use gecko::values::round_border_to_device_pixels;
 use logical_geometry::WritingMode;
 use media_queries::Device;
 use properties::animated_properties::TransitionProperty;
 use properties::computed_value_flags::*;
-use properties::{default_font_size_keyword, longhands, FontComputationData, Importance, LonghandId};
+use properties::{longhands, Importance, LonghandId};
 use properties::{PropertyDeclaration, PropertyDeclarationBlock, PropertyDeclarationId};
 use rule_tree::StrongRuleNode;
 use selector_parser::PseudoElement;
 use servo_arc::{Arc, RawOffsetArc};
 use std::mem::{forget, uninitialized, transmute, zeroed};
 use std::{cmp, ops, ptr};
 use values::{self, Auto, CustomIdent, Either, KeyframesName, None_};
 use values::computed::{NonNegativeLength, ToComputedValue, Percentage};
@@ -80,28 +80,26 @@ pub struct ComputedValues(::gecko_bindin
 
 impl ComputedValues {
     pub fn new(
         device: &Device,
         parent: Option<<&ComputedValues>,
         pseudo: Option<<&PseudoElement>,
         custom_properties: Option<Arc<CustomPropertiesMap>>,
         writing_mode: WritingMode,
-        font_size_keyword: FontComputationData,
         flags: ComputedValueFlags,
         rules: Option<StrongRuleNode>,
         visited_style: Option<Arc<ComputedValues>>,
         % for style_struct in data.style_structs:
         ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
         % endfor
     ) -> Arc<Self> {
         ComputedValuesInner::new(
             custom_properties,
             writing_mode,
-            font_size_keyword,
             flags,
             rules,
             visited_style,
             % for style_struct in data.style_structs:
             ${style_struct.ident},
             % endfor
         ).to_outer(
             device.pres_context(),
@@ -109,17 +107,16 @@ impl ComputedValues {
             pseudo.map(|p| p.pseudo_info())
         )
     }
 
     pub fn default_values(pres_context: RawGeckoPresContextBorrowed) -> Arc<Self> {
         ComputedValuesInner::new(
             /* custom_properties = */ None,
             /* writing_mode = */ WritingMode::empty(), // FIXME(bz): This seems dubious
-            default_font_size_keyword(),
             ComputedValueFlags::empty(),
             /* rules = */ None,
             /* visited_style = */ None,
             % for style_struct in data.style_structs:
             style_structs::${style_struct.name}::default(pres_context),
             % endfor
         ).to_outer(pres_context, None, None)
     }
@@ -169,42 +166,39 @@ impl Clone for ComputedValues {
 impl Clone for ComputedValuesInner {
     fn clone(&self) -> Self {
         ComputedValuesInner {
             % for style_struct in data.style_structs:
                 ${style_struct.gecko_name}: self.${style_struct.gecko_name}.clone(),
             % endfor
             custom_properties: self.custom_properties.clone(),
             writing_mode: self.writing_mode.clone(),
-            font_computation_data: self.font_computation_data.clone(),
             flags: self.flags.clone(),
             rules: self.rules.clone(),
             visited_style: self.visited_style.clone(),
         }
     }
 }
 
 type PseudoInfo = (*mut structs::nsIAtom, structs::CSSPseudoElementType);
 type ParentStyleContextInfo<'a> = Option< &'a ComputedValues>;
 
 impl ComputedValuesInner {
     pub fn new(custom_properties: Option<Arc<CustomPropertiesMap>>,
                writing_mode: WritingMode,
-               font_size_keyword: FontComputationData,
                flags: ComputedValueFlags,
                rules: Option<StrongRuleNode>,
                visited_style: Option<Arc<ComputedValues>>,
                % for style_struct in data.style_structs:
                ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
                % endfor
     ) -> Self {
         ComputedValuesInner {
             custom_properties: custom_properties,
             writing_mode: writing_mode,
-            font_computation_data: font_size_keyword,
             rules: rules,
             visited_style: visited_style.map(|x| Arc::into_raw_offset(x)),
             flags: flags,
             % for style_struct in data.style_structs:
             ${style_struct.gecko_name}: Arc::into_raw_offset(${style_struct.ident}),
             % endfor
         }
     }
@@ -2162,35 +2156,54 @@ fn static_assert() {
 
     pub fn unzoom_fonts(&mut self, device: &Device) {
         self.gecko.mSize = device.unzoom_text(Au(self.gecko.mSize)).0;
         self.gecko.mScriptUnconstrainedSize = device.unzoom_text(Au(self.gecko.mScriptUnconstrainedSize)).0;
         self.gecko.mFont.size = device.unzoom_text(Au(self.gecko.mFont.size)).0;
     }
 
     pub fn set_font_size(&mut self, v: longhands::font_size::computed_value::T) {
-        self.gecko.mSize = v.0.to_i32_au();
-        self.gecko.mScriptUnconstrainedSize = v.0.to_i32_au();
+        use values::specified::font::KeywordSize;
+        self.gecko.mSize = v.size().0;
+        self.gecko.mScriptUnconstrainedSize = v.size().0;
+        if let Some(info) = v.keyword_info {
+            self.gecko.mFontSizeKeyword = match info.kw {
+                KeywordSize::XXSmall => structs::NS_STYLE_FONT_SIZE_XXSMALL,
+                KeywordSize::XSmall => structs::NS_STYLE_FONT_SIZE_XSMALL,
+                KeywordSize::Small => structs::NS_STYLE_FONT_SIZE_SMALL,
+                KeywordSize::Medium => structs::NS_STYLE_FONT_SIZE_MEDIUM,
+                KeywordSize::Large => structs::NS_STYLE_FONT_SIZE_LARGE,
+                KeywordSize::XLarge => structs::NS_STYLE_FONT_SIZE_XLARGE,
+                KeywordSize::XXLarge => structs::NS_STYLE_FONT_SIZE_XXLARGE,
+                KeywordSize::XXXLarge => structs::NS_STYLE_FONT_SIZE_XXXLARGE,
+            } as u8;
+            self.gecko.mFontSizeFactor = info.factor;
+            self.gecko.mFontSizeOffset = info.offset.0.to_i32_au();
+        } else {
+            self.gecko.mFontSizeKeyword = structs::NS_STYLE_FONT_SIZE_NO_KEYWORD as u8;
+            self.gecko.mFontSizeFactor = 1.;
+            self.gecko.mFontSizeOffset = 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,
                            device: &Device) -> Option<NonNegativeLength> {
         let (adjusted_size, adjusted_unconstrained_size) =
             self.calculate_script_level_size(parent, device);
         // 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.mSize = v.0.to_i32_au();
+            self.gecko.mSize = v.size().0;
             self.fixup_font_min_size(device);
             Some(Au(parent.gecko.mScriptUnconstrainedSize).into())
         }
     }
 
     pub fn fixup_font_min_size(&mut self, device: &Device) {
         unsafe { bindings::Gecko_nsStyleFont_FixupMinFontSize(&mut self.gecko, device.pres_context()) }
     }
@@ -2298,21 +2311,19 @@ fn static_assert() {
             (cmp::min(new_size, cmp::max(new_unconstrained_size, min)),
              new_unconstrained_size)
         }
     }
 
     /// 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<NonNegativeLength>,
-                                  device: &Device) -> bool {
+                                  device: &Device) {
         let (adjusted_size, adjusted_unconstrained_size)
             = self.calculate_script_level_size(parent, device);
         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.
@@ -2322,38 +2333,76 @@ fn static_assert() {
             // 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.mSize = adjusted_size.0;
+
+            // Technically the MathML constrained size may also be keyword-derived
+            // but we ignore this since it would be too complicated
+            // to correctly track and it's mostly unnecessary.
+            self.gecko.mFontSizeKeyword = structs::NS_STYLE_FONT_SIZE_NO_KEYWORD as u8;
+            self.gecko.mFontSizeFactor = 1.;
+            self.gecko.mFontSizeOffset = 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.mSize = size.0.to_i32_au();
+            // Copy keyword info over.
+            self.gecko.mFontSizeFactor = parent.gecko.mFontSizeFactor;
+            self.gecko.mFontSizeOffset = parent.gecko.mFontSizeOffset;
+            self.gecko.mFontSizeKeyword = parent.gecko.mFontSizeKeyword;
             // MathML constraints didn't apply here, so we can ignore this.
             self.gecko.mScriptUnconstrainedSize = size.0.to_i32_au();
-            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.mSize = parent.gecko.mSize;
+            // copy keyword info over
+            self.gecko.mFontSizeKeyword = structs::NS_STYLE_FONT_SIZE_NO_KEYWORD as u8;
+            self.gecko.mFontSizeFactor = 1.;
+            self.gecko.mFontSizeOffset = 0;
             self.gecko.mScriptUnconstrainedSize = parent.gecko.mScriptUnconstrainedSize;
-            self.fixup_font_min_size(device);
-            false
-        }
+        }
+        self.fixup_font_min_size(device);
     }
 
     pub fn clone_font_size(&self) -> longhands::font_size::computed_value::T {
-        Au(self.gecko.mSize).into()
+        use values::computed::font::KeywordInfo;
+        use values::specified::font::KeywordSize;
+        let size = Au(self.gecko.mSize).into();
+        let kw = match self.gecko.mFontSizeKeyword as u32 {
+            structs::NS_STYLE_FONT_SIZE_XXSMALL => KeywordSize::XXSmall,
+            structs::NS_STYLE_FONT_SIZE_XSMALL => KeywordSize::XSmall,
+            structs::NS_STYLE_FONT_SIZE_SMALL => KeywordSize::Small,
+            structs::NS_STYLE_FONT_SIZE_MEDIUM => KeywordSize::Medium,
+            structs::NS_STYLE_FONT_SIZE_LARGE => KeywordSize::Large,
+            structs::NS_STYLE_FONT_SIZE_XLARGE => KeywordSize::XLarge,
+            structs::NS_STYLE_FONT_SIZE_XXLARGE => KeywordSize::XXLarge,
+            structs::NS_STYLE_FONT_SIZE_XXXLARGE => KeywordSize::XXXLarge,
+            structs::NS_STYLE_FONT_SIZE_NO_KEYWORD => {
+                return longhands::font_size::computed_value::T {
+                    size: size,
+                    keyword_info: None,
+                }
+            }
+            _ => unreachable!("mFontSizeKeyword should be an absolute keyword or NO_KEYWORD")
+        };
+        longhands::font_size::computed_value::T {
+            size: size,
+            keyword_info: Some(KeywordInfo {
+                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) {
         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 {
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -591,358 +591,47 @@ macro_rules! impl_gecko_keyword_conversi
 
         #[inline]
         fn from_computed_value(computed: &computed_value::T) -> Self {
             SpecifiedValue::Weight(*computed)
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="font-size" animation_value_type="NonNegativeLength"
+<%helpers:longhand name="font-size" animation_value_type="ComputedValue"
                    flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER"
                    allow_quirks="True" spec="https://drafts.csswg.org/css-fonts/#propdef-font-size">
     use app_units::Au;
-    use properties::longhands::system_font::SystemFont;
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::FONT_MEDIUM_PX;
-    use values::computed::NonNegativeLength;
-    use values::specified::{AllowQuirks, FontRelativeLength, LengthOrPercentage, NoCalcLength};
+    use values::specified::AllowQuirks;
     use values::specified::length::FontBaseSize;
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                SpecifiedValue::Length(ref lop) => lop.to_css(dest),
-                SpecifiedValue::Keyword(kw, _, _) => kw.to_css(dest),
-                SpecifiedValue::Smaller => dest.write_str("smaller"),
-                SpecifiedValue::Larger => dest.write_str("larger"),
-                SpecifiedValue::System(sys) => sys.to_css(dest),
-            }
-        }
-    }
-
-    #[derive(Clone, Debug, PartialEq)]
-    #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub enum SpecifiedValue {
-        Length(specified::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
-        /// after font-relative (percent and em) values
-        /// have been applied, which is where the ratio
-        /// comes in. The offset comes in if we cascaded a calc value,
-        /// where the font-relative portion (em and percentage) will
-        /// go into the ratio, and the remaining units all computed together
-        /// will go into the offset.
-        /// See bug 1355707.
-        Keyword(KeywordSize, f32, NonNegativeLength),
-        Smaller,
-        Larger,
-        System(SystemFont)
-    }
-
-    impl From<specified::LengthOrPercentage> for SpecifiedValue {
-        fn from(other: specified::LengthOrPercentage) -> Self {
-            SpecifiedValue::Length(other)
-        }
-    }
+    use values::specified::font::{FONT_MEDIUM_PX, KeywordSize};
+    use values::computed::font::{KeywordInfo};
 
     pub mod computed_value {
-        use values::computed::NonNegativeLength;
-        pub type T = NonNegativeLength;
-    }
-
-    /// CSS font keywords
-    #[derive(Clone, Copy, Debug, PartialEq)]
-    #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub enum KeywordSize {
-        XXSmall = 1, // This is to enable the NonZero optimization
-                     // which simplifies the representation of Option<KeywordSize>
-                     // in bindgen
-        XSmall,
-        Small,
-        Medium,
-        Large,
-        XLarge,
-        XXLarge,
-        // This is not a real font keyword and will not parse
-        // HTML font-size 7 corresponds to this value
-        XXXLarge,
-    }
-
-    pub use self::KeywordSize::*;
-
-    impl KeywordSize {
-        pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
-            try_match_ident_ignore_ascii_case! { input.expect_ident()?,
-                "xx-small" => Ok(XXSmall),
-                "x-small" => Ok(XSmall),
-                "small" => Ok(Small),
-                "medium" => Ok(Medium),
-                "large" => Ok(Large),
-                "x-large" => Ok(XLarge),
-                "xx-large" => Ok(XXLarge),
-            }
-        }
-
-        pub fn html_size(&self) -> u8 {
-            match *self {
-                KeywordSize::XXSmall => 0,
-                KeywordSize::XSmall => 1,
-                KeywordSize::Small => 2,
-                KeywordSize::Medium => 3,
-                KeywordSize::Large => 4,
-                KeywordSize::XLarge => 5,
-                KeywordSize::XXLarge => 6,
-                KeywordSize::XXXLarge => 7,
-            }
-        }
-    }
-
-    impl Default for KeywordSize {
-        fn default() -> Self {
-            Medium
-        }
-    }
-
-    impl ToCss for KeywordSize {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            dest.write_str(match *self {
-                XXSmall => "xx-small",
-                XSmall => "x-small",
-                Small => "small",
-                Medium => "medium",
-                Large => "large",
-                XLarge => "x-large",
-                XXLarge => "xx-large",
-                XXXLarge => unreachable!("We should never serialize \
-                                          specified values set via
-                                          HTML presentation attributes"),
-            })
-        }
+        use values::computed::font;
+        pub type T = font::FontSize;
     }
 
-    % if product == "servo":
-        impl ToComputedValue for KeywordSize {
-            type ComputedValue = NonNegativeLength;
-            #[inline]
-            fn to_computed_value(&self, _: &Context) -> computed_value::T {
-                // https://drafts.csswg.org/css-fonts-3/#font-size-prop
-                use values::FONT_MEDIUM_PX;
-                match *self {
-                    XXSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 5,
-                    XSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 4,
-                    Small => Au::from_px(FONT_MEDIUM_PX) * 8 / 9,
-                    Medium => Au::from_px(FONT_MEDIUM_PX),
-                    Large => Au::from_px(FONT_MEDIUM_PX) * 6 / 5,
-                    XLarge => Au::from_px(FONT_MEDIUM_PX) * 3 / 2,
-                    XXLarge => Au::from_px(FONT_MEDIUM_PX) * 2,
-                    XXXLarge => Au::from_px(FONT_MEDIUM_PX) * 3,
-                }.into()
-            }
-
-            #[inline]
-            fn from_computed_value(_: &computed_value::T) -> Self {
-                unreachable!()
-            }
-        }
-    % else:
-        impl ToComputedValue for KeywordSize {
-            type ComputedValue = NonNegativeLength;
-            #[inline]
-            fn to_computed_value(&self, cx: &Context) -> computed_value::T {
-                use gecko_bindings::structs::nsIAtom;
-                use values::specified::length::au_to_int_px;
-                // Data from nsRuleNode.cpp in Gecko
-                // Mapping from base size and HTML size to pixels
-                // The first index is (base_size - 9), the second is the
-                // HTML size. "0" is CSS keyword xx-small, not HTML size 0,
-                // since HTML size 0 is the same as 1.
-                //
-                //  xxs   xs      s      m     l      xl     xxl   -
-                //  -     0/1     2      3     4      5      6     7
-                static FONT_SIZE_MAPPING: [[i32; 8]; 8] = [
-                    [9,    9,     9,     9,    11,    14,    18,    27],
-                    [9,    9,     9,    10,    12,    15,    20,    30],
-                    [9,    9,    10,    11,    13,    17,    22,    33],
-                    [9,    9,    10,    12,    14,    18,    24,    36],
-                    [9,   10,    12,    13,    16,    20,    26,    39],
-                    [9,   10,    12,    14,    17,    21,    28,    42],
-                    [9,   10,    13,    15,    18,    23,    30,    45],
-                    [9,   10,    13,    16,    18,    24,    32,    48]
-                ];
-
-                static FONT_SIZE_FACTORS: [i32; 8] = [60, 75, 89, 100, 120, 150, 200, 300];
-
-                // XXXManishearth handle quirks mode
-
-                let ref gecko_font = cx.style().get_font().gecko();
-                let base_size = unsafe { Atom::with(gecko_font.mLanguage.raw::<nsIAtom>(), |atom| {
-                    cx.font_metrics_provider.get_size(atom, gecko_font.mGenericID).0
-                }) };
-
-                let base_size_px = au_to_int_px(base_size as f32);
-                let html_size = self.html_size() as usize;
-                if base_size_px >= 9 && base_size_px <= 16 {
-                    NonNegativeLength::new(FONT_SIZE_MAPPING[(base_size_px - 9) as usize][html_size] as f32)
-                } else {
-                    Au(FONT_SIZE_FACTORS[html_size] * base_size / 100).into()
-                }
-            }
-
-            #[inline]
-            fn from_computed_value(_: &computed_value::T) -> Self {
-                unreachable!()
-            }
-        }
-    % endif
-
-    /// This is the ratio applied for font-size: larger
-    /// and smaller by both Firefox and Chrome
-    const LARGER_FONT_SIZE_RATIO: f32 = 1.2;
+    pub use values::specified::font::FontSize as SpecifiedValue;
 
-    impl SpecifiedValue {
-        /// https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size
-        pub fn from_html_size(size: u8) -> Self {
-            SpecifiedValue::Keyword(match size {
-                // If value is less than 1, let it be 1.
-                0 | 1 => XSmall,
-                2 => Small,
-                3 => Medium,
-                4 => Large,
-                5 => XLarge,
-                6 => XXLarge,
-                // If value is greater than 7, let it be 7.
-                _ => XXXLarge,
-            }, 1., Au(0).into())
-        }
-
-        /// If this value is specified as a ratio of the parent font (em units
-        /// or percent) return the ratio
-        pub fn as_font_ratio(&self, context: &Context) -> Option<(f32, NonNegativeLength)> {
-            match *self {
-                SpecifiedValue::Length(ref lop) => {
-                    match *lop {
-                        LengthOrPercentage::Percentage(pc) => {
-                            Some((pc.0, NonNegativeLength::zero()))
-                        }
-                        LengthOrPercentage::Length(ref nocalc) => {
-                            match *nocalc {
-                                NoCalcLength::FontRelative(FontRelativeLength::Em(em)) => {
-                                    Some((em, NonNegativeLength::zero()))
-                                }
-                                _ => None,
-                            }
-                        }
-                        LengthOrPercentage::Calc(ref calc) => {
-                            if calc.em.is_none() && calc.percentage.is_none() {
-                                return None;
-                            }
-                            let ratio = calc.em.unwrap_or(0.) + calc.percentage.map_or(0., |pc| pc.0);
-                            // Compute it, but shave off the font-relative part (em, %)
-                            // This will mean that other font-relative units like ex and ch will be computed against
-                            // the old font even when the font changes. There's no particular "right answer" for what
-                            // to do here -- Gecko recascades as if the font had changed, we instead track the changes
-                            // and reapply, which means that we carry over old computed ex/ch values whilst Gecko
-                            // recomputes new ones. This is enough of an edge case to not really matter.
-                            let abs = calc.to_computed_value_zoomed(context, FontBaseSize::Custom(Au(0)))
-                                          .length_component().into();
-                            Some((ratio, abs))
-                        }
-                    }
-                }
-                SpecifiedValue::Larger => Some((LARGER_FONT_SIZE_RATIO, Au(0).into())),
-                SpecifiedValue::Smaller => Some((1. / LARGER_FONT_SIZE_RATIO, Au(0).into())),
-                _ => None,
-            }
-        }
-
-        /// Compute it against a given base font size
-        pub fn to_computed_value_against(
-            &self,
-            context: &Context,
-            base_size: FontBaseSize,
-        ) -> NonNegativeLength {
-            use values::specified::length::FontRelativeLength;
-            match *self {
-                SpecifiedValue::Length(LengthOrPercentage::Length(
-                        NoCalcLength::FontRelative(value))) => {
-                    value.to_computed_value(context, base_size).into()
-                }
-                SpecifiedValue::Length(LengthOrPercentage::Length(
-                        NoCalcLength::ServoCharacterWidth(value))) => {
-                    value.to_computed_value(base_size.resolve(context)).into()
-                }
-                SpecifiedValue::Length(LengthOrPercentage::Length(
-                        NoCalcLength::Absolute(ref l))) => {
-                    context.maybe_zoom_text(l.to_computed_value(context)).into()
-                }
-                SpecifiedValue::Length(LengthOrPercentage::Length(ref l)) => {
-                    l.to_computed_value(context).into()
-                }
-                SpecifiedValue::Length(LengthOrPercentage::Percentage(pc)) => {
-                    base_size.resolve(context).scale_by(pc.0).into()
-                }
-                SpecifiedValue::Length(LengthOrPercentage::Calc(ref calc)) => {
-                    let calc = calc.to_computed_value_zoomed(context, base_size);
-                    calc.to_used_value(Some(base_size.resolve(context))).unwrap().into()
-                }
-                SpecifiedValue::Keyword(ref key, fraction, offset) => {
-                    let key_len = key.to_computed_value(context).scale_by(fraction) + offset;
-                    context.maybe_zoom_text(key_len.0).into()
-                }
-                SpecifiedValue::Smaller => {
-                    FontRelativeLength::Em(1. / LARGER_FONT_SIZE_RATIO)
-                        .to_computed_value(context, base_size).into()
-                }
-                SpecifiedValue::Larger => {
-                    FontRelativeLength::Em(LARGER_FONT_SIZE_RATIO)
-                        .to_computed_value(context, base_size).into()
-                }
-
-                SpecifiedValue::System(_) => {
-                    <%self:nongecko_unreachable>
-                        context.cached_system_font.as_ref().unwrap().font_size
-                    </%self:nongecko_unreachable>
-                }
-            }
+    #[inline]
+    #[allow(missing_docs)]
+    pub fn get_initial_value() -> computed_value::T {
+        computed_value::T {
+            size: Au::from_px(FONT_MEDIUM_PX).into(),
+            keyword_info: Some(KeywordInfo::medium())
         }
     }
 
     #[inline]
-    #[allow(missing_docs)]
-    pub fn get_initial_value() -> computed_value::T {
-        NonNegativeLength::new(FONT_MEDIUM_PX as f32)
-    }
-
-    #[inline]
     pub fn get_initial_specified_value() -> SpecifiedValue {
-        SpecifiedValue::Keyword(Medium, 1., Au(0).into())
+        SpecifiedValue::Keyword(KeywordInfo::medium())
     }
 
 
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            self.to_computed_value_against(context, FontBaseSize::InheritedStyle)
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            SpecifiedValue::Length(LengthOrPercentage::Length(
-                ToComputedValue::from_computed_value(&computed.0)
-            ))
-        }
-    }
-
     /// <length> | <percentage> | <absolute-size> | <relative-size>
     pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                          -> Result<SpecifiedValue, ParseError<'i>> {
         parse_quirky(context, input, AllowQuirks::No)
     }
 
     /// Parses a font-size, with quirks.
     pub fn parse_quirky<'i, 't>(context: &ParserContext,
@@ -950,74 +639,42 @@ macro_rules! impl_gecko_keyword_conversi
                                 allow_quirks: AllowQuirks)
                                 -> Result<SpecifiedValue, ParseError<'i>> {
         use self::specified::LengthOrPercentage;
         if let Ok(lop) = input.try(|i| LengthOrPercentage::parse_non_negative_quirky(context, i, allow_quirks)) {
             return Ok(SpecifiedValue::Length(lop))
         }
 
         if let Ok(kw) = input.try(KeywordSize::parse) {
-            return Ok(SpecifiedValue::Keyword(kw, 1., Au(0).into()))
+            return Ok(SpecifiedValue::Keyword(kw.into()))
         }
 
         try_match_ident_ignore_ascii_case! { input.expect_ident()?,
             "smaller" => Ok(SpecifiedValue::Smaller),
             "larger" => Ok(SpecifiedValue::Larger),
         }
     }
 
-    impl SpecifiedValue {
-        pub fn system_font(f: SystemFont) -> Self {
-            SpecifiedValue::System(f)
-        }
-        pub fn get_system(&self) -> Option<SystemFont> {
-            if let SpecifiedValue::System(s) = *self {
-                Some(s)
-            } else {
-                None
-            }
-        }
-    }
-
     #[allow(unused_mut)]
     pub fn cascade_specified_font_size(context: &mut Context,
                                        specified_value: &SpecifiedValue,
-                                       mut computed: NonNegativeLength) {
-        if let SpecifiedValue::Keyword(kw, fraction, offset) = *specified_value {
-            context.builder.font_size_keyword = Some((kw, fraction, offset));
-        } else if let Some((ratio, abs)) = specified_value.as_font_ratio(context) {
-            // In case a font-size-relative value was applied to a keyword
-            // value, we must preserve this fact in case the generic font family
-            // changes. relative values (em and %) applied to keywords must be
-            // recomputed from the base size for the keyword and the relative size.
-            //
-            // See bug 1355707
-            if let Some((kw, fraction, old_abs)) = *context.builder.inherited_font_computation_data() {
-                context.builder.font_size_keyword =
-                    Some((kw, fraction * ratio, abs + old_abs.scale_by(ratio)));
-            } else {
-                context.builder.font_size_keyword = None;
-            }
-        } else {
-            context.builder.font_size_keyword = None;
-        }
-
+                                       mut computed: computed_value::T) {
         // we could use clone_language and clone_font_family() here but that's
         // expensive. Do it only in gecko mode for now.
         % if product == "gecko":
             use gecko_bindings::structs::nsIAtom;
             // if the language or generic changed, we need to recalculate
             // the font size from the stored font-size origin information.
             if context.builder.get_font().gecko().mLanguage.raw::<nsIAtom>() !=
                context.builder.get_parent_font().gecko().mLanguage.raw::<nsIAtom>() ||
                context.builder.get_font().gecko().mGenericID !=
                context.builder.get_parent_font().gecko().mGenericID {
-                if let Some((kw, ratio, offset)) = context.builder.font_size_keyword {
-                    let len = kw.to_computed_value(context).scale_by(ratio) + offset;
-                    computed = context.maybe_zoom_text(len.0).into();
+                if let Some(info) = computed.keyword_info {
+                    computed.size = context.maybe_zoom_text(info.kw.to_computed_value(context)
+                                                                .scale_by(info.factor) + info.offset)
                 }
             }
         % endif
 
         let device = context.builder.device;
         let mut font = context.builder.take_font();
         let parent_unconstrained = {
             let parent_font = context.builder.get_parent_font();
@@ -1026,63 +683,56 @@ macro_rules! impl_gecko_keyword_conversi
         context.builder.put_font(font);
 
         if let Some(parent) = parent_unconstrained {
             let new_unconstrained =
                 specified_value
                     .to_computed_value_against(context, FontBaseSize::Custom(Au::from(parent)));
             context.builder
                    .mutate_font()
-                   .apply_unconstrained_font_size(new_unconstrained);
+                   .apply_unconstrained_font_size(new_unconstrained.size);
         }
     }
 
     /// FIXME(emilio): This is very complex. Also, it should move to
     /// StyleBuilder.
     pub fn cascade_inherit_font_size(context: &mut Context) {
         // 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.builder.font_size_keyword.map(|(kw, ratio, offset)| {
-            let len = SpecifiedValue::Keyword(kw, ratio, offset).to_computed_value(context);
-            context.maybe_zoom_text(len.0).into()
+        let kw_inherited_size = context.builder.get_parent_font()
+                                       .clone_font_size()
+                                       .keyword_info.map(|info| {
+            context.maybe_zoom_text(SpecifiedValue::Keyword(info)
+                  .to_computed_value(context).size)
         });
-        let parent_kw;
-        let device = context.builder.device;
         let mut font = context.builder.take_font();
-        let used_kw = {
-            let parent_font = context.builder.get_parent_font();
-            parent_kw = *context.builder.inherited_font_computation_data();
-
-            font.inherit_font_size_from(parent_font, kw_inherited_size, device)
-        };
+        font.inherit_font_size_from(context.builder.get_parent_font(),
+                                    kw_inherited_size,
+                                    context.builder.device);
         context.builder.put_font(font);
-        context.builder.font_size_keyword =
-            if used_kw { parent_kw } else { None };
     }
 
     /// Cascade the initial value for the `font-size` property.
     ///
     /// FIXME(emilio): This is the only function that is outside of the
     /// `StyleBuilder`, and should really move inside!
     ///
     /// Can we move the font stuff there?
     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 = context.maybe_zoom_text(
-                            longhands::font_size::get_initial_specified_value()
-                                .to_computed_value(context).0
-                        ).into();
+        let mut computed = longhands::font_size::get_initial_specified_value()
+                                            .to_computed_value(context);
+        computed.size = context.maybe_zoom_text(computed.size);
         context.builder.mutate_font().set_font_size(computed);
         % if product == "gecko":
             let device = context.builder.device;
             context.builder.mutate_font().fixup_font_min_size(device);
         % endif
-        context.builder.font_size_keyword = Some((Default::default(), 1., Au(0).into()));
     }
 </%helpers:longhand>
 
 <%helpers:longhand products="gecko" name="font-size-adjust"
                    animation_value_type="longhands::font_size_adjust::computed_value::T"
                    flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER"
                    spec="https://drafts.csswg.org/css-fonts/#propdef-font-size-adjust">
     use properties::longhands::system_font::SystemFont;
@@ -2547,17 +2197,20 @@ https://drafts.csswg.org/css-fonts-4/#lo
                     FontFamily::FamilyName(FamilyName {
                         name: (&*font.mName).into(),
                         syntax: FamilyNameSyntax::Quoted,
                     })
                 }).collect::<Vec<_>>();
                 let weight = longhands::font_weight::computed_value::T::from_gecko_weight(system.weight);
                 let ret = ComputedSystemFont {
                     font_family: longhands::font_family::computed_value::T(family),
-                    font_size: Au(system.size).into(),
+                    font_size: longhands::font_size::computed_value::T {
+                            size: Au(system.size).into(),
+                            keyword_info: None
+                    },
                     font_weight: weight,
                     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
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -5,16 +5,17 @@
 // This file is a Mako template: http://www.makotemplates.org/
 
 // Please note that valid Rust syntax may be mangled by the Mako parser.
 // For example, Vec<&Foo> will be mangled as Vec&Foo>. To work around these issues, the code
 // can be escaped. In the above example, Vec<<&Foo> or Vec< &Foo> achieves the desired result of Vec<&Foo>.
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
+#[cfg(feature = "servo")]
 use app_units::Au;
 use servo_arc::{Arc, UniqueArc};
 use smallbitvec::SmallBitVec;
 use std::borrow::Cow;
 use hash::HashSet;
 use std::{fmt, mem, ops};
 use std::cell::RefCell;
 #[cfg(feature = "gecko")] use std::ptr;
@@ -87,42 +88,16 @@ pub mod computed_value_flags;
 pub mod declaration_block;
 
 /// Conversion with fewer impls than From/Into
 pub trait MaybeBoxed<Out> {
     /// Convert
     fn maybe_boxed(self) -> Out;
 }
 
-
-/// This is where we store extra font data while
-/// while computing font sizes.
-///
-/// font-size keyword values (and font-size-relative values applied
-/// to keyword values) need to preserve their identity as originating
-/// from keywords and relative font sizes. We store this information
-/// out of band in the ComputedValues. When None, the font size on the
-/// current struct was computed from a value that was not a keyword
-/// or a chain of font-size-relative values applying to successive parents
-/// terminated by a keyword. When Some, this means the font-size was derived
-/// from a keyword value or a keyword value on some ancestor with only
-/// font-size-relative keywords and regular inheritance in between. The
-/// integer stores the final ratio of the chain of font size relative values.
-/// and is 1 when there was just a keyword and no relative values.
-///
-/// When this is Some, we compute font sizes by computing the keyword against
-/// the generic font, and then multiplying it by the ratio (as well as adding any
-/// absolute offset from calcs)
-pub type FontComputationData = Option<(longhands::font_size::KeywordSize, f32, NonNegativeLength)>;
-
-/// Default value for FontComputationData
-pub fn default_font_size_keyword() -> FontComputationData {
-    Some((Default::default(), 1., Au(0).into()))
-}
-
 impl<T> MaybeBoxed<T> for T {
     #[inline]
     fn maybe_boxed(self) -> T { self }
 }
 
 impl<T> MaybeBoxed<Box<T>> for T {
     #[inline]
     fn maybe_boxed(self) -> Box<T> { Box::new(self) }
@@ -1840,19 +1815,18 @@ pub mod style_structs {
                     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<NonNegativeLength>,
-                                              _: &Device) -> bool {
+                                              _: &Device) {
                     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,
                                        _: &Device) -> Option<NonNegativeLength> {
                     self.set_font_size(v);
                     None
@@ -1980,19 +1954,16 @@ pub use gecko_properties::{ComputedValue
 pub struct ComputedValuesInner {
     % for style_struct in data.active_style_structs():
         ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
     % endfor
     custom_properties: Option<Arc<::custom_properties::CustomPropertiesMap>>,
     /// The writing mode of this computed values struct.
     pub writing_mode: WritingMode,
 
-    /// The keyword behind the current font-size property, if any
-    pub font_computation_data: FontComputationData,
-
     /// A set of flags we use to store misc information regarding this style.
     pub flags: ComputedValueFlags,
 
     /// The rule node representing the ordered list of rules matched for this
     /// node.  Can be None for default values and text nodes.  This is
     /// essentially an optimization to avoid referencing the root rule node.
     pub rules: Option<StrongRuleNode>,
 
@@ -2038,29 +2009,27 @@ impl ComputedValues {
 impl ComputedValues {
     /// Create a new refcounted `ComputedValues`
     pub fn new(
         _: &Device,
         _: Option<<&ComputedValues>,
         _: Option<<&PseudoElement>,
         custom_properties: Option<Arc<::custom_properties::CustomPropertiesMap>>,
         writing_mode: WritingMode,
-        font_size_keyword: FontComputationData,
         flags: ComputedValueFlags,
         rules: Option<StrongRuleNode>,
         visited_style: Option<Arc<ComputedValues>>,
         % for style_struct in data.active_style_structs():
         ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
         % endfor
     ) -> Arc<Self> {
         Arc::new(Self {
             inner: ComputedValuesInner::new(
                 custom_properties,
                 writing_mode,
-                font_size_keyword,
                 flags,
                 rules,
                 visited_style,
                 % for style_struct in data.active_style_structs():
                 ${style_struct.ident},
                 % endfor
             )
         })
@@ -2071,28 +2040,26 @@ impl ComputedValues {
 }
 
 #[cfg(feature = "servo")]
 impl ComputedValuesInner {
     /// Construct a `ComputedValuesInner` instance.
     pub fn new(
         custom_properties: Option<Arc<::custom_properties::CustomPropertiesMap>>,
         writing_mode: WritingMode,
-        font_size_keyword: FontComputationData,
         flags: ComputedValueFlags,
         rules: Option<StrongRuleNode>,
         visited_style: Option<Arc<ComputedValues>>,
         % for style_struct in data.active_style_structs():
         ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
         % endfor
     ) -> Self {
         ComputedValuesInner {
             custom_properties: custom_properties,
             writing_mode: writing_mode,
-            font_computation_data: font_size_keyword,
             rules: rules,
             visited_style: visited_style,
             flags: flags,
         % for style_struct in data.active_style_structs():
             ${style_struct.ident}: ${style_struct.ident},
         % endfor
         }
     }
@@ -2604,18 +2571,16 @@ pub struct StyleBuilder<'a> {
     /// `clear_modified_reset` was called.  This is used to tell whether the
     /// `StyleAdjuster` did any work.
     modified_reset: bool,
 
     /// The writing mode flags.
     ///
     /// TODO(emilio): Make private.
     pub writing_mode: WritingMode,
-    /// The keyword behind the current font-size property, if any.
-    pub font_size_keyword: FontComputationData,
     /// Flags for the computed value.
     pub flags: ComputedValueFlags,
     /// The element's style if visited, only computed if there's a relevant link
     /// for this element.  A element's "relevant link" is the element being
     /// matched if it is a link or the nearest ancestor link.
     visited_style: Option<Arc<ComputedValues>>,
     % for style_struct in data.active_style_structs():
         ${style_struct.ident}: StyleStructRef<'a, style_structs::${style_struct.name}>,
@@ -2628,17 +2593,16 @@ impl<'a> StyleBuilder<'a> {
         device: &'a Device,
         parent_style: Option<<&'a ComputedValues>,
         parent_style_ignoring_first_line: Option<<&'a ComputedValues>,
         pseudo: Option<<&'a PseudoElement>,
         cascade_flags: CascadeFlags,
         rules: Option<StrongRuleNode>,
         custom_properties: Option<Arc<::custom_properties::CustomPropertiesMap>>,
         writing_mode: WritingMode,
-        font_size_keyword: FontComputationData,
         mut flags: ComputedValueFlags,
         visited_style: Option<Arc<ComputedValues>>,
     ) -> Self {
         debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some());
         #[cfg(feature = "gecko")]
         debug_assert!(parent_style.is_none() ||
                       ptr::eq(parent_style.unwrap(),
                               parent_style_ignoring_first_line.unwrap()) ||
@@ -2666,17 +2630,16 @@ impl<'a> StyleBuilder<'a> {
             inherited_style,
             inherited_style_ignoring_first_line,
             reset_style,
             pseudo,
             rules,
             modified_reset: false,
             custom_properties,
             writing_mode,
-            font_size_keyword,
             flags,
             visited_style,
             % for style_struct in data.active_style_structs():
             % if style_struct.inherited:
             ${style_struct.ident}: StyleStructRef::Borrowed(inherited_style.${style_struct.name_lower}_arc()),
             % else:
             ${style_struct.ident}: StyleStructRef::Borrowed(reset_style.${style_struct.name_lower}_arc()),
             % endif
@@ -2709,17 +2672,16 @@ impl<'a> StyleBuilder<'a> {
             // None of our callers pass in ::first-line parent styles.
             inherited_style_ignoring_first_line: inherited_style,
             reset_style,
             pseudo,
             modified_reset: false,
             rules: None, // FIXME(emilio): Dubious...
             custom_properties: style_to_derive_from.custom_properties(),
             writing_mode: style_to_derive_from.writing_mode,
-            font_size_keyword: style_to_derive_from.font_computation_data,
             flags: style_to_derive_from.flags,
             visited_style: style_to_derive_from.clone_visited_style(),
             % for style_struct in data.active_style_structs():
             ${style_struct.ident}: StyleStructRef::Borrowed(
                 style_to_derive_from.${style_struct.name_lower}_arc()
             ),
             % endfor
         }
@@ -2829,17 +2791,16 @@ impl<'a> StyleBuilder<'a> {
             device,
             Some(parent),
             Some(parent),
             pseudo,
             CascadeFlags::empty(),
             /* rules = */ None,
             parent.custom_properties(),
             parent.writing_mode,
-            parent.font_computation_data,
             parent.flags,
             parent.clone_visited_style()
         )
     }
 
     /// Returns whether we have a visited style.
     pub fn has_visited_style(&self) -> bool {
         self.visited_style.is_some()
@@ -2936,17 +2897,16 @@ impl<'a> StyleBuilder<'a> {
     /// Turns this `StyleBuilder` into a proper `ComputedValues` instance.
     pub fn build(self) -> Arc<ComputedValues> {
         ComputedValues::new(
             self.device,
             self.parent_style,
             self.pseudo,
             self.custom_properties,
             self.writing_mode,
-            self.font_size_keyword,
             self.flags,
             self.rules,
             self.visited_style,
             % for style_struct in data.active_style_structs():
             self.${style_struct.ident}.build(),
             % endfor
         )
     }
@@ -2959,21 +2919,16 @@ impl<'a> StyleBuilder<'a> {
         self.custom_properties.clone()
     }
 
     /// Access to various information about our inherited styles.  We don't
     /// expose an inherited ComputedValues directly, because in the
     /// ::first-line case some of the inherited information needs to come from
     /// one ComputedValues instance and some from a different one.
 
-    /// Inherited font bits.
-    pub fn inherited_font_computation_data(&self) -> &FontComputationData {
-        &self.inherited_style.font_computation_data
-    }
-
     /// Inherited writing-mode.
     pub fn inherited_writing_mode(&self) -> &WritingMode {
         &self.inherited_style.writing_mode
     }
 
     /// Inherited style flags.
     pub fn inherited_flags(&self) -> &ComputedValueFlags {
         &self.inherited_style.flags
@@ -3001,17 +2956,16 @@ pub use self::lazy_static_module::INITIA
 
 // Use a module to work around #[cfg] on lazy_static! not being applied to every generated item.
 #[cfg(feature = "servo")]
 #[allow(missing_docs)]
 mod lazy_static_module {
     use logical_geometry::WritingMode;
     use servo_arc::Arc;
     use super::{ComputedValues, ComputedValuesInner, longhands, style_structs};
-    use super::default_font_size_keyword;
     use super::computed_value_flags::ComputedValueFlags;
 
     /// The initial values for all style structs as defined by the specification.
     lazy_static! {
         pub static ref INITIAL_SERVO_VALUES: ComputedValues = ComputedValues {
             inner: ComputedValuesInner {
                 % for style_struct in data.active_style_structs():
                     ${style_struct.ident}: Arc::new(style_structs::${style_struct.name} {
@@ -3020,17 +2974,16 @@ mod lazy_static_module {
                         % endfor
                         % if style_struct.name == "Font":
                             hash: 0,
                         % endif
                     }),
                 % endfor
                 custom_properties: None,
                 writing_mode: WritingMode::empty(),
-                font_computation_data: default_font_size_keyword(),
                 rules: None,
                 visited_style: None,
                 flags: ComputedValueFlags::empty(),
             }
         };
     }
 }
 
@@ -3247,17 +3200,16 @@ where
             device,
             parent_style,
             parent_style_ignoring_first_line,
             pseudo,
             flags,
             Some(rules.clone()),
             custom_properties,
             WritingMode::empty(),
-            inherited_style.font_computation_data,
             ComputedValueFlags::empty(),
             visited_style,
         ),
         cached_system_font: None,
         in_media_query: false,
         for_smil_animation: false,
         for_non_inherited_property: None,
         font_metrics_provider,
--- a/servo/components/style/rule_cache.rs
+++ b/servo/components/style/rule_cache.rs
@@ -47,17 +47,17 @@ impl RuleCacheConditions {
 
     /// Returns whether `style` matches the conditions.
     fn matches(&self, style: &StyleBuilder) -> bool {
         if self.uncacheable {
             return false;
         }
 
         if let Some(fs) = self.font_size {
-            if style.get_font().clone_font_size() != fs {
+            if style.get_font().clone_font_size().size != fs {
                 return false;
             }
         }
 
         if let Some(wm) = self.writing_mode {
             if style.writing_mode != wm {
                 return false;
             }
--- a/servo/components/style/servo/media_queries.rs
+++ b/servo/components/style/servo/media_queries.rs
@@ -62,17 +62,17 @@ impl Device {
         viewport_size: TypedSize2D<f32, CSSPixel>,
         device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>
     ) -> Device {
         Device {
             media_type,
             viewport_size,
             device_pixel_ratio,
             // FIXME(bz): Seems dubious?
-            root_font_size: AtomicIsize::new(font_size::get_initial_value().0.to_i32_au() as isize),
+            root_font_size: AtomicIsize::new(font_size::get_initial_value().size().0 as isize),
             used_root_font_size: AtomicBool::new(false),
             used_viewport_units: AtomicBool::new(false),
         }
     }
 
     /// Return the default computed values for this device.
     pub fn default_computed_values(&self) -> &ComputedValues {
         // FIXME(bz): This isn't really right, but it's no more wrong
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/computed/font.rs
@@ -0,0 +1,76 @@
+/* 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 values for font properties
+
+use app_units::Au;
+use std::fmt;
+use style_traits::ToCss;
+use values::computed::NonNegativeLength;
+use values::specified::font as specified;
+
+#[derive(Animate, ComputeSquaredDistance, ToAnimatedValue, ToAnimatedZero)]
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
+/// The computed value of font-size
+pub struct FontSize {
+    /// The size.
+    pub size: NonNegativeLength,
+    /// If derived from a keyword, the keyword and additional transformations applied to it
+    pub keyword_info: Option<KeywordInfo>,
+}
+
+#[derive(Animate, ComputeSquaredDistance, ToAnimatedValue, ToAnimatedZero)]
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
+/// Additional information for keyword-derived font sizes.
+pub struct KeywordInfo {
+    /// The keyword used
+    pub kw: specified::KeywordSize,
+    /// A factor to be multiplied by the computed size of the keyword
+    pub factor: f32,
+    /// An additional Au offset to add to the kw*factor in the case of calcs
+    pub offset: NonNegativeLength,
+}
+
+impl KeywordInfo {
+    /// Given a parent keyword info (self), apply an additional factor/offset to it
+    pub fn compose(self, factor: f32, offset: NonNegativeLength) -> Self {
+        KeywordInfo {
+            kw: self.kw,
+            factor: self.factor * factor,
+            offset: self.offset.scale_by(factor) + offset,
+        }
+    }
+
+    /// KeywordInfo value for font-size: medium
+    pub fn medium() -> Self {
+        specified::KeywordSize::Medium.into()
+    }
+}
+
+impl From<specified::KeywordSize> for KeywordInfo {
+    fn from(x: specified::KeywordSize) -> Self {
+        KeywordInfo {
+            kw: x,
+            factor: 1.,
+            offset: Au(0).into(),
+        }
+    }
+}
+
+impl FontSize {
+    /// The actual computed font size.
+    pub fn size(self) -> Au {
+        self.size.into()
+    }
+}
+
+impl ToCss for FontSize {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        self.size.to_css(dest)
+    }
+}
--- a/servo/components/style/values/computed/length.rs
+++ b/servo/components/style/values/computed/length.rs
@@ -30,17 +30,17 @@ impl ToComputedValue for specified::NoCa
         match *self {
             specified::NoCalcLength::Absolute(length) =>
                 length.to_computed_value(context),
             specified::NoCalcLength::FontRelative(length) =>
                 length.to_computed_value(context, FontBaseSize::CurrentStyle),
             specified::NoCalcLength::ViewportPercentage(length) =>
                 length.to_computed_value(context.viewport_size_for_viewport_unit_resolution()),
             specified::NoCalcLength::ServoCharacterWidth(length) =>
-                length.to_computed_value(Au::from(context.style().get_font().clone_font_size())),
+                length.to_computed_value(context.style().get_font().clone_font_size().size()),
             #[cfg(feature = "gecko")]
             specified::NoCalcLength::Physical(length) =>
                 length.to_computed_value(context),
         }
     }
 
     #[inline]
     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
@@ -264,17 +264,17 @@ impl specified::CalcLengthOrPercentage {
             clamping_mode: self.clamping_mode,
             length: Length::new(length.min(f32::MAX).max(f32::MIN)),
             percentage: self.percentage,
         }
     }
 
     /// Compute font-size or line-height taking into account text-zoom if necessary.
     pub fn to_computed_value_zoomed(&self, context: &Context, base_size: FontBaseSize) -> CalcLengthOrPercentage {
-        self.to_computed_value_with_zoom(context, |abs| context.maybe_zoom_text(abs), base_size)
+        self.to_computed_value_with_zoom(context, |abs| context.maybe_zoom_text(abs.into()).0, base_size)
     }
 }
 
 impl ToComputedValue for specified::CalcLengthOrPercentage {
     type ComputedValue = CalcLengthOrPercentage;
 
     fn to_computed_value(&self, context: &Context) -> CalcLengthOrPercentage {
         // normal properties don't zoom, and compute em units against the current style's font-size
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -62,16 +62,17 @@ pub mod angle;
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 #[path = "box.rs"]
 pub mod box_;
 pub mod color;
 pub mod effects;
 pub mod flex;
+pub mod font;
 pub mod image;
 #[cfg(feature = "gecko")]
 pub mod gecko;
 pub mod length;
 pub mod percentage;
 pub mod position;
 pub mod rect;
 pub mod svg;
@@ -154,30 +155,30 @@ impl<'a> Context<'a> {
 
     /// The current style.
     pub fn style(&self) -> &StyleBuilder {
         &self.builder
     }
 
     /// Apply text-zoom if enabled.
     #[cfg(feature = "gecko")]
-    pub fn maybe_zoom_text(&self, size: CSSPixelLength) -> CSSPixelLength {
+    pub fn maybe_zoom_text(&self, size: NonNegativeLength) -> NonNegativeLength {
         // We disable zoom for <svg:text> by unsetting the
         // -x-text-zoom property, which leads to a false value
         // in mAllowZoom
         if self.style().get_font().gecko.mAllowZoom {
             self.device().zoom_text(Au::from(size)).into()
         } else {
             size
         }
     }
 
     /// (Servo doesn't do text-zoom)
     #[cfg(feature = "servo")]
-    pub fn maybe_zoom_text(&self, size: CSSPixelLength) -> CSSPixelLength {
+    pub fn maybe_zoom_text(&self, size: NonNegativeLength) -> NonNegativeLength {
         size
     }
 }
 
 /// 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>,
--- a/servo/components/style/values/mod.rs
+++ b/servo/components/style/values/mod.rs
@@ -24,19 +24,16 @@ pub mod generics;
 pub mod specified;
 
 /// A CSS float value.
 pub type CSSFloat = f32;
 
 /// A CSS integer value.
 pub type CSSInteger = i32;
 
-/// The default font size.
-pub const FONT_MEDIUM_PX: i32 = 16;
-
 define_keyword_type!(None_, "none");
 define_keyword_type!(Auto, "auto");
 define_keyword_type!(Normal, "normal");
 
 /// Serialize a normalized value into percentage.
 pub fn serialize_percentage<W>(value: CSSFloat, dest: &mut W)
     -> fmt::Result where W: fmt::Write
 {
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/specified/font.rs
@@ -0,0 +1,354 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Specified values for font properties
+
+#[cfg(feature = "gecko")]
+use Atom;
+use app_units::Au;
+use cssparser::Parser;
+use properties::longhands::system_font::SystemFont;
+use std::fmt;
+use style_traits::{ToCss, ParseError};
+use values::computed::{font as computed, Context, NonNegativeLength, ToComputedValue};
+use values::specified::{LengthOrPercentage, NoCalcLength};
+use values::specified::length::FontBaseSize;
+
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
+/// 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
+    /// after font-relative (percent and em) values
+    /// have been applied, which is where the ratio
+    /// comes in. The offset comes in if we cascaded a calc value,
+    /// where the font-relative portion (em and percentage) will
+    /// go into the ratio, and the remaining units all computed together
+    /// will go into the offset.
+    /// See bug 1355707.
+    Keyword(computed::KeywordInfo),
+    /// font-size: smaller
+    Smaller,
+    /// font-size: larger
+    Larger,
+    /// Derived from a specified system font.
+    System(SystemFont)
+}
+
+impl ToCss for FontSize {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            FontSize::Length(ref lop) => lop.to_css(dest),
+            FontSize::Keyword(info) => info.kw.to_css(dest),
+            FontSize::Smaller => dest.write_str("smaller"),
+            FontSize::Larger => dest.write_str("larger"),
+            FontSize::System(sys) => sys.to_css(dest),
+        }
+    }
+}
+
+impl From<LengthOrPercentage> for FontSize {
+    fn from(other: LengthOrPercentage) -> Self {
+        FontSize::Length(other)
+    }
+}
+
+/// CSS font keywords
+#[derive(Animate, ComputeSquaredDistance, ToAnimatedValue, ToAnimatedZero)]
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
+#[allow(missing_docs)]
+pub enum KeywordSize {
+    XXSmall = 1, // This is to enable the NonZero optimization
+                 // which simplifies the representation of Option<KeywordSize>
+                 // in bindgen
+    XSmall,
+    Small,
+    Medium,
+    Large,
+    XLarge,
+    XXLarge,
+    // This is not a real font keyword and will not parse
+    // HTML font-size 7 corresponds to this value
+    XXXLarge,
+}
+
+impl KeywordSize {
+    /// Parse a keyword size
+    pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+        try_match_ident_ignore_ascii_case! { input.expect_ident()?,
+            "xx-small" => Ok(KeywordSize::XXSmall),
+            "x-small" => Ok(KeywordSize::XSmall),
+            "small" => Ok(KeywordSize::Small),
+            "medium" => Ok(KeywordSize::Medium),
+            "large" => Ok(KeywordSize::Large),
+            "x-large" => Ok(KeywordSize::XLarge),
+            "xx-large" => Ok(KeywordSize::XXLarge),
+        }
+    }
+
+    /// Convert to an HTML <font size> value
+    pub fn html_size(&self) -> u8 {
+        match *self {
+            KeywordSize::XXSmall => 0,
+            KeywordSize::XSmall => 1,
+            KeywordSize::Small => 2,
+            KeywordSize::Medium => 3,
+            KeywordSize::Large => 4,
+            KeywordSize::XLarge => 5,
+            KeywordSize::XXLarge => 6,
+            KeywordSize::XXXLarge => 7,
+        }
+    }
+}
+
+impl Default for KeywordSize {
+    fn default() -> Self {
+        KeywordSize::Medium
+    }
+}
+
+impl ToCss for KeywordSize {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        dest.write_str(match *self {
+            KeywordSize::XXSmall => "xx-small",
+            KeywordSize::XSmall => "x-small",
+            KeywordSize::Small => "small",
+            KeywordSize::Medium => "medium",
+            KeywordSize::Large => "large",
+            KeywordSize::XLarge => "x-large",
+            KeywordSize::XXLarge => "xx-large",
+            KeywordSize::XXXLarge => unreachable!("We should never serialize \
+                                      specified values set via
+                                      HTML presentation attributes"),
+        })
+    }
+}
+
+/// This is the ratio applied for font-size: larger
+/// and smaller by both Firefox and Chrome
+const LARGER_FONT_SIZE_RATIO: f32 = 1.2;
+
+/// The default font size.
+pub const FONT_MEDIUM_PX: i32 = 16;
+
+#[cfg(feature = "servo")]
+impl ToComputedValue for KeywordSize {
+    type ComputedValue = NonNegativeLength;
+    #[inline]
+    fn to_computed_value(&self, _: &Context) -> NonNegativeLength {
+        // https://drafts.csswg.org/css-fonts-3/#font-size-prop
+        match *self {
+            KeywordSize::XXSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 5,
+            KeywordSize::XSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 4,
+            KeywordSize::Small => Au::from_px(FONT_MEDIUM_PX) * 8 / 9,
+            KeywordSize::Medium => Au::from_px(FONT_MEDIUM_PX),
+            KeywordSize::Large => Au::from_px(FONT_MEDIUM_PX) * 6 / 5,
+            KeywordSize::XLarge => Au::from_px(FONT_MEDIUM_PX) * 3 / 2,
+            KeywordSize::XXLarge => Au::from_px(FONT_MEDIUM_PX) * 2,
+            KeywordSize::XXXLarge => Au::from_px(FONT_MEDIUM_PX) * 3,
+        }.into()
+    }
+
+    #[inline]
+    fn from_computed_value(_: &NonNegativeLength) -> Self {
+        unreachable!()
+    }
+}
+
+#[cfg(feature = "gecko")]
+impl ToComputedValue for KeywordSize {
+    type ComputedValue = NonNegativeLength;
+    #[inline]
+    fn to_computed_value(&self, cx: &Context) -> NonNegativeLength {
+        use gecko_bindings::structs::nsIAtom;
+        use values::specified::length::au_to_int_px;
+        // Data from nsRuleNode.cpp in Gecko
+        // Mapping from base size and HTML size to pixels
+        // The first index is (base_size - 9), the second is the
+        // HTML size. "0" is CSS keyword xx-small, not HTML size 0,
+        // since HTML size 0 is the same as 1.
+        //
+        //  xxs   xs      s      m     l      xl     xxl   -
+        //  -     0/1     2      3     4      5      6     7
+        static FONT_SIZE_MAPPING: [[i32; 8]; 8] = [
+            [9,    9,     9,     9,    11,    14,    18,    27],
+            [9,    9,     9,    10,    12,    15,    20,    30],
+            [9,    9,    10,    11,    13,    17,    22,    33],
+            [9,    9,    10,    12,    14,    18,    24,    36],
+            [9,   10,    12,    13,    16,    20,    26,    39],
+            [9,   10,    12,    14,    17,    21,    28,    42],
+            [9,   10,    13,    15,    18,    23,    30,    45],
+            [9,   10,    13,    16,    18,    24,    32,    48]
+        ];
+
+        static FONT_SIZE_FACTORS: [i32; 8] = [60, 75, 89, 100, 120, 150, 200, 300];
+
+        // XXXManishearth handle quirks mode (bug 1401322)
+
+        let ref gecko_font = cx.style().get_font().gecko();
+        let base_size = unsafe { Atom::with(gecko_font.mLanguage.raw::<nsIAtom>(), |atom| {
+            cx.font_metrics_provider.get_size(atom, gecko_font.mGenericID).0
+        }) };
+
+        let base_size_px = au_to_int_px(base_size as f32);
+        let html_size = self.html_size() as usize;
+        if base_size_px >= 9 && base_size_px <= 16 {
+            Au::from_px(FONT_SIZE_MAPPING[(base_size_px - 9) as usize][html_size]).into()
+        } else {
+            Au(FONT_SIZE_FACTORS[html_size] * base_size / 100).into()
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(_: &NonNegativeLength) -> Self {
+        unreachable!()
+    }
+}
+
+impl FontSize {
+    /// https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size
+    pub fn from_html_size(size: u8) -> Self {
+        FontSize::Keyword(match size {
+            // If value is less than 1, let it be 1.
+            0 | 1 => KeywordSize::XSmall,
+            2 => KeywordSize::Small,
+            3 => KeywordSize::Medium,
+            4 => KeywordSize::Large,
+            5 => KeywordSize::XLarge,
+            6 => KeywordSize::XXLarge,
+            // If value is greater than 7, let it be 7.
+            _ => KeywordSize::XXXLarge,
+        }.into())
+    }
+
+    /// Compute it against a given base font size
+    pub fn to_computed_value_against(
+        &self,
+        context: &Context,
+        base_size: FontBaseSize,
+    ) -> computed::FontSize {
+        use values::specified::length::FontRelativeLength;
+
+        let compose_keyword = |factor| {
+            context.style().get_parent_font()
+                   .clone_font_size().keyword_info
+                   .map(|i| i.compose(factor, Au(0).into()))
+        };
+        let mut info = None;
+        let size = match *self {
+            FontSize::Length(LengthOrPercentage::Length(
+                    NoCalcLength::FontRelative(value))) => {
+                if let FontRelativeLength::Em(em) = value {
+                    // If the parent font was keyword-derived, this is too.
+                    // Tack the em unit onto the factor
+                    info = compose_keyword(em);
+                }
+                value.to_computed_value(context, base_size).into()
+            }
+            FontSize::Length(LengthOrPercentage::Length(
+                    NoCalcLength::ServoCharacterWidth(value))) => {
+                value.to_computed_value(base_size.resolve(context)).into()
+            }
+            FontSize::Length(LengthOrPercentage::Length(
+                    NoCalcLength::Absolute(ref l))) => {
+                context.maybe_zoom_text(l.to_computed_value(context).into())
+            }
+            FontSize::Length(LengthOrPercentage::Length(ref l)) => {
+                l.to_computed_value(context).into()
+            }
+            FontSize::Length(LengthOrPercentage::Percentage(pc)) => {
+                // If the parent font was keyword-derived, this is too.
+                // Tack the % onto the factor
+                info = compose_keyword(pc.0);
+                base_size.resolve(context).scale_by(pc.0).into()
+            }
+            FontSize::Length(LengthOrPercentage::Calc(ref calc)) => {
+                let parent = context.style().get_parent_font().clone_font_size();
+                // if we contain em/% units and the parent was keyword derived, this is too
+                // Extract the ratio/offset and compose it
+                if (calc.em.is_some() || calc.percentage.is_some()) && parent.keyword_info.is_some() {
+                    let ratio = calc.em.unwrap_or(0.) + calc.percentage.map_or(0., |pc| pc.0);
+                    // Compute it, but shave off the font-relative part (em, %)
+                    // This will mean that other font-relative units like ex and ch will be computed against
+                    // the old font even when the font changes. There's no particular "right answer" for what
+                    // to do here -- Gecko recascades as if the font had changed, we instead track the changes
+                    // and reapply, which means that we carry over old computed ex/ch values whilst Gecko
+                    // recomputes new ones. This is enough of an edge case to not really matter.
+                    let abs = calc.to_computed_value_zoomed(context, FontBaseSize::Custom(Au(0).into()))
+                                  .length_component().into();
+                    info = parent.keyword_info.map(|i| i.compose(ratio, abs));
+                }
+                let calc = calc.to_computed_value_zoomed(context, base_size);
+                calc.to_used_value(Some(base_size.resolve(context))).unwrap().into()
+            }
+            FontSize::Keyword(i) => {
+                // As a specified keyword, this is keyword derived
+                info = Some(i);
+                context.maybe_zoom_text(i.kw.to_computed_value(context).scale_by(i.factor) + i.offset)
+            }
+            FontSize::Smaller => {
+                info = compose_keyword(1. / LARGER_FONT_SIZE_RATIO);
+                FontRelativeLength::Em(1. / LARGER_FONT_SIZE_RATIO)
+                    .to_computed_value(context, base_size).into()
+            }
+            FontSize::Larger => {
+                info = compose_keyword(LARGER_FONT_SIZE_RATIO);
+                FontRelativeLength::Em(LARGER_FONT_SIZE_RATIO)
+                    .to_computed_value(context, base_size).into()
+            }
+
+            FontSize::System(_) => {
+                #[cfg(feature = "servo")] {
+                    unreachable!()
+                }
+                #[cfg(feature = "gecko")] {
+                    context.cached_system_font.as_ref().unwrap().font_size.size
+                }
+            }
+        };
+        computed::FontSize {
+            size: size,
+            keyword_info: info,
+        }
+    }
+}
+
+impl ToComputedValue for FontSize {
+    type ComputedValue = computed::FontSize;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> computed::FontSize {
+        self.to_computed_value_against(context, FontBaseSize::InheritedStyle)
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &computed::FontSize) -> Self {
+        FontSize::Length(LengthOrPercentage::Length(
+            ToComputedValue::from_computed_value(&computed.size.0)
+        ))
+    }
+}
+
+impl FontSize {
+    /// Construct a system font value.
+    pub fn system_font(f: SystemFont) -> Self {
+        FontSize::System(f)
+    }
+
+    /// Obtain the system font, if any
+    pub fn get_system(&self) -> Option<SystemFont> {
+        if let FontSize::System(s) = *self {
+            Some(s)
+        } else {
+            None
+        }
+    }
+}
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -13,17 +13,17 @@ use font_metrics::FontMetricsQueryResult
 use parser::{Parse, ParserContext};
 use std::{cmp, fmt, mem};
 use std::ascii::AsciiExt;
 use std::ops::{Add, Mul};
 use style_traits::{ToCss, ParseError, StyleParseError};
 use style_traits::values::specified::AllowedNumericType;
 use stylesheets::CssRuleType;
 use super::{AllowQuirks, Number, ToComputedValue, Percentage};
-use values::{Auto, CSSFloat, Either, FONT_MEDIUM_PX, None_, Normal};
+use values::{Auto, CSSFloat, Either, None_, Normal};
 use values::{ExtremumLength, serialize_dimension};
 use values::computed::{self, CSSPixelLength, Context};
 use values::generics::NonNegative;
 use values::specified::NonNegativeNumber;
 use values::specified::calc::CalcNode;
 
 pub use values::specified::calc::CalcLengthOrPercentage;
 pub use super::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
@@ -91,18 +91,18 @@ pub enum FontBaseSize {
     Custom(Au),
 }
 
 impl FontBaseSize {
     /// Calculate the actual size for a given context
     pub fn resolve(&self, context: &Context) -> Au {
         match *self {
             FontBaseSize::Custom(size) => size,
-            FontBaseSize::CurrentStyle => Au::from(context.style().get_font().clone_font_size()),
-            FontBaseSize::InheritedStyle => Au::from(context.style().get_parent_font().clone_font_size()),
+            FontBaseSize::CurrentStyle => context.style().get_font().clone_font_size().size(),
+            FontBaseSize::InheritedStyle => context.style().get_parent_font().clone_font_size().size(),
         }
     }
 }
 
 impl FontRelativeLength {
     /// Computes the font-relative length.
     pub fn to_computed_value(&self, context: &Context, base_size: FontBaseSize) -> CSSPixelLength {
         use std::f32;
@@ -566,22 +566,16 @@ impl NoCalcLength {
         match *self {
             NoCalcLength::Absolute(length) => length.is_zero(),
             #[cfg(feature = "gecko")]
             NoCalcLength::Physical(length) => length.is_zero(),
             _ => false
         }
     }
 
-    #[inline]
-    /// Returns a `medium` length.
-    pub fn medium() -> NoCalcLength {
-        NoCalcLength::Absolute(AbsoluteLength::Px(FONT_MEDIUM_PX as f32))
-    }
-
     /// Get an absolute length from a px value.
     #[inline]
     pub fn from_px(px_value: CSSFloat) -> NoCalcLength {
         NoCalcLength::Absolute(AbsoluteLength::Px(px_value))
     }
 }
 
 /// An extension to `NoCalcLength` to parse `calc` expressions.
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -60,16 +60,17 @@ pub mod background;
 pub mod basic_shape;
 pub mod border;
 #[path = "box.rs"]
 pub mod box_;
 pub mod calc;
 pub mod color;
 pub mod effects;
 pub mod flex;
+pub mod font;
 #[cfg(feature = "gecko")]
 pub mod gecko;
 pub mod grid;
 pub mod image;
 pub mod length;
 pub mod percentage;
 pub mod position;
 pub mod rect;
--- a/servo/components/style/values/specified/text.rs
+++ b/servo/components/style/values/specified/text.rs
@@ -95,17 +95,17 @@ impl ToComputedValue for LineHeight {
                 GenericLineHeight::MozBlockHeight
             },
             GenericLineHeight::Number(number) => {
                 GenericLineHeight::Number(number.to_computed_value(context))
             },
             GenericLineHeight::Length(ref non_negative_lop) => {
                 let result = match non_negative_lop.0 {
                     LengthOrPercentage::Length(NoCalcLength::Absolute(ref abs)) => {
-                        context.maybe_zoom_text(abs.to_computed_value(context))
+                        context.maybe_zoom_text(abs.to_computed_value(context).into()).0
                     }
                     LengthOrPercentage::Length(ref length) => {
                         length.to_computed_value(context)
                     },
                     LengthOrPercentage::Percentage(ref p) => {
                         FontRelativeLength::Em(p.0)
                             .to_computed_value(
                                 context,
--- a/servo/tests/unit/stylo/size_of.rs
+++ b/servo/tests/unit/stylo/size_of.rs
@@ -27,17 +27,17 @@ fn size_of_selectors_dummy_types() {
 }
 
 // The size of this is critical to performance on the bloom-basic microbenchmark.
 // When iterating over a large Rule array, we want to be able to fast-reject
 // selectors (with the inline hashes) with as few cache misses as possible.
 size_of_test!(test_size_of_rule, style::stylist::Rule, 32);
 
 // Large pages generate tens of thousands of ComputedValues.
-size_of_test!(test_size_of_cv, ComputedValues, 272);
+size_of_test!(test_size_of_cv, ComputedValues, 256);
 
 size_of_test!(test_size_of_option_arc_cv, Option<Arc<ComputedValues>>, 8);
 size_of_test!(test_size_of_option_rule_node, Option<StrongRuleNode>, 8);
 
 size_of_test!(test_size_of_element_styles, ElementStyles, 16);
 size_of_test!(test_size_of_element_data, ElementData, 24);
 
 size_of_test!(test_size_of_property_declaration, style::properties::PropertyDeclaration, 32);