Bug 1367275 - Part 5: stylo: Support -moz-min-font-size-ratio; r?xidorn draft
authorManish Goregaokar <manishearth@gmail.com>
Tue, 23 May 2017 18:15:47 -0700
changeset 585460 71ae832686a1bb6b3b81f69fcd20935b75ced68a
parent 585456 78fc31992ef3c597ce92e8daf05d282c39e85f0a
child 630722 608a62e0e8f9e53e2705db89ed5ff7c5e2ed51f9
push id61112
push userbmo:manishearth@gmail.com
push dateFri, 26 May 2017 21:54:33 +0000
reviewersxidorn
bugs1367275
milestone55.0a1
Bug 1367275 - Part 5: stylo: Support -moz-min-font-size-ratio; r?xidorn MozReview-Commit-ID: xRxkFJodeK
layout/base/nsPresContext.h
layout/reftests/mathml/reftest.list
layout/style/ServoBindings.cpp
layout/style/ServoBindings.h
layout/style/nsRuleNode.cpp
layout/style/nsRuleNode.h
servo/components/style/gecko/generated/bindings.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/specified/length.rs
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -578,18 +578,21 @@ public:
   float EffectiveTextZoom() const { return mEffectiveTextZoom; }
 
   /**
    * Get the minimum font size for the specified language. If aLanguage
    * is nullptr, then the document's language is used.  This combines
    * the language-specific global preference with the per-presentation
    * base minimum font size.
    */
