Bug 1349417 - Part 2: stylo: Add basic system font support, use for font-size and font-family; r?xidorn draft
authorManish Goregaokar <manishearth@gmail.com>
Tue, 21 Mar 2017 20:38:12 -0700
changeset 562841 0e0148a96e7ed644039c7565b06ce0de1e7819ee
parent 562840 e7ab11db1e8438ef6164f1924c9f3637ebe6daa3
child 562842 64e3b4ff90fd1d6f25225f4f195333f012b49fc5
child 564171 960d40b060d8d8b00cb43f01a440123c4951289b
push id54143
push userbmo:manishearth@gmail.com
push dateFri, 14 Apr 2017 13:31:18 +0000
reviewersxidorn
bugs1349417
milestone55.0a1
Bug 1349417 - Part 2: stylo: Add basic system font support, use for font-size and font-family; r?xidorn MozReview-Commit-ID: 7p3gtzw540J
layout/style/ServoBindings.cpp
layout/style/ServoBindings.h
layout/style/nsRuleNode.h
servo/components/script/dom/element.rs
servo/components/style/gecko_bindings/bindings.rs
servo/components/style/gecko_string_cache/mod.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/components/style/properties/shorthand/font.mako.rs
servo/ports/geckolib/glue.rs
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -844,16 +844,22 @@ SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIO
 #undef SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS
 
 nsIAtom*
 Gecko_Atomize(const char* aString, uint32_t aLength)
 {
   return NS_Atomize(nsDependentCSubstring(aString, aLength)).take();
 }
 
+nsIAtom*
+Gecko_Atomize16(const nsAString* aString)
+{
+  return NS_Atomize(*aString).take();
+}
+
 void
 Gecko_AddRefAtom(nsIAtom* aAtom)
 {
   NS_ADDREF(aAtom);
 }
 
 void
 Gecko_ReleaseAtom(nsIAtom* aAtom)
