Bug 1399228 - Add keyword info to computed value of font-size; r?emilio draft
authorManish Goregaokar <manishearth@gmail.com>
Tue, 12 Sep 2017 13:53:39 -0700
changeset 669456 d947faab48a4b5d90c6f22277f9a8fcb8428acbe
parent 669216 14db7c0bcf9ae86c9ec2cc9d3c249a42e459a2a9
child 669457 412b4c823835e23d56eaa817d26d1e553c3c6d60
push id81335
push userbmo:manishearth@gmail.com
push dateSat, 23 Sep 2017 08:49:12 +0000
reviewersemilio
bugs1399228
milestone58.0a1
Bug 1399228 - Add keyword info to computed value of font-size; r?emilio MozReview-Commit-ID: AfPkvA2j1Gt
servo/components/gfx/font_context.rs
servo/components/layout/multicol.rs
servo/components/layout/text.rs
servo/components/style/gecko/media_queries.rs
servo/components/style/matching.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/components/style/rule_cache.rs
servo/components/style/servo/media_queries.rs
servo/components/style/values/computed/length.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/specified/length.rs
servo/components/style/values/specified/text.rs
--- 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/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
@@ -2162,35 +2162,35 @@ 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();
+        self.gecko.mSize = v.size().0;
+        self.gecko.mScriptUnconstrainedSize = v.size().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()) }
     }
@@ -2343,17 +2343,20 @@ fn static_assert() {
             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).into()
+        longhands::font_size::computed_value::T {
+            size: Au(self.gecko.mSize).into(),
+            info: None, // XXXManishearth this is a placeholder
+        }
     }
 
     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,17 +591,17 @@ 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;
@@ -643,23 +643,66 @@ macro_rules! impl_gecko_keyword_conversi
 
     impl From<specified::LengthOrPercentage> for SpecifiedValue {
         fn from(other: specified::LengthOrPercentage) -> Self {
             SpecifiedValue::Length(other)
         }
     }
 
     pub mod computed_value {
+        use app_units::Au;
+        use std::fmt;
+        use style_traits::ToCss;
         use values::computed::NonNegativeLength;
-        pub type T = NonNegativeLength;
+
+        #[derive(Copy, Clone, PartialEq, Debug)]
+        #[derive(ToAnimatedValue, Animate, ToAnimatedZero, ComputeSquaredDistance)]
+        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+        pub struct T {
+            pub size: NonNegativeLength,
+            pub info: Option<KeywordInfo>,
+        }
+
+        #[derive(Copy, Clone, PartialEq, Debug)]
+        #[derive(ToAnimatedValue, Animate, ToAnimatedZero, ComputeSquaredDistance)]
+        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+        pub struct KeywordInfo {
+            pub kw: super::KeywordSize,
+            pub factor: f32,
+            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,
+                }
+            }
+        }
+
+        impl T {
+            pub fn size(self) -> Au {
+                self.size.into()
+            }
+        }
+
+        impl ToCss for T {
+            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+                self.size.to_css(dest)
+            }
+        }
     }
 
     /// CSS font keywords
     #[derive(Clone, Copy, Debug, PartialEq)]
     #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
+    #[derive(ToAnimatedValue, Animate, ToAnimatedZero, ComputeSquaredDistance)]
     #[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,