-  int32_t MinFontSize(nsIAtom *aLanguage) const {
-    const LangGroupFontPrefs *prefs = GetFontPrefsForLang(aLanguage);
+  int32_t MinFontSize(nsIAtom *aLanguage, bool* aNeedsToCache = nullptr) const {
+    const LangGroupFontPrefs *prefs = GetFontPrefsForLang(aLanguage, aNeedsToCache);
+    if (aNeedsToCache && *aNeedsToCache) {
+      return 0;
+    }
     return std::max(mBaseMinFontSize, prefs->mMinimumFontSize);
   }
 
   /**
    * Get the per-presentation base minimum font size.  This size is
    * independent of the language-specific global preference.
    */
   int32_t BaseMinFontSize() const {
--- a/layout/reftests/mathml/reftest.list
+++ b/layout/reftests/mathml/reftest.list
@@ -364,17 +364,17 @@ fuzzy-if(OSX,1,100) fuzzy-if(skiaContent
 == mfrac-C-4.html mfrac-C-4-ref.html
 fuzzy-if(OSX,1,100) fuzzy-if(skiaContent,1,14) == mfrac-D-1.html mfrac-D-1-ref.html
 == mfrac-D-2.html mfrac-D-2-ref.html
 == mfrac-D-3.html mfrac-D-3-ref.html
 == mfrac-D-4.html mfrac-D-4-ref.html
 == mfrac-E-1.html mfrac-E-1-ref.html
 test-pref(dom.webcomponents.enabled,true) == shadow-dom-1.html shadow-dom-1-ref.html
 pref(font.size.inflation.emPerLine,25) == font-inflation-1.html font-inflation-1-ref.html
-test-pref(font.minimum-size.x-math,40) fails-if(styloVsGecko) == default-font.html default-font-ref.html
+test-pref(font.minimum-size.x-math,40) == default-font.html default-font-ref.html
 != radicalbar-1.html about:blank
 != radicalbar-1a.html about:blank
 != radicalbar-1b.html about:blank
 != radicalbar-1c.html about:blank
 != radicalbar-1d.html about:blank
 != radicalbar-2.html about:blank
 != radicalbar-2a.html about:blank
 != radicalbar-2b.html about:blank
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -2000,16 +2000,36 @@ Gecko_nsStyleFont_FixupNoneGeneric(nsSty
                                    RawGeckoPresContextBorrowed aPresContext)
 {
   const nsFont* defaultVariableFont = ThreadSafeGetDefaultFontHelper(aPresContext, aFont->mLanguage);
   nsRuleNode::FixupNoneGeneric(&aFont->mFont, aPresContext,
                                aFont->mGenericID, defaultVariableFont);
 }
 
 void
+Gecko_nsStyleFont_FixupMinFontSize(nsStyleFont* aFont,
+                                   RawGeckoPresContextBorrowed aPresContext)
+{
+  nscoord minFontSize;
+  bool needsCache = false;
+
+  {
+    AutoReadLock guard(*sServoLangFontPrefsLock);
+    minFontSize = aPresContext->MinFontSize(aFont->mLanguage, &needsCache);
+  }
+
+  if (needsCache) {
+    AutoWriteLock guard(*sServoLangFontPrefsLock);
+    minFontSize = aPresContext->MinFontSize(aFont->mLanguage, nullptr);
+  }
+
+  nsRuleNode::ApplyMinFontSize(aFont, aPresContext, minFontSize);
+}
+
+void
 FontSizePrefs::CopyFrom(const LangGroupFontPrefs& prefs)
 {
   mDefaultVariableSize = prefs.mDefaultVariableFont.size;
   mDefaultFixedSize = prefs.mDefaultFixedFont.size;
   mDefaultSerifSize = prefs.mDefaultSerifFont.size;
   mDefaultSansSerifSize = prefs.mDefaultSansSerifFont.size;
   mDefaultMonospaceSize = prefs.mDefaultMonospaceFont.size;
   mDefaultCursiveSize = prefs.mDefaultCursiveFont.size;
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -494,16 +494,18 @@ void Gecko_CSSValue_SetPairList(nsCSSVal
 void Gecko_CSSValue_Drop(nsCSSValueBorrowedMut css_value);
 NS_DECL_THREADSAFE_FFI_REFCOUNTING(nsCSSValueSharedList, CSSValueSharedList);
 bool Gecko_PropertyId_IsPrefEnabled(nsCSSPropertyID id);
 
 void Gecko_nsStyleFont_SetLang(nsStyleFont* font, nsIAtom* atom);
 void Gecko_nsStyleFont_CopyLangFrom(nsStyleFont* aFont, const nsStyleFont* aSource);
 void Gecko_nsStyleFont_FixupNoneGeneric(nsStyleFont* font,
                                         RawGeckoPresContextBorrowed pres_context);
+void Gecko_nsStyleFont_FixupMinFontSize(nsStyleFont* font,
+                                        RawGeckoPresContextBorrowed pres_context);
 FontSizePrefs Gecko_GetBaseSize(nsIAtom* lang);
 
 struct GeckoFontMetrics
 {
   nscoord mChSize;
   nscoord mXSize;
 };
 
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -438,16 +438,40 @@ nsRuleNode::FixupNoneGeneric(nsFont* aFo
         }
       }
     }
   } else {
     aFont->fontlist.SetDefaultFontType(eFamily_none);
   }
 }
 
+/* static */
+void
+nsRuleNode::ApplyMinFontSize(nsStyleFont* aFont,
+                             const nsPresContext* aPresContext,
+                             nscoord aMinFontSize)
+{
+  nscoord fontSize = aFont->mSize;
+
+  // enforce the user' specified minimum font-size on the value that we expose
+  // (but don't change font-size:0, since that would unhide hidden text)
+  if (fontSize > 0) {
+    if (aMinFontSize < 0) {
+      aMinFontSize = 0;
+    } else {
+      aMinFontSize = (aMinFontSize * aFont->mMinFontSizeRatio) / 100;
+    }
+    if (fontSize < aMinFontSize && !aPresContext->IsChrome()) {
+      // override the minimum font-size constraint
+      fontSize = aMinFontSize;
+    }
+  }
+  aFont->mFont.size = fontSize;
+}
+
 static nsSize CalcViewportUnitsScale(nsPresContext* aPresContext)
 {
   // The caller is making use of viewport units, so notify the pres context
   // that it will need to rebuild the rule tree if the size of the viewport
   // changes.
   aPresContext->SetUsesViewportUnits(true);
 
   // The default (when we have 'overflow: auto' on the root element, or
@@ -4036,33 +4060,18 @@ nsRuleNode::SetFont(nsPresContext* aPres
                 &aFont->mScriptUnconstrainedSize,
                 systemFont, aParentFont->mScriptUnconstrainedSize,
                 scriptLevelAdjustedUnconstrainedParentSize,
                 aUsedStartStruct, atRoot, unconstrainedConditions);
   }
   NS_ASSERTION(aFont->mScriptUnconstrainedSize <= aFont->mSize,
                "scriptminsize should never be making things bigger");
 
-  nscoord fontSize = aFont->mSize;
-
-  // enforce the user' specified minimum font-size on the value that we expose
-  // (but don't change font-size:0, since that would unhide hidden text)
-  if (fontSize > 0) {
-    nscoord minFontSize = aPresContext->MinFontSize(aFont->mLanguage);
-    if (minFontSize < 0) {
-      minFontSize = 0;
-    } else {
-      minFontSize = (minFontSize * aFont->mMinFontSizeRatio) / 100;
-    }
-    if (fontSize < minFontSize && !aPresContext->IsChrome()) {
-      // override the minimum font-size constraint
-      fontSize = minFontSize;
-    }
-  }
-  aFont->mFont.size = fontSize;
+  nsRuleNode::ApplyMinFontSize(aFont, aPresContext,
+                               aPresContext->MinFontSize(aFont->mLanguage));
 
   // font-size-adjust: number, none, inherit, initial, -moz-system-font
   const nsCSSValue* sizeAdjustValue = aRuleData->ValueForFontSizeAdjust();
   if (eCSSUnit_System_Font == sizeAdjustValue->GetUnit()) {
     aFont->mFont.sizeAdjust = systemFont.sizeAdjust;
   } else
     SetFactor(*sizeAdjustValue, aFont->mFont.sizeAdjust,
               aConditions, aParentFont->mFont.sizeAdjust, -1.0f,
--- a/layout/style/nsRuleNode.h
+++ b/layout/style/nsRuleNode.h
@@ -815,16 +815,24 @@ public:
    * Appropriately add the correct font if we are using DocumentFonts or
    * overriding for XUL
    */
   static void FixupNoneGeneric(nsFont* aFont,
                                const nsPresContext* aPresContext,
                                uint8_t aGenericFontID,
                                const nsFont* aDefaultVariableFont);
 
+  /**
+   * For an nsStyleFont with mSize set, apply minimum font size constraints
+   * from preferences, as well as -moz-min-font-size-ratio.
+   */
+  static void ApplyMinFontSize(nsStyleFont* aFont,
+                               const nsPresContext* aPresContext,
+                               nscoord aMinFontSize);
+
   // Transition never returns null; on out of memory it'll just return |this|.
   nsRuleNode* Transition(nsIStyleRule* aRule, mozilla::SheetType aLevel,
                          bool aIsImportantRule);
   nsRuleNode* GetParent() const { return mParent; }
   bool IsRoot() const { return mParent == nullptr; }
 
   // Return the root of the rule tree that this rule node is in.
   nsRuleNode* RuleTree();
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -1300,16 +1300,21 @@ extern "C" {
                                           aSource: *const nsStyleFont);
 }
 extern "C" {
     pub fn Gecko_nsStyleFont_FixupNoneGeneric(font: *mut nsStyleFont,
                                               pres_context:
                                                   RawGeckoPresContextBorrowed);
 }
 extern "C" {
+    pub fn Gecko_nsStyleFont_FixupMinFontSize(font: *mut nsStyleFont,
+                                              pres_context:
+                                                  RawGeckoPresContextBorrowed);
+}
+extern "C" {
     pub fn Gecko_GetBaseSize(lang: *mut nsIAtom) -> FontSizePrefs;
 }
 extern "C" {
     pub fn Gecko_GetFontMetrics(pres_context: RawGeckoPresContextBorrowed,
                                 is_vertical: bool, font: *const nsStyleFont,
                                 font_size: nscoord, use_user_font_set: bool)
      -> GeckoFontMetrics;
 }
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -1501,17 +1501,18 @@ fn static_assert() {
     }
 </%self:impl_trait>
 
 <%
     skip_font_longhands = """font-family font-size font-size-adjust font-weight
                              font-synthesis -x-lang font-variant-alternates
                              font-variant-east-asian font-variant-ligatures
                              font-variant-numeric font-language-override
-                             font-feature-settings font-variation-settings"""
+                             font-feature-settings font-variation-settings
+                             -moz-min-font-size-ratio"""
 %>
 <%self:impl_trait style_struct_name="Font"
     skip_longhands="${skip_font_longhands}"
     skip_additionals="*">
 
     pub fn set_font_feature_settings(&mut self, v: longhands::font_feature_settings::computed_value::T) {
         use values::generics::FontSettings;
 
@@ -1644,39 +1645,44 @@ fn static_assert() {
         unsafe { Gecko_CopyFontFamilyFrom(&mut self.gecko.mFont, &other.gecko.mFont); }
         self.gecko.mGenericID = other.gecko.mGenericID;
     }
 
     // FIXME(bholley): Gecko has two different sizes, one of which (mSize) is the
     // actual computed size, and the other of which (mFont.size) is the 'display
     // size' which takes font zooming into account. We don't handle font zooming yet.
     pub fn set_font_size(&mut self, v: longhands::font_size::computed_value::T) {
-        self.gecko.mFont.size = v.0;
         self.gecko.mSize = v.0;
         self.gecko.mScriptUnconstrainedSize = v.0;
     }
 
     /// Set font size, taking into account scriptminsize and scriptlevel
     /// Returns Some(size) if we have to recompute the script unconstrained size
     pub fn apply_font_size(&mut self, v: longhands::font_size::computed_value::T,
-                           parent: &Self) -> Option<Au> {
+                           parent: &Self,
+                           device: &Device) -> Option<Au> {
         let (adjusted_size, adjusted_unconstrained_size)
             = self.calculate_script_level_size(parent);
         // In this case, we have been unaffected by scriptminsize, ignore it
         if parent.gecko.mSize == parent.gecko.mScriptUnconstrainedSize &&
            adjusted_size == adjusted_unconstrained_size {
             self.set_font_size(v);
+            self.fixup_font_min_size(device);
             None
         } else {
-            self.gecko.mFont.size = v.0;
             self.gecko.mSize = v.0;
+            self.fixup_font_min_size(device);
             Some(Au(parent.gecko.mScriptUnconstrainedSize))
         }
     }
 
+    pub fn fixup_font_min_size(&mut self, device: &Device) {
+        unsafe { bindings::Gecko_nsStyleFont_FixupMinFontSize(&mut self.gecko, &*device.pres_context) }
+    }
+
     pub fn apply_unconstrained_font_size(&mut self, v: Au) {
         self.gecko.mScriptUnconstrainedSize = v.0;
     }
 
     /// Calculates the constrained and unconstrained font sizes to be inherited
     /// from the parent.
     ///
     /// See ComputeScriptLevelSize in Gecko's nsRuleNode.cpp
@@ -1775,17 +1781,18 @@ fn static_assert() {
     }
 
     /// This function will also handle scriptminsize and scriptlevel
     /// so should not be called when you just want the font sizes to be copied.
     /// Hence the different name.
     ///
     /// Returns true if the inherited keyword size was actually used
     pub fn inherit_font_size_from(&mut self, parent: &Self,
-                                  kw_inherited_size: Option<Au>) -> bool {
+                                  kw_inherited_size: Option<Au>,
+                                  device: &Device) -> bool {
         let (adjusted_size, adjusted_unconstrained_size)
             = self.calculate_script_level_size(parent);
         if adjusted_size.0 != parent.gecko.mSize ||
            adjusted_unconstrained_size.0 != parent.gecko.mScriptUnconstrainedSize {
             // This is incorrect. When there is both a keyword size being inherited
             // and a scriptlevel change, we must handle the keyword size the same
             // way we handle em units. This complicates things because we now have
             // to keep track of the adjusted and unadjusted ratios in the kw font size.
@@ -1794,33 +1801,33 @@ fn static_assert() {
             // If we were to fix this I would prefer doing it by removing the
             // ruletree walk on the Gecko side in nsRuleNode::SetGenericFont
             // and instead using extra bookkeeping in the mSize and mScriptUnconstrainedSize
             // values, and reusing those instead of font_size_keyword.
 
 
             // In the case that MathML has given us an adjusted size, apply it.
             // Keep track of the unconstrained adjusted size.
-            self.gecko.mFont.size = adjusted_size.0;
             self.gecko.mSize = adjusted_size.0;
             self.gecko.mScriptUnconstrainedSize = adjusted_unconstrained_size.0;
+            self.fixup_font_min_size(device);
             false
         } else if let Some(size) = kw_inherited_size {
             // Parent element was a keyword-derived size.
-            self.gecko.mFont.size = size.0;
             self.gecko.mSize = size.0;
             // MathML constraints didn't apply here, so we can ignore this.
             self.gecko.mScriptUnconstrainedSize = size.0;
+            self.fixup_font_min_size(device);
             true
         } else {
             // MathML isn't affecting us, and our parent element does not
             // have a keyword-derived size. Set things normally.
-            self.gecko.mFont.size = parent.gecko.mFont.size;
             self.gecko.mSize = parent.gecko.mSize;
             self.gecko.mScriptUnconstrainedSize = parent.gecko.mScriptUnconstrainedSize;
+            self.fixup_font_min_size(device);
             false
         }
     }
 
     pub fn clone_font_size(&self) -> longhands::font_size::computed_value::T {
         Au(self.gecko.mSize)
     }
 
@@ -1913,16 +1920,31 @@ fn static_assert() {
 
     ${impl_simple_copy('font_variant_east_asian', 'mFont.variantEastAsian')}
 
     pub fn set_font_variant_numeric(&mut self, v: longhands::font_variant_numeric::computed_value::T) {
         self.gecko.mFont.variantNumeric = v.to_gecko_keyword()
     }
 
     ${impl_simple_copy('font_variant_numeric', 'mFont.variantNumeric')}
+
+    #[allow(non_snake_case)]
+    pub fn set__moz_min_font_size_ratio(&mut self, v: longhands::_moz_min_font_size_ratio::computed_value::T) {
+        let percentage = if v.0 > 255. {
+            255.
+        } else if v.0 < 0. {
+            0.
+        } else {
+            v.0
+        };
+
+        self.gecko.mMinFontSizeRatio = percentage as u8;
+    }
+
+    ${impl_simple_copy('_moz_min_font_size_ratio', 'mMinFontSizeRatio')}
 </%self:impl_trait>
 
 <%def name="impl_copy_animation_or_transition_value(type, ident, gecko_ffi_name)">
     #[allow(non_snake_case)]
     pub fn copy_${type}_${ident}_from(&mut self, other: &Self) {
         unsafe { self.gecko.m${type.capitalize()}s.ensure_len(other.gecko.m${type.capitalize()}s.len()) };
 
         let count = other.gecko.m${type.capitalize()}${gecko_ffi_name}Count;
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -920,20 +920,22 @@
                context.style().get_font().gecko().mGenericID !=
                context.inherited_style().get_font().gecko().mGenericID {
                 if let Some((kw, ratio)) = context.style().font_size_keyword {
                     computed = kw.to_computed_value(context).scale_by(ratio);
                 }
             }
         % endif
 
-        let parent_unconstrained = context.mutate_style()
-                                       .mutate_font()
-                                       .apply_font_size(computed,
-                                                        parent);
+        let parent_unconstrained = {
+            let (style, device) = context.mutate_style_with_device();
+
+            style.mutate_font().apply_font_size(computed, parent, device)
+        };
+
 
         if let Some(parent) = parent_unconstrained {
             let new_unconstrained = specified_value
                         .to_computed_value_against(context, FontBaseSize::Custom(parent));
             context.mutate_style()
                    .mutate_font()
                    .apply_unconstrained_font_size(new_unconstrained);
         }
@@ -941,34 +943,36 @@
 
     pub fn cascade_inherit_font_size(context: &mut Context, parent: &Font) {
         // If inheriting, we must recompute font-size in case of language changes
         // using the font_size_keyword. We also need to do this to handle
         // mathml scriptlevel changes
         let kw_inherited_size = context.style().font_size_keyword.map(|(kw, ratio)| {
             SpecifiedValue::Keyword(kw, ratio).to_computed_value(context)
         });
-        let used_kw = context.mutate_style().mutate_font()
-               .inherit_font_size_from(parent, kw_inherited_size);
+        let parent_kw = context.inherited_style.font_size_keyword;
+        let (style, device) = context.mutate_style_with_device();
+        let used_kw = style.mutate_font()
+               .inherit_font_size_from(parent, kw_inherited_size, device);
         if used_kw {
-            context.mutate_style().font_size_keyword =
-                context.inherited_style.font_size_keyword;
+            style.font_size_keyword = parent_kw;
         } else {
-            context.mutate_style().font_size_keyword = None;
+            style.font_size_keyword = None;
         }
     }
 
     pub fn cascade_initial_font_size(context: &mut Context) {
         // font-size's default ("medium") does not always
         // compute to the same value and depends on the font
         let computed = longhands::font_size::get_initial_specified_value()
                             .to_computed_value(context);
-        context.mutate_style().mutate_${data.current_style_struct.name_lower}()
-               .set_font_size(computed);
-        context.mutate_style().font_size_keyword = Some((Default::default(), 1.));
+        let (style, device) = context.mutate_style_with_device();
+        style.mutate_font().set_font_size(computed);
+        style.mutate_font().fixup_font_min_size(device);
+        style.font_size_keyword = Some((Default::default(), 1.));
     }
 </%helpers:longhand>
 
 <%helpers:longhand products="gecko" name="font-size-adjust" animation_value_type="ComputedValue"
                    spec="https://drafts.csswg.org/css-fonts/#propdef-font-size-adjust">
     use properties::longhands::system_font::SystemFont;
     use std::fmt;
     use style_traits::ToCss;
@@ -2376,8 +2380,16 @@ macro_rules! exclusive_value {
 ${helpers.single_keyword("-moz-osx-font-smoothing",
                          "auto grayscale",
                          gecko_constant_prefix="NS_FONT_SMOOTHING",
                          gecko_ffi_name="mFont.smoothing",
                          products="gecko",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/font-smooth)",
                          animation_value_type="none",
                          need_clone=True)}
+
+${helpers.predefined_type("-moz-min-font-size-ratio",
+                          "Percentage",
+                          "computed::Percentage::hundred()",
+                          animation_value_type="none",
+                          products="gecko",
+                          internal=True,
+                          spec="Nonstandard (Internal-only)")}
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -613,16 +613,17 @@ impl LonghandId {
     fn is_early_property(&self) -> bool {
         matches!(*self,
             % if product == 'gecko':
             LonghandId::TextOrientation |
             LonghandId::AnimationName |
             LonghandId::TransitionProperty |
             LonghandId::XLang |
             LonghandId::MozScriptLevel |
+            LonghandId::MozMinFontSizeRatio |
             % endif
             LonghandId::FontSize |
             LonghandId::FontFamily |
             LonghandId::Color |
             LonghandId::TextDecorationLine |
             LonghandId::WritingMode |
             LonghandId::Direction
         )
@@ -1640,24 +1641,25 @@ pub mod style_structs {
                     hasher.write_u16(self.font_weight as u16);
                     self.font_stretch.hash(&mut hasher);
                     self.font_family.hash(&mut hasher);
                     self.hash = hasher.finish()
                 }
 
                 /// (Servo does not handle MathML, so this just calls copy_font_size_from)
                 pub fn inherit_font_size_from(&mut self, parent: &Self,
-                                              _: Option<Au>) -> bool {
+                                              _: Option<Au>, _: &Device) -> bool {
                     self.copy_font_size_from(parent);
                     false
                 }
                 /// (Servo does not handle MathML, so this just calls set_font_size)
                 pub fn apply_font_size(&mut self,
                                        v: longhands::font_size::computed_value::T,
-                                       _: &Self) -> Option<Au> {
+                                       _: &Self,
+                                       _: &Device) -> Option<Au> {
                     self.set_font_size(v);
                     None
                 }
                 /// (Servo does not handle MathML, so this does nothing)
                 pub fn apply_unconstrained_font_size(&mut self, _: Au) {
                 }
 
             % elif style_struct.name == "Outline":
@@ -2638,27 +2640,16 @@ pub fn apply_declarations<'a, F, I>(devi
             // Only a few properties are allowed to depend on the visited state
             // of links.  When cascading visited styles, we can save time by
             // only processing these properties.
             if flags.contains(VISITED_DEPENDENT_ONLY) &&
                !longhand_id.is_visited_dependent() {
                 continue
             }
 
-            // The computed value of some properties depends on the
-            // (sometimes computed) value of *other* properties.
-            //
-            // So we classify properties into "early" and "other", such that
-            // the only dependencies can be from "other" to "early".
-            //
-            // We iterate applicable_declarations twice, first cascading
-            // "early" properties then "other".
-            //
-            // Unfortunately, it’s not easy to check that this
-            // classification is correct.
             if
                 % if category_to_cascade_now == "early":
                     !
                 % endif
                 longhand_id.is_early_property()
             {
                 continue
             }
@@ -2728,16 +2719,17 @@ pub fn apply_declarations<'a, F, I>(devi
                                                  &mut cacheable,
                                                  &mut cascade_info,
                                                  error_reporter);
             % if product == "gecko":
             // Font size must be explicitly inherited to handle lang changes and
             // scriptlevel changes.
             } else if seen.contains(LonghandId::XLang) ||
                       seen.contains(LonghandId::MozScriptLevel) ||
+                      seen.contains(LonghandId::MozMinFontSizeRatio) ||
                       font_family.is_some() {
                 let discriminant = LonghandId::FontSize as usize;
                 let size = PropertyDeclaration::CSSWideKeyword(
                     LonghandId::FontSize, CSSWideKeyword::Inherit);
 
                 (CASCADE_PROPERTY[discriminant])(&size,
                                                  inherited_style,
                                                  default_style,
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -99,16 +99,18 @@ impl<'a> Context<'a> {
     pub fn viewport_size(&self) -> Size2D<Au> { self.device.au_viewport_size() }
     /// The style we're inheriting from.
     pub fn inherited_style(&self) -> &ComputedValues { &self.inherited_style }
     /// The current style. Note that only "eager" properties should be accessed
     /// from here, see the comment in the member.
     pub fn style(&self) -> &StyleBuilder { &self.style }
     /// A mutable reference to the current style.
     pub fn mutate_style(&mut self) -> &mut StyleBuilder<'a> { &mut self.style }
+    /// Get a mutable reference to the current style as well as the device
+    pub fn mutate_style_with_device(&mut self) -> (&mut StyleBuilder<'a>, &Device) { (&mut self.style, &self.device) }
 }
 
 /// An iterator over a slice of computed values
 #[derive(Clone)]
 pub struct ComputedVecIter<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> {
     cx: &'cx Context<'cx_a>,
     values: &'a [S],
 }
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -729,16 +729,22 @@ impl Percentage {
             _ => Err(())
         }
     }
 
     /// Parses a percentage token, but rejects it if it's negative.
     pub fn parse_non_negative(input: &mut Parser) -> Result<Self, ()> {
         Self::parse_with_clamping_mode(input, AllowedNumericType::NonNegative)
     }
+
+    /// 100%
+    #[inline]
+    pub fn hundred() -> Self {
+        Percentage(1.)
+    }
 }
 
 impl Parse for Percentage {
     #[inline]
     fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         Self::parse_with_clamping_mode(input, AllowedNumericType::All)
     }
 }