@@ -918,16 +924,43 @@ Gecko_FontFamilyList_AppendGeneric(FontF
 
 void
 Gecko_CopyFontFamilyFrom(nsFont* dst, const nsFont* src)
 {
   dst->fontlist = src->fontlist;
 }
 
 void
+Gecko_nsFont_InitSystem(nsFont* aDest, int32_t aFontId,
+                        const nsStyleFont* aFont, RawGeckoPresContextBorrowed aPresContext)
+{
+
+  const nsFont* defaultVariableFont =
+    aPresContext->GetDefaultFont(kPresContext_DefaultVariableFont_ID,
+                                 aFont->mLanguage);
+
+  // We have passed uninitialized memory to this function,
+  // initialize it. We can't simply return an nsFont because then
+  // we need to know its size beforehand. Servo cannot initialize nsFont
+  // itself, so this will do.
+  nsFont* system = new (aDest) nsFont(*defaultVariableFont);
+
+  *system = *defaultVariableFont;
+  LookAndFeel::FontID fontID = static_cast<LookAndFeel::FontID>(aFontId);
+  nsRuleNode::ComputeSystemFont(system, fontID, aPresContext);
+}
+
+void
+Gecko_nsFont_Destroy(nsFont* aDest)
+{
+  aDest->~nsFont();
+}
+
+
+void
 Gecko_SetImageOrientation(nsStyleVisibility* aVisibility,
                           double aRadians, bool aFlip)
 {
   aVisibility->mImageOrientation =
     nsStyleImageOrientation::CreateAsAngleAndFlip(aRadians, aFlip);
 }
 
 void
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -215,27 +215,33 @@ double Gecko_GetPositionInSegment(
 // |aBaseStyles| is nsRefPtrHashtable<nsUint32HashKey, RawServoAnimationValue>.
 // We use void* to avoid exposing nsRefPtrHashtable in FFI.
 RawServoAnimationValueBorrowedOrNull Gecko_AnimationGetBaseStyle(
   void* aBaseStyles,
   nsCSSPropertyID aProperty);
 
 // Atoms.
 nsIAtom* Gecko_Atomize(const char* aString, uint32_t aLength);
+nsIAtom* Gecko_Atomize16(const nsAString* aString);
 void Gecko_AddRefAtom(nsIAtom* aAtom);
 void Gecko_ReleaseAtom(nsIAtom* aAtom);
 const uint16_t* Gecko_GetAtomAsUTF16(nsIAtom* aAtom, uint32_t* aLength);
 bool Gecko_AtomEqualsUTF8(nsIAtom* aAtom, const char* aString, uint32_t aLength);
 bool Gecko_AtomEqualsUTF8IgnoreCase(nsIAtom* aAtom, const char* aString, uint32_t aLength);
 
 // Font style
 void Gecko_FontFamilyList_Clear(FontFamilyList* aList);
 void Gecko_FontFamilyList_AppendNamed(FontFamilyList* aList, nsIAtom* aName, bool aQuoted);
 void Gecko_FontFamilyList_AppendGeneric(FontFamilyList* list, FontFamilyType familyType);
 void Gecko_CopyFontFamilyFrom(nsFont* dst, const nsFont* src);
+// will not run destructors on dst, give it uninitialized memory
+// font_id is LookAndFeel::FontID
+void Gecko_nsFont_InitSystem(nsFont* dst, int32_t font_id,
+                             const nsStyleFont* font, RawGeckoPresContextBorrowed pres_context);
+void Gecko_nsFont_Destroy(nsFont* dst);
 
 // Visibility style
 void Gecko_SetImageOrientation(nsStyleVisibility* aVisibility,
                                double aRadians,
                                bool aFlip);
 void Gecko_SetImageOrientationAsFromImage(nsStyleVisibility* aVisibility);
 void Gecko_CopyImageOrientationFrom(nsStyleVisibility* aDst,
                                     const nsStyleVisibility* aSrc);
--- a/layout/style/nsRuleNode.h
+++ b/layout/style/nsRuleNode.h
@@ -1082,31 +1082,32 @@ public:
   // Fill unspecified layers by cycling through their values
   // till they all are of length aMaxItemCount
   static void FillAllBackgroundLists(nsStyleImageLayers& aLayers,
                                      uint32_t aMaxItemCount);
 
   static void FillAllMaskLists(nsStyleImageLayers& aLayers,
                                uint32_t aMaxItemCount);
 
+  static void ComputeSystemFont(nsFont* aSystemFont,
+                                mozilla::LookAndFeel::FontID aFontID,
+                                const nsPresContext* aPresContext);
+
 private:
 #ifdef DEBUG
   // non-inline helper function to allow assertions without incomplete
   // type errors
   bool ContextHasCachedData(nsStyleContext* aContext, nsStyleStructID aSID);
 #endif
 
   // Store style struct on the style context and tell the style context
   // that it doesn't own the data
   static void StoreStyleOnContext(nsStyleContext* aContext,
                                   nsStyleStructID aSID,
                                   void* aStruct);
-  static void ComputeSystemFont(nsFont* aSystemFont,
-                                mozilla::LookAndFeel::FontID aFontID,
-                                const nsPresContext* aPresContext);
 };
 
 /**
  * We allocate arrays of CSS values with alloca.  (These arrays are a
  * fixed size per style struct, but we don't want to waste the
  * allocation and construction/destruction costs of the big structs when
  * we're handling much smaller ones.)  Since the lifetime of an alloca
  * allocation is the life of the calling function, the caller must call
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -463,17 +463,17 @@ impl LayoutElementHelpers for LayoutJS<E
         } else {
             None
         };
 
         if let Some(font_family) = font_family {
             hints.push(from_declaration(
                 shared_lock,
                 PropertyDeclaration::FontFamily(
-                        font_family::computed_value::T(vec![
+                        font_family::SpecifiedValue::Values(vec![
                             font_family::computed_value::FontFamily::from_atom(
                                 font_family)]))));
         }
 
         let font_size = self.downcast::<HTMLFontElement>().and_then(|this| this.get_size());
 
         if let Some(font_size) = font_size {
             hints.push(from_declaration(
--- a/servo/components/style/gecko_bindings/bindings.rs
+++ b/servo/components/style/gecko_bindings/bindings.rs
@@ -666,16 +666,19 @@ extern "C" {
                                        aProperty: nsCSSPropertyID)
      -> RawServoAnimationValueBorrowedOrNull;
 }
 extern "C" {
     pub fn Gecko_Atomize(aString: *const ::std::os::raw::c_char, aLength: u32)
      -> *mut nsIAtom;
 }
 extern "C" {
+    pub fn Gecko_Atomize16(aString: *const nsAString) -> *mut nsIAtom;
+}
+extern "C" {
     pub fn Gecko_AddRefAtom(aAtom: *mut nsIAtom);
 }
 extern "C" {
     pub fn Gecko_ReleaseAtom(aAtom: *mut nsIAtom);
 }
 extern "C" {
     pub fn Gecko_GetAtomAsUTF16(aAtom: *mut nsIAtom, aLength: *mut u32)
      -> *const u16;
@@ -702,16 +705,24 @@ extern "C" {
 extern "C" {
     pub fn Gecko_FontFamilyList_AppendGeneric(list: *mut FontFamilyList,
                                               familyType: FontFamilyType);
 }
 extern "C" {
     pub fn Gecko_CopyFontFamilyFrom(dst: *mut nsFont, src: *const nsFont);
 }
 extern "C" {
+    pub fn Gecko_nsFont_InitSystem(dst: *mut nsFont, font_id: i32,
+                                   font: *const nsStyleFont,
+                                   pres_context: RawGeckoPresContextBorrowed);
+}
+extern "C" {
+    pub fn Gecko_nsFont_Destroy(dst: *mut nsFont);
+}
+extern "C" {
     pub fn Gecko_SetImageOrientation(aVisibility: *mut nsStyleVisibility,
                                      aRadians: f64, aFlip: bool);
 }
 extern "C" {
     pub fn Gecko_SetImageOrientationAsFromImage(aVisibility:
                                                     *mut nsStyleVisibility);
 }
 extern "C" {
--- a/servo/components/style/gecko_string_cache/mod.rs
+++ b/servo/components/style/gecko_string_cache/mod.rs
@@ -3,19 +3,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #![allow(unsafe_code)]
 
 //! A drop-in replacement for string_cache, but backed by Gecko `nsIAtom`s.
 
 use gecko_bindings::bindings::Gecko_AddRefAtom;
 use gecko_bindings::bindings::Gecko_Atomize;
+use gecko_bindings::bindings::Gecko_Atomize16;
 use gecko_bindings::bindings::Gecko_ReleaseAtom;
 use gecko_bindings::structs::nsIAtom;
 use precomputed_hash::PrecomputedHash;
+use nsstring::nsAString;
 use std::borrow::{Cow, Borrow};
 use std::char::{self, DecodeUtf16};
 use std::fmt::{self, Write};
 use std::hash::{Hash, Hasher};
 use std::iter::Cloned;
 use std::mem;
 use std::ops::Deref;
 use std::slice;
@@ -276,16 +278,27 @@ impl<'a> From<&'a str> for Atom {
         unsafe {
             Atom(WeakAtom::new(
                 Gecko_Atomize(string.as_ptr() as *const _, string.len() as u32)
             ))
         }
     }
 }
 
+impl<'a> From<&'a nsAString> for Atom {
+    #[inline]
+    fn from(string: &nsAString) -> Atom {
+        unsafe {
+            Atom(WeakAtom::new(
+                Gecko_Atomize16(string)
+            ))
+        }
+    }
+}
+
 impl<'a> From<Cow<'a, str>> for Atom {
     #[inline]
     fn from(string: Cow<'a, str>) -> Atom {
         Atom::from(&*string)
     }
 }
 
 impl From<String> for Atom {
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -89,25 +89,27 @@ pub struct ComputedValues {
     /// 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.
     pub font_size_keyword: Option<(longhands::font_size::KeywordSize, f32)>,
+    pub cached_system_font: Option<longhands::system_font::ComputedSystemFont>,
 }
 
 impl ComputedValues {
     pub fn inherit_from(parent: &Self, default: &Self) -> Arc<Self> {
         Arc::new(ComputedValues {
             custom_properties: parent.custom_properties.clone(),
             writing_mode: parent.writing_mode,
             root_font_size: parent.root_font_size,
             font_size_keyword: parent.font_size_keyword,
+            cached_system_font: None,
             % for style_struct in data.style_structs:
             % if style_struct.inherited:
             ${style_struct.ident}: parent.${style_struct.ident}.clone(),
             % else:
             ${style_struct.ident}: default.${style_struct.ident}.clone(),
             % endif
             % endfor
         })
@@ -120,29 +122,31 @@ impl ComputedValues {
             % for style_struct in data.style_structs:
            ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
             % endfor
     ) -> Self {
         ComputedValues {
             custom_properties: custom_properties,
             writing_mode: writing_mode,
             root_font_size: root_font_size,
+            cached_system_font: None,
             font_size_keyword: font_size_keyword,
             % for style_struct in data.style_structs:
             ${style_struct.ident}: ${style_struct.ident},
             % endfor
         }
     }
 
     pub fn default_values(pres_context: RawGeckoPresContextBorrowed) -> Arc<Self> {
         Arc::new(ComputedValues {
             custom_properties: None,
             writing_mode: WritingMode::empty(), // FIXME(bz): This seems dubious
             root_font_size: longhands::font_size::get_initial_value(), // FIXME(bz): Also seems dubious?
             font_size_keyword: Some((Default::default(), 1.)),
+            cached_system_font: None,
             % for style_struct in data.style_structs:
                 ${style_struct.ident}: style_structs::${style_struct.name}::default(pres_context),
             % endfor
         })
     }
 
     #[inline]
     pub fn is_display_contents(&self) -> bool {
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -260,16 +260,21 @@
                                                              &value);
                         }
                         % if property.logical:
                             let wm = context.style.writing_mode;
                         % endif
                         <% maybe_wm = ", wm" if property.logical else "" %>
                         match *value {
                             DeclaredValue::Value(ref specified_value) => {
+                                % if property.ident in "font_size font_family".split() and product == "gecko":
+                                    if let Some(sf) = specified_value.get_system() {
+                                        longhands::system_font::resolve_system_font(sf, context);
+                                    }
+                                % endif
                                 let computed = specified_value.to_computed_value(context);
                                 % if property.ident == "font_size":
                                     if let longhands::font_size::SpecifiedValue::Keyword(kw, fraction)
                                                         = **specified_value {
                                         context.mutate_style().font_size_keyword = Some((kw, fraction));
                                     } else if let Some(ratio) = specified_value.as_font_ratio() {
                                         // In case a font-size-relative value was applied to a keyword
                                         // value, we must preserve this fact in case the generic font family
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -340,25 +340,30 @@ impl AnimationValue {
                             % endif
                     }
                 % endif
             % endfor
         }
     }
 
     /// Construct an AnimationValue from a property declaration
-    pub fn from_declaration(decl: &PropertyDeclaration, context: &Context, initial: &ComputedValues) -> Option<Self> {
+    pub fn from_declaration(decl: &PropertyDeclaration, context: &mut Context, initial: &ComputedValues) -> Option<Self> {
         use error_reporting::StdoutErrorReporter;
         use properties::LonghandId;
         use properties::DeclaredValue;
 
         match *decl {
             % for prop in data.longhands:
             % if prop.animatable:
             PropertyDeclaration::${prop.camel_case}(ref val) => {
+                % if prop.ident in "font_size font_family".split() and product == "gecko":
+                    if let Some(sf) = val.get_system() {
+                        longhands::system_font::resolve_system_font(sf, context);
+                    }
+                % endif
                 Some(AnimationValue::${prop.camel_case}(val.to_computed_value(context)))
             },
             % endif
             % endfor
             PropertyDeclaration::CSSWideKeyword(id, keyword) => {
                 match id {
                     // We put all the animatable properties first in the hopes
                     // that it might increase match locality.
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -1,29 +1,40 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
-<% from data import Method %>
+<% from data import Method, to_camel_case, to_rust_ident %>
 
 <% data.new_style_struct("Font",
                          inherited=True) %>
+
+<%def name="nongecko_unreachable()">
+    %if product == "gecko":
+        ${caller.body()}
+    %else:
+        unreachable!()
+    %endif
+</%def>
+
 <%helpers:longhand name="font-family" animation_type="none" need_index="True"
                    spec="https://drafts.csswg.org/css-fonts/#propdef-font-family">
+    use properties::longhands::system_font::SystemFont;
     use self::computed_value::{FontFamily, FamilyName};
+    use std::fmt;
+    use style_traits::ToCss;
     use values::HasViewportPercentage;
     use values::computed::ComputedValueAsSpecified;
-    pub use self::computed_value::T as SpecifiedValue;
 
-    impl ComputedValueAsSpecified for SpecifiedValue {}
     no_viewport_percentage!(SpecifiedValue);
 
     pub mod computed_value {
         use cssparser::{CssStringWriter, Parser, serialize_identifier};
+        use properties::longhands::system_font::SystemFont;
         use std::fmt::{self, Write};
         use Atom;
         use style_traits::ToCss;
         pub use self::FontFamily as SingleComputedValue;
 
         #[derive(Debug, PartialEq, Eq, Clone, Hash)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
         pub enum FontFamily {
@@ -195,19 +206,71 @@
 
     /// <family-name>#
     /// <family-name> = <string> | [ <ident>+ ]
     /// TODO: <generic-family>
     pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
         SpecifiedValue::parse(input)
     }
 
+    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+    pub enum SpecifiedValue {
+        Values(Vec<FontFamily>),
+        System(SystemFont),
+    }
+
+    impl ToComputedValue for SpecifiedValue {
+        type ComputedValue = computed_value::T;
+        fn to_computed_value(&self, _cx: &Context) -> Self::ComputedValue {
+            match *self {
+                SpecifiedValue::Values(ref v) => computed_value::T(v.clone()),
+                SpecifiedValue::System(_) => {
+                    <%self:nongecko_unreachable>
+                        _cx.style.cached_system_font.as_ref().unwrap().font_family.clone()
+                    </%self:nongecko_unreachable>
+                }
+            }
+        }
+        fn from_computed_value(other: &computed_value::T) -> Self {
+            SpecifiedValue::Values(other.0.clone())
+        }
+    }
+
     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
+            }
+        }
+
         pub fn parse(input: &mut Parser) -> Result<Self, ()> {
-            input.parse_comma_separated(|input| FontFamily::parse(input)).map(SpecifiedValue)
+            input.parse_comma_separated(|input| FontFamily::parse(input)).map(SpecifiedValue::Values)
+        }
+    }
+
+
+    impl ToCss for SpecifiedValue {
+        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+            match *self {
+                SpecifiedValue::Values(ref v) => {
+                    let mut iter = v.iter();
+                    iter.next().unwrap().to_css(dest)?;
+                    for family in iter {
+                        dest.write_str(", ")?;
+                        family.to_css(dest)?;
+                    }
+                    Ok(())
+                }
+                _ => Ok(())
+            }
         }
     }
 
     /// `FamilyName::parse` is based on `FontFamily::parse` and not the other way around
     /// because we want the former to exclude generic family keywords.
     impl Parse for FamilyName {
         fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
             match FontFamily::parse(input) {
@@ -413,24 +476,26 @@
 <%helpers:longhand name="font-size" need_clone="True" animation_type="normal"
                    spec="https://drafts.csswg.org/css-fonts/#propdef-font-size">
     use app_units::Au;
     use std::fmt;
     use style_traits::ToCss;
     use values::{FONT_MEDIUM_PX, HasViewportPercentage};
     use values::specified::{FontRelativeLength, LengthOrPercentage, Length};
     use values::specified::{NoCalcLength, Percentage};
+    use properties::longhands::system_font::SystemFont;
 
     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(_) => Ok(()),
             }
         }
     }
 
     impl HasViewportPercentage for SpecifiedValue {
         fn has_viewport_percentage(&self) -> bool {
             match *self {
                 SpecifiedValue::Length(ref lop) => lop.has_viewport_percentage(),
@@ -447,16 +512,17 @@
         /// The ratio in any specified keyword value
         /// will be 1, but we cascade keywordness even
         /// after font-relative (percent and em) values
         /// have been applied, which is where the keyword
         /// comes in. See bug 1355707
         Keyword(KeywordSize, f32),
         Smaller,
         Larger,
+        System(SystemFont)
     }
 
     impl From<specified::LengthOrPercentage> for SpecifiedValue {
         fn from(other: specified::LengthOrPercentage) -> Self {
             SpecifiedValue::Length(other)
         }
     }
 
@@ -672,16 +738,22 @@
                 SpecifiedValue::Smaller => {
                     FontRelativeLength::Em(0.85).to_computed_value(context,
                                                                    /* use_inherited */ true)
                 }
                 SpecifiedValue::Larger => {
                     FontRelativeLength::Em(1.2).to_computed_value(context,
                                                                    /* use_inherited */ true)
                 }