@@ -722,41 +765,41 @@ macro_rules! impl_gecko_keyword_conversi
             })
         }
     }
 
     % if product == "servo":
         impl ToComputedValue for KeywordSize {
             type ComputedValue = NonNegativeLength;
             #[inline]
-            fn to_computed_value(&self, _: &Context) -> computed_value::T {
+            fn to_computed_value(&self, _: &Context) -> NonNegativeLength {
                 // 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 {
+            fn from_computed_value(_: &NonNegativeLength) -> Self {
                 unreachable!()
             }
         }
     % else:
         impl ToComputedValue for KeywordSize {
             type ComputedValue = NonNegativeLength;
             #[inline]
-            fn to_computed_value(&self, cx: &Context) -> computed_value::T {
+            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.
                 //
@@ -787,17 +830,17 @@ macro_rules! impl_gecko_keyword_conversi
                 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 {
+            fn from_computed_value(_: &NonNegativeLength) -> 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;
@@ -858,67 +901,106 @@ macro_rules! impl_gecko_keyword_conversi
             }
         }
 
         /// Compute it against a given base font size
         pub fn to_computed_value_against(
             &self,
             context: &Context,
             base_size: FontBaseSize,
-        ) -> NonNegativeLength {
+        ) -> computed_value::T {
             use values::specified::length::FontRelativeLength;
-            match *self {
+            let mut info = None;
+            let size = match *self {
                 SpecifiedValue::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 = context.style().get_parent_font()
+                                      .clone_font_size().info.map(|i| i.compose(em, Au(0).into()));
+                    }
                     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()
+                    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)) => {
+                    // If the parent font was keyword-derived, this is too.
+                    // Tack the % onto the factor
+                    info = context.style().get_parent_font().clone_font_size().info.map(|i| i.compose(pc.0, Au(0).into()));
                     base_size.resolve(context).scale_by(pc.0).into()
                 }
                 SpecifiedValue::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.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.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()
                 }
-                SpecifiedValue::Keyword(ref key, fraction, offset) => {
+                SpecifiedValue::Keyword(key, fraction, offset) => {
+                    // As a specified keyword, this is keyword derived
+                    info = Some(computed_value::KeywordInfo {
+                        kw: key,
+                        factor: fraction,
+                        offset: offset,
+                    });
                     let key_len = key.to_computed_value(context).scale_by(fraction) + offset;
-                    context.maybe_zoom_text(key_len.0).into()
+                    context.maybe_zoom_text(key_len).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
+                        context.cached_system_font.as_ref().unwrap().font_size.size
                     </%self:nongecko_unreachable>
                 }
-            }
+            };
+            computed_value::T { size, info }
         }
     }
 
     #[inline]
     #[allow(missing_docs)]
     pub fn get_initial_value() -> computed_value::T {
-        NonNegativeLength::new(FONT_MEDIUM_PX as f32)
+        computed_value::T {
+            size: Au::from_px(FONT_MEDIUM_PX).into(),
+            info: Some(computed_value::KeywordInfo {
+                kw: KeywordSize::Medium,
+                factor: 1.,
+                offset: Au(0).into(),
+            })
+        }
     }
 
     #[inline]
     pub fn get_initial_specified_value() -> SpecifiedValue {
         SpecifiedValue::Keyword(Medium, 1., Au(0).into())
     }
 
 
@@ -928,17 +1010,17 @@ macro_rules! impl_gecko_keyword_conversi
         #[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)
+                ToComputedValue::from_computed_value(&computed.size.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)
@@ -975,17 +1057,17 @@ macro_rules! impl_gecko_keyword_conversi
                 None
             }
         }
     }
 
     #[allow(unused_mut)]
     pub fn cascade_specified_font_size(context: &mut Context,
                                        specified_value: &SpecifiedValue,
-                                       mut computed: NonNegativeLength) {
+                                       mut computed: computed_value::T) {
         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.
             //
@@ -1006,18 +1088,25 @@ macro_rules! impl_gecko_keyword_conversi
             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();
+                    let len = context.maybe_zoom_text(kw.to_computed_value(context).scale_by(ratio) + offset);
+                    computed = computed_value::T {
+                        size: len,
+                        info: Some(computed_value::KeywordInfo {
+                            kw: kw,
+                            factor: ratio,
+                            offset: 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,29 +1115,28 @@ 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()
+            context.maybe_zoom_text(SpecifiedValue::Keyword(kw, ratio, offset).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();
 
@@ -1063,20 +1151,19 @@ macro_rules! impl_gecko_keyword_conversi
     ///
     /// 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>
@@ -2547,17 +2634,17 @@ 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(), 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/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
--- 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
@@ -154,30 +154,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/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -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;
--- 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,