+
+                SpecifiedValue::System(_) => {
+                    <%self:nongecko_unreachable>
+                        context.style.cached_system_font.as_ref().unwrap().font_size
+                    </%self:nongecko_unreachable>
+                }
             }
         }
 
         #[inline]
         fn from_computed_value(computed: &computed_value::T) -> Self {
                 SpecifiedValue::Length(LengthOrPercentage::Length(
                         ToComputedValue::from_computed_value(computed)
                 ))
@@ -698,16 +770,28 @@
         }
 
         match_ignore_ascii_case! {&*input.expect_ident()?,
             "smaller" => Ok(SpecifiedValue::Smaller),
             "larger" => Ok(SpecifiedValue::Larger),
             _ => Err(())
         }
     }
+    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
+            }
+        }
+    }
 </%helpers:longhand>
 
 <%helpers:longhand products="gecko" name="font-size-adjust" animation_type="normal"
                    spec="https://drafts.csswg.org/css-fonts/#propdef-font-size-adjust">
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
 
@@ -1796,8 +1880,121 @@ macro_rules! exclusive_value {
     }
 
     pub fn parse(_context: &ParserContext, _input: &mut Parser) -> Result<SpecifiedValue, ()> {
         debug_assert!(false, "Should be set directly by presentation attributes only.");
         Err(())
     }
 </%helpers:longhand>
 
+
+% if product == "gecko":
+    pub mod system_font {
+        use app_units::Au;
+        use cssparser::Parser;
+        use properties::longhands;
+        use values::computed::{ToComputedValue, Context};
+        <%
+            system_fonts = """caption icon menu message-box small-caption status-bar
+                              -moz-window -moz-document -moz-workspace -moz-desktop
+                              -moz-info -moz-dialog -moz-button -moz-pull-down-menu
+                              -moz-list -moz-field""".split()
+        %>
+        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+        pub enum SystemFont {
+            % for font in system_fonts:
+                ${to_camel_case(font)},
+            % endfor
+        }
+
+        impl ToComputedValue for SystemFont {
+            type ComputedValue = ComputedSystemFont;
+
+            fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
+                use gecko_bindings::bindings;
+                use gecko_bindings::structs::{LookAndFeel_FontID, nsFont};
+                use std::mem;
+
+                let id = match *self {
+                    % for font in system_fonts:
+                        SystemFont::${to_camel_case(font)} => {
+                            LookAndFeel_FontID::eFont_${to_camel_case(font.replace("-moz-", ""))}
+                        }
+                    % endfor
+                };
+
+                let mut system: nsFont = unsafe { mem::uninitialized() };
+                unsafe {
+                    bindings::Gecko_nsFont_InitSystem(&mut system, id as i32,
+                                            cx.style.get_font().gecko(),
+                                            &*cx.device.pres_context)
+                }
+                let family = system.fontlist.mFontlist.iter().map(|font| {
+                    use properties::longhands::font_family::computed_value::*;
+                    FontFamily::FamilyName(FamilyName {
+                        name: (&*font.mName).into(),
+                        quoted: true
+                    })
+                }).collect::<Vec<_>>();
+                let ret = ComputedSystemFont {
+                    font_family: longhands::font_family::computed_value::T(family),
+                    font_size: Au(system.size),
+                    system_font: *self,
+                };
+                unsafe { bindings::Gecko_nsFont_Destroy(&mut system); }
+                ret
+            }
+
+            fn from_computed_value(_: &ComputedSystemFont) -> Self {
+                unreachable!()
+            }
+        }
+
+        #[inline]
+        /// Compute and cache a system font
+        ///
+        /// Must be called before attempting to compute a system font
+        /// specified value
+        pub fn resolve_system_font(system: SystemFont, context: &mut Context) {
+            if context.style.cached_system_font.is_none() {
+                let computed = system.to_computed_value(context);
+                context.style.cached_system_font = Some(computed);
+            }
+            debug_assert!(system == context.style.cached_system_font.as_ref().unwrap().system_font)
+        }
+
+        #[derive(Clone, Debug, PartialEq, Eq, Hash)]
+        pub struct ComputedSystemFont {
+            pub font_family: longhands::font_family::computed_value::T,
+            pub font_size: longhands::font_size::computed_value::T,
+            pub system_font: SystemFont,
+        }
+
+        impl SystemFont {
+            pub fn parse(input: &mut Parser) -> Result<Self, ()> {
+                Ok(match_ignore_ascii_case! { &*input.expect_ident()?,
+                    % for font in system_fonts:
+                        "${font}" => SystemFont::${to_camel_case(font)},
+                    % endfor
+                    _ => return Err(())
+                })
+            }
+        }
+    }
+% else:
+    pub mod system_font {
+        use cssparser::Parser;
+
+        // We don't parse system fonts, but in the interest of not littering
+        // a lot of code with `if product == gecko` conditionals, we have a
+        // dummy system font module that does nothing
+
+        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+        /// void enum for system font, can never exist
+        pub enum SystemFont {}
+        impl SystemFont {
+            pub fn parse(_: &mut Parser) -> Result<Self, ()> {
+                Err(())
+            }
+        }
+    }
+% endif
--- a/servo/components/style/properties/shorthand/font.mako.rs
+++ b/servo/components/style/properties/shorthand/font.mako.rs
@@ -11,18 +11,19 @@
                                     ${'font-kerning' if product == 'gecko' or data.testing else ''}
                                     ${'font-variant-alternates' if product == 'gecko' or data.testing else ''}
                                     ${'font-variant-east-asian' if product == 'gecko' or data.testing else ''}
                                     ${'font-variant-ligatures' if product == 'gecko' or data.testing else ''}
                                     ${'font-variant-numeric' if product == 'gecko' or data.testing else ''}
                                     ${'font-variant-position' if product == 'gecko' or data.testing else ''}
                                     ${'font-language-override' if product == 'gecko' or data.testing else ''}"
                     spec="https://drafts.csswg.org/css-fonts-3/#propdef-font">
-    use properties::longhands::{font_style, font_variant_caps, font_weight, font_stretch};
-    use properties::longhands::{font_size, line_height};
+    use properties::longhands::{font_family, font_style, font_weight, font_stretch};
+    use properties::longhands::{font_size, line_height, font_variant_caps};
+    use properties::longhands::system_font::SystemFont;
     <%
         gecko_sub_properties = "kerning language_override size_adjust \
                                 variant_alternates variant_east_asian \
                                 variant_ligatures variant_numeric \
                                 variant_position".split()
     %>
     % if product == "gecko" or data.testing:
         % for prop in gecko_sub_properties:
@@ -33,16 +34,29 @@
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let mut nb_normals = 0;
         let mut style = None;
         let mut variant_caps = None;
         let mut weight = None;
         let mut stretch = None;
         let size;
+        % if product == "gecko":
+            if let Ok(sys) = input.try(SystemFont::parse) {
+                return Ok(Longhands {
+                     % for name in "family size".split():
+                         font_${name}: font_${name}::SpecifiedValue::system_font(sys),
+                     % endfor
+                     % for name in "style weight stretch variant_caps".split() + gecko_sub_properties:
+                        font_${name}: font_${name}::get_initial_specified_value(),
+                     % endfor
+                     line_height: line_height::get_initial_specified_value(),
+                 })
+            }
+        % endif
         loop {
             // Special-case 'normal' because it is valid in each of
             // font-style, font-weight, font-variant and font-stretch.
             // Leaves the values to None, 'normal' is the initial value for each of them.
             if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
                 nb_normals += 1;
                 continue;
             }
@@ -83,17 +97,17 @@
         }
         let line_height = if input.try(|input| input.expect_delim('/')).is_ok() {
             Some(try!(line_height::parse(context, input)))
         } else {
             None
         };
         let family = FontFamily::parse(input)?;
         Ok(Longhands {
-            % for name in "style variant_caps weight stretch size".split():
+            % for name in "style weight stretch size variant_caps".split():
                 font_${name}: unwrap_or_initial!(font_${name}, ${name}),
             % endfor
             line_height: unwrap_or_initial!(line_height),
             font_family: family,
             % if product == "gecko" or data.testing:
                 % for name in gecko_sub_properties:
                     font_${name}: font_${name}::get_initial_specified_value(),
                 % endfor
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -1904,17 +1904,17 @@ pub extern "C" fn Servo_GetComputedKeyfr
     let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
 
     let style = ComputedValues::as_arc(&style);
     let parent_style = parent_style.as_ref().map(|r| &**ComputedValues::as_arc(&r));
 
     let default_values = data.default_computed_values();
     let metrics = get_metrics_provider_for_product();
 
-    let context = Context {
+    let mut context = Context {
         is_root_element: false,
         device: &data.stylist.device,
         inherited_style: parent_style.unwrap_or(default_values),
         layout_parent_style: parent_style.unwrap_or(default_values),
         style: (**style).clone(),
         font_metrics_provider: &metrics,
         in_media_query: false,
     };
@@ -1932,17 +1932,17 @@ pub extern "C" fn Servo_GetComputedKeyfr
             let declarations = Locked::<PropertyDeclarationBlock>::as_arc(&declarations);
             let guard = declarations.read_with(&guard);
 
             let anim_iter = guard.declarations()
                             .iter()
                             .filter_map(|&(ref decl, imp)| {
                                 if imp == Importance::Normal {
                                     let property = TransitionProperty::from_declaration(decl);
-                                    let animation = AnimationValue::from_declaration(decl, &context, default_values);
+                                    let animation = AnimationValue::from_declaration(decl, &mut context, default_values);
                                     debug_assert!(property.is_none() == animation.is_none(),
                                                   "The failure condition of TransitionProperty::from_declaration \
                                                    and AnimationValue::from_declaration should be the same");
                                     // Skip the property if either ::from_declaration fails.
                                     if property.is_none() || animation.is_none() {
                                         None
                                     } else {
                                         Some((property.unwrap(), animation.unwrap()))