servo: Merge #14174 - style: Refactor and add infrastructure for font metrics in style (from emilio:font-provider); r=Manishearth
authorEmilio Cobos Álvarez <ecoal95@gmail.com>
Sun, 13 Nov 2016 04:55:02 -0600
changeset 340144 e28f21183e390f9ab078255a6cfca3cc0d260cba
parent 340143 384594b35ee44674b49e58a2cf97d2635fe3ee8c
child 340145 d27bb1f46b5e6ddb8706ebd65db0aad9918f3268
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersManishearth
servo: Merge #14174 - style: Refactor and add infrastructure for font metrics in style (from emilio:font-provider); r=Manishearth <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors <!-- Either: --> - [x] These changes do not require tests because moves stuff around without adding functionality. <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> This commit itself only moves things around and adds an extra parameter to the `apply_declarations` function to eventually handle #14079 correctly. Probably needs a more granular API to query fonts, á la nsFontMetrics, but that's trivial to do once this is landed. Then we should make the font provider mandatory, and implement the missing stylo bits. Source-Repo: https://github.com/servo/servo Source-Revision: 57c4db7c670f34fffbee0c179077e8afdadf09f8
servo/components/style/animation.rs
servo/components/style/font_metrics.rs
servo/components/style/gecko/conversions.rs
servo/components/style/lib.rs
servo/components/style/media_queries.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/components/style/properties/properties.mako.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/position.rs
servo/components/style/viewport.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -398,16 +398,17 @@ fn compute_style_for_animation_step(cont
 
             let computed =
                 properties::apply_declarations(context.viewport_size,
                                                /* is_root = */ false,
                                                iter,
                                                previous_style,
                                                /* cascade_info = */ None,
                                                context.error_reporter.clone(),
+                                               /* Metrics provider */ None,
                                                CascadeFlags::empty());
             computed
         }
     }
 }
 
 pub fn maybe_start_animations(context: &SharedStyleContext,
                               new_animations_sender: &Sender<Animation>,
new file mode 100644
--- /dev/null
+++ b/servo/components/style/font_metrics.rs
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use Atom;
+use app_units::Au;
+use euclid::Size2D;
+use std::fmt;
+
+/// Represents the font metrics that style needs from a font to compute the
+/// value of certain CSS units like `ex`.
+#[derive(Debug, PartialEq, Clone)]
+pub struct FontMetrics {
+    pub x_height: Au,
+    pub zero_advance_measure: Size2D<Au>,
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub enum FontMetricsQueryResult {
+    Available(Option<FontMetrics>),
+    NotAvailable,
+}
+
+/// A trait used to represent something capable of providing us font metrics.
+pub trait FontMetricsProvider: Send + Sync + fmt::Debug {
+    /// Obtain the metrics for given font family.
+    ///
+    /// TODO: We could make this take the full list, I guess, and save a few
+    /// virtual calls.
+    ///
+    /// This is not too common in practice though.
+    fn query(&self, _font_name: &Atom) -> FontMetricsQueryResult {
+        FontMetricsQueryResult::NotAvailable
+    }
+}
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -33,32 +33,32 @@ unsafe impl HasFFI for RwLock<PropertyDe
     type FFIType = RawServoDeclarationBlock;
 }
 unsafe impl HasArcFFI for RwLock<PropertyDeclarationBlock> {}
 
 impl From<CalcLengthOrPercentage> for nsStyleCoord_CalcValue {
     fn from(other: CalcLengthOrPercentage) -> nsStyleCoord_CalcValue {
         let has_percentage = other.percentage.is_some();
         nsStyleCoord_CalcValue {
-            mLength: other.length.map_or(0, |l| l.0),
+            mLength: other.length.0,
             mPercent: other.percentage.unwrap_or(0.0),
             mHasPercent: has_percentage,
         }
     }
 }
 
 impl From<nsStyleCoord_CalcValue> for CalcLengthOrPercentage {
     fn from(other: nsStyleCoord_CalcValue) -> CalcLengthOrPercentage {
         let percentage = if other.mHasPercent {
             Some(other.mPercent)
         } else {
             None
         };
         CalcLengthOrPercentage {
-            length: Some(Au(other.mLength)),
+            length: Au(other.mLength),
             percentage: percentage,
         }
     }
 }
 
 impl From<LengthOrPercentage> for nsStyleCoord_CalcValue {
     fn from(other: LengthOrPercentage) -> nsStyleCoord_CalcValue {
         match other {
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -98,16 +98,17 @@ pub mod cache;
 pub mod cascade_info;
 pub mod context;
 pub mod custom_properties;
 pub mod data;
 pub mod dom;
 pub mod element_state;
 pub mod error_reporting;
 pub mod font_face;
+pub mod font_metrics;
 #[cfg(feature = "gecko")] #[allow(unsafe_code)] pub mod gecko;
 #[cfg(feature = "gecko")] #[allow(unsafe_code)] pub mod gecko_bindings;
 pub mod keyframes;
 pub mod logical_geometry;
 pub mod matching;
 pub mod media_queries;
 pub mod owning_handle;
 pub mod parallel;
--- a/servo/components/style/media_queries.rs
+++ b/servo/components/style/media_queries.rs
@@ -5,20 +5,20 @@
 //! [Media queries][mq].
 //!
 //! [mq]: https://drafts.csswg.org/mediaqueries/
 
 use Atom;
 use app_units::Au;
 use cssparser::{Delimiter, Parser, Token};
 use euclid::size::{Size2D, TypedSize2D};
-use properties::longhands;
 use serialize_comma_separated_list;
 use std::fmt::{self, Write};
 use style_traits::{ToCss, ViewportPx};
+use values::computed::{self, ToComputedValue};
 use values::specified;
 
 
 #[derive(Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct MediaList {
     pub media_queries: Vec<MediaQuery>
 }
@@ -44,38 +44,21 @@ pub enum Range<T> {
     Max(T),
     //Eq(T),    // FIXME: Implement parsing support for equality then re-enable this.
 }
 
 impl Range<specified::Length> {
     fn to_computed_range(&self, viewport_size: Size2D<Au>) -> Range<Au> {
         // http://dev.w3.org/csswg/mediaqueries3/#units
         // em units are relative to the initial font-size.
-        let initial_font_size = longhands::font_size::get_initial_value();
-        let compute_width = |&width| {
-            match width {
-                specified::Length::Absolute(value) => value,
-                specified::Length::FontRelative(value)
-                    => value.to_computed_value(initial_font_size, initial_font_size),
-                specified::Length::ViewportPercentage(value)
-                    => value.to_computed_value(viewport_size),
-                specified::Length::Calc(val, range)
-                    => range.clamp(
-                        val.compute_from_viewport_and_font_size(viewport_size,
-                                                                initial_font_size,
-                                                                initial_font_size)
-                           .length()),
-                specified::Length::ServoCharacterWidth(..)
-                    => unreachable!(),
-            }
-        };
+        let context = computed::Context::initial(viewport_size, false);
 
         match *self {
-            Range::Min(ref width) => Range::Min(compute_width(width)),
-            Range::Max(ref width) => Range::Max(compute_width(width)),
+            Range::Min(ref width) => Range::Min(width.to_computed_value(&context)),
+            Range::Max(ref width) => Range::Max(width.to_computed_value(&context)),
             //Range::Eq(ref width) => Range::Eq(compute_width(width))
         }
     }
 }
 
 impl<T: Ord> Range<T> {
     fn evaluate(&self, value: T) -> bool {
         match *self {
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -911,16 +911,24 @@ fn static_assert() {
                         else if name == &atom!("monospace") { FontFamilyType::eFamily_monospace }
                         else { panic!("Unknown generic font family") };
                     unsafe { Gecko_FontFamilyList_AppendGeneric(list, family_type); }
                 }
             }
         }
     }
 
+    pub fn font_family_count(&self) -> usize {
+        0
+    }
+
+    pub fn font_family_at(&self, _: usize) -> longhands::font_family::computed_value::FontFamily {
+        unimplemented!()
+    }
+
     pub fn copy_font_family_from(&mut self, other: &Self) {
         unsafe { Gecko_CopyFontFamilyFrom(&mut self.gecko.mFont, &other.gecko.mFont); }
     }
 
     // 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) {
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -413,17 +413,17 @@ impl Interpolate for CalcLengthOrPercent
                     let this = this.unwrap_or(T::default());
                     let other = other.unwrap_or(T::default());
                     this.interpolate(&other, progress).map(Some)
                 }
             }
         }
 
         Ok(CalcLengthOrPercentage {
-            length: try!(interpolate_half(self.length, other.length, progress)),
+            length: try!(self.length.interpolate(&other.length, progress)),
             percentage: try!(interpolate_half(self.percentage, other.percentage, progress)),
         })
     }
 }
 
 /// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
 impl Interpolate for LengthOrPercentage {
     #[inline]
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -3,38 +3,39 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 <% from data import Method %>
 
 <% data.new_style_struct("Font",
                          inherited=True,
                          additional_methods=[Method("compute_font_hash", is_mut=True)]) %>
-<%helpers:longhand name="font-family" animatable="False">
+<%helpers:longhand name="font-family" animatable="False" need_index="True">
     use self::computed_value::FontFamily;
     use values::NoViewportPercentage;
     use values::computed::ComputedValueAsSpecified;
     pub use self::computed_value::T as SpecifiedValue;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
     impl NoViewportPercentage for SpecifiedValue {}
 
     pub mod computed_value {
         use std::fmt;
         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 {
             FamilyName(Atom),
             Generic(Atom),
         }
+
         impl FontFamily {
-
             #[inline]
             pub fn atom(&self) -> &Atom {
                 match *self {
                     FontFamily::FamilyName(ref name) => name,
                     FontFamily::Generic(ref name) => name,
                 }
             }
 
@@ -62,32 +63,35 @@
                     "cursive" => return FontFamily::Generic(atom!("cursive")),
                     "fantasy" => return FontFamily::Generic(atom!("fantasy")),
                     "monospace" => return FontFamily::Generic(atom!("monospace")),
                     _ => {}
                 }
                 FontFamily::FamilyName(input)
             }
         }
+
         impl ToCss for FontFamily {
             fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                 self.atom().with_str(|s| dest.write_str(s))
             }
         }
+
         impl ToCss for T {
             fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                 let mut iter = self.0.iter();
                 try!(iter.next().unwrap().to_css(dest));
                 for family in iter {
                     try!(dest.write_str(", "));
                     try!(family.to_css(dest));
                 }
                 Ok(())
             }
         }
+
         #[derive(Debug, Clone, PartialEq, Eq, Hash)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct T(pub Vec<FontFamily>);
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(vec![FontFamily::Generic(atom!("serif"))])
@@ -302,18 +306,17 @@
 
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value(&self, context: &Context) -> computed_value::T {
             match self.0 {
                 LengthOrPercentage::Length(Length::FontRelative(value)) => {
-                    value.to_computed_value(context.inherited_style().get_font().clone_font_size(),
-                                            context.style().root_font_size())
+                    value.to_computed_value(context, /* use inherited */ true)
                 }
                 LengthOrPercentage::Length(Length::ServoCharacterWidth(value)) => {
                     value.to_computed_value(context.inherited_style().get_font().clone_font_size())
                 }
                 LengthOrPercentage::Length(l) => {
                     l.to_computed_value(context)
                 }
                 LengthOrPercentage::Percentage(Percentage(value)) => {
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -20,16 +20,17 @@ use Atom;
 use app_units::Au;
 #[cfg(feature = "servo")] use cssparser::{Color as CSSParserColor, RGBA};
 use cssparser::{Parser, TokenSerializationType};
 use error_reporting::ParseErrorReporter;
 use url::Url;
 #[cfg(feature = "servo")] use euclid::side_offsets::SideOffsets2D;
 use euclid::size::Size2D;
 use computed_values;
+use font_metrics::FontMetricsProvider;
 #[cfg(feature = "servo")] use logical_geometry::{LogicalMargin, PhysicalSide};
 use logical_geometry::WritingMode;
 use parser::{Parse, ParserContext, ParserContextExtraData};
 use style_traits::ToCss;
 use stylesheets::Origin;
 #[cfg(feature = "servo")] use values::Either;
 use values::{HasViewportPercentage, computed};
 use cascade_info::CascadeInfo;
@@ -1459,27 +1460,29 @@ pub fn cascade(viewport_size: Size2D<Au>
         })
     };
     apply_declarations(viewport_size,
                        is_root_element,
                        iter_declarations,
                        inherited_style,
                        cascade_info,
                        error_reporter,
+                       None,
                        flags)
 }
 
 /// NOTE: This function expects the declaration with more priority to appear
 /// first.
 pub fn apply_declarations<'a, F, I>(viewport_size: Size2D<Au>,
                                     is_root_element: bool,
                                     iter_declarations: F,
                                     inherited_style: &ComputedValues,
                                     mut cascade_info: Option<<&mut CascadeInfo>,
                                     mut error_reporter: StdBox<ParseErrorReporter + Send>,
+                                    font_metrics_provider: Option<<&FontMetricsProvider>,
                                     flags: CascadeFlags)
                                     -> ComputedValues
     where F: Fn() -> I, I: Iterator<Item = &'a PropertyDeclaration>
 {
     let inherited_custom_properties = inherited_style.custom_properties();
     let mut custom_properties = None;
     let mut seen_custom = HashSet::new();
     for declaration in iter_declarations() {
@@ -1523,16 +1526,17 @@ pub fn apply_declarations<'a, F, I>(view
                             )
     };
 
     let mut context = computed::Context {
         is_root_element: is_root_element,
         viewport_size: viewport_size,
         inherited_style: inherited_style,
         style: starting_style,
+        font_metrics_provider: font_metrics_provider,
     };
 
     // Set computed values, overwriting earlier declarations for the same
     // property.
     let mut cacheable = true;
     let mut seen = PropertyBitField::new();
 
     // Declaration blocks are stored in increasing precedence order, we want
@@ -1557,16 +1561,17 @@ pub fn apply_declarations<'a, F, I>(view
                 //
                 // We iterate applicable_declarations twice, first cascading
                 // "early" properties then "other".
                 //
                 // Unfortunately, it’s not easy to check that this
                 // classification is correct.
                 let is_early_property = matches!(*declaration,
                     PropertyDeclaration::FontSize(_) |
+                    PropertyDeclaration::FontFamily(_) |
                     PropertyDeclaration::Color(_) |
                     PropertyDeclaration::Position(_) |
                     PropertyDeclaration::Float(_) |
                     PropertyDeclaration::TextDecoration${'' if product == 'servo' else 'Line'}(_) |
                     PropertyDeclaration::WritingMode(_) |
                     PropertyDeclaration::Direction(_) |
                     PropertyDeclaration::TextOrientation(_)
                 );
--- a/servo/components/style/values/computed/length.rs
+++ b/servo/components/style/values/computed/length.rs
@@ -12,104 +12,121 @@ use values::{CSSFloat, Either, None_, sp
 pub use cssparser::Color as CSSColor;
 pub use super::image::{EndingShape as GradientShape, Gradient, GradientKind, Image};
 pub use super::image::{LengthOrKeyword, LengthOrPercentageOrKeyword};
 pub use values::specified::{Angle, BorderStyle, Time, UrlOrNone};
 
 #[derive(Clone, PartialEq, Copy, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct CalcLengthOrPercentage {
-    pub length: Option<Au>,
+    pub length: Au,
     pub percentage: Option<CSSFloat>,
 }
 
 impl CalcLengthOrPercentage {
     #[inline]
     pub fn length(&self) -> Au {
-        self.length.unwrap_or(Au(0))
+        self.length
     }
 
     #[inline]
     pub fn percentage(&self) -> CSSFloat {
         self.percentage.unwrap_or(0.)
     }
 }
 
 impl From<LengthOrPercentage> for CalcLengthOrPercentage {
     fn from(len: LengthOrPercentage) -> CalcLengthOrPercentage {
         match len {
             LengthOrPercentage::Percentage(this) => {
                 CalcLengthOrPercentage {
-                    length: None,
+                    length: Au(0),
                     percentage: Some(this),
                 }
             }
             LengthOrPercentage::Length(this) => {
                 CalcLengthOrPercentage {
-                    length: Some(this),
+                    length: this,
                     percentage: None,
                 }
             }
             LengthOrPercentage::Calc(this) => {
                 this
             }
         }
     }
 }
 
 impl From<LengthOrPercentageOrAuto> for Option<CalcLengthOrPercentage> {
     fn from(len: LengthOrPercentageOrAuto) -> Option<CalcLengthOrPercentage> {
         match len {
             LengthOrPercentageOrAuto::Percentage(this) => {
                 Some(CalcLengthOrPercentage {
-                    length: None,
+                    length: Au(0),
                     percentage: Some(this),
                 })
             }
             LengthOrPercentageOrAuto::Length(this) => {
                 Some(CalcLengthOrPercentage {
-                    length: Some(this),
+                    length: this,
                     percentage: None,
                 })
             }
             LengthOrPercentageOrAuto::Calc(this) => {
                 Some(this)
             }
             LengthOrPercentageOrAuto::Auto => {
                 None
             }
         }
     }
 }
 
 impl ToCss for CalcLengthOrPercentage {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match (self.length, self.percentage) {
-            (None, Some(p)) => write!(dest, "{}%", p * 100.),
-            (Some(l), None) => write!(dest, "{}px", Au::to_px(l)),
-            (Some(l), Some(p)) => write!(dest, "calc({}px + {}%)", Au::to_px(l), p * 100.),
-            _ => unreachable!()
+            (l, Some(p)) if l == Au(0) => write!(dest, "{}%", p * 100.),
+            (l, Some(p)) => write!(dest, "calc({}px + {}%)", Au::to_px(l), p * 100.),
+            (l, None) => write!(dest, "{}px", Au::to_px(l)),
         }
     }
 }
 
 impl ToComputedValue for specified::CalcLengthOrPercentage {
     type ComputedValue = CalcLengthOrPercentage;
 
     fn to_computed_value(&self, context: &Context) -> CalcLengthOrPercentage {
-        self.compute_from_viewport_and_font_size(context.viewport_size(),
-                                                 context.style().get_font().clone_font_size(),
-                                                 context.style().root_font_size())
+        let mut length = Au(0);
+
+        if let Some(absolute) = self.absolute {
+            length += absolute;
+        }
 
+        for val in &[self.vw, self.vh, self.vmin, self.vmax] {
+            if let Some(val) = *val {
+                length += val.to_computed_value(context.viewport_size());
+            }
+        }
+
+        for val in &[self.ch, self.em, self.ex, self.rem] {
+            if let Some(val) = *val {
+                length += val.to_computed_value(context, /* use inherited */ false);
+            }
+        }
+
+        CalcLengthOrPercentage {
+            length: length,
+            percentage: self.percentage.map(|p| p.0),
+        }
     }
 
     #[inline]
     fn from_computed_value(computed: &CalcLengthOrPercentage) -> Self {
         specified::CalcLengthOrPercentage {
-            absolute: computed.length,
+            absolute: Some(computed.length),
             percentage: computed.percentage.map(specified::Percentage),
             ..Default::default()
         }
     }
 }
 
 #[derive(PartialEq, Clone, Copy)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use euclid::size::Size2D;
+use font_metrics::FontMetricsProvider;
 use properties::ComputedValues;
 use std::fmt;
 use style_traits::ToCss;
 use super::{CSSFloat, specified};
 
 pub use cssparser::Color as CSSColor;
 pub use self::image::{EndingShape as GradientShape, Gradient, GradientKind, Image};
 pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword};
@@ -23,27 +24,43 @@ pub mod image;
 pub mod length;
 pub mod position;
 
 pub struct Context<'a> {
     pub is_root_element: bool,
     pub viewport_size: Size2D<Au>,
     pub inherited_style: &'a ComputedValues,
 
-    /// Values access through this need to be in the properties "computed early":
-    /// color, text-decoration, font-size, display, position, float, border-*-style, outline-style
+    /// Values access through this need to be in the properties "computed
+    /// early": color, text-decoration, font-size, display, position, float,
+    /// border-*-style, outline-style, font-family, writing-mode...
     pub style: ComputedValues,
+    pub font_metrics_provider: Option<&'a FontMetricsProvider>,
 }
 
 impl<'a> Context<'a> {
     pub fn is_root_element(&self) -> bool { self.is_root_element }
     pub fn viewport_size(&self) -> Size2D<Au> { self.viewport_size }
     pub fn inherited_style(&self) -> &ComputedValues { &self.inherited_style }
     pub fn style(&self) -> &ComputedValues { &self.style }
     pub fn mutate_style(&mut self) -> &mut ComputedValues { &mut self.style }
+
+    /// Creates a dummy computed context for use in multiple places, like
+    /// evaluating media queries.
+    pub fn initial(viewport_size: Size2D<Au>, is_root_element: bool) -> Self {
+        let initial_style = ComputedValues::initial_values();
+        // FIXME: Enforce a font metrics provider.
+        Context {
+            is_root_element: is_root_element,
+            viewport_size: viewport_size,
+            inherited_style: initial_style,
+            style: initial_style.clone(),
+            font_metrics_provider: None,
+        }
+    }
 }
 
 pub trait ToComputedValue {
     type ComputedValue;
 
     #[inline]
     fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue;
 
@@ -94,18 +111,17 @@ impl ToComputedValue for specified::Leng
     type ComputedValue = Au;
 
     #[inline]
     fn to_computed_value(&self, context: &Context) -> Au {
         match *self {
             specified::Length::Absolute(length) => length,
             specified::Length::Calc(calc, range) => range.clamp(calc.to_computed_value(context).length()),
             specified::Length::FontRelative(length) =>
-                length.to_computed_value(context.style().get_font().clone_font_size(),
-                                         context.style().root_font_size()),
+                length.to_computed_value(context, /* use inherited */ false),
             specified::Length::ViewportPercentage(length) =>
                 length.to_computed_value(context.viewport_size()),
             specified::Length::ServoCharacterWidth(length) =>
                 length.to_computed_value(context.style().get_font().clone_font_size())
         }
     }
 
     #[inline]
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -1,24 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use cssparser::{Parser, Token};
 use euclid::size::Size2D;
+use font_metrics::FontMetrics;
 use parser::Parse;
 use std::ascii::AsciiExt;
 use std::cmp;
 use std::fmt;
 use std::ops::Mul;
 use style_traits::ToCss;
 use style_traits::values::specified::AllowedNumericType;
 use super::{Angle, Number, SimplifiedValueNode, SimplifiedSumNode, Time};
-use values::{CSSFloat, Either, FONT_MEDIUM_PX, HasViewportPercentage, None_, computed};
+use values::{CSSFloat, Either, FONT_MEDIUM_PX, HasViewportPercentage, None_};
+use values::computed::Context;
 
 pub use super::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use super::image::{GradientKind, HorizontalDirection, Image, LengthOrKeyword, LengthOrPercentageOrKeyword};
 pub use super::image::{SizeKeyword, VerticalDirection};
 
 #[derive(Clone, PartialEq, Copy, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum FontRelativeLength {
@@ -35,28 +37,88 @@ impl ToCss for FontRelativeLength {
             FontRelativeLength::Ex(length) => write!(dest, "{}ex", length),
             FontRelativeLength::Ch(length) => write!(dest, "{}ch", length),
             FontRelativeLength::Rem(length) => write!(dest, "{}rem", length)
         }
     }
 }
 
 impl FontRelativeLength {
-    pub fn to_computed_value(&self,
-                             reference_font_size: Au,
-                             root_font_size: Au)
-                             -> Au
-    {
+    pub fn find_first_available_font_metrics(context: &Context) -> Option<FontMetrics> {
+        use font_metrics::FontMetricsQueryResult::*;
+        if let Some(ref metrics_provider) = context.font_metrics_provider {
+            for family in context.style().get_font().font_family_iter() {
+                if let Available(metrics) = metrics_provider.query(family.atom()) {
+                    return metrics;
+                }
+            }
+        }
+
+        None
+    }
+
+    // NB: The use_inherited flag is used to special-case the computation of
+    // font-family.
+    pub fn to_computed_value(&self, context: &Context, use_inherited: bool) -> Au {
+        let reference_font_size = if use_inherited {
+            context.inherited_style().get_font().clone_font_size()
+        } else {
+            context.style().get_font().clone_font_size()
+        };
+
+        let root_font_size = context.style().root_font_size;
         match *self {
             FontRelativeLength::Em(length) => reference_font_size.scale_by(length),
-            FontRelativeLength::Ex(length) | FontRelativeLength::Ch(length) => {
-                // https://github.com/servo/servo/issues/7462
-                let em_factor = 0.5;
-                reference_font_size.scale_by(length * em_factor)
+            FontRelativeLength::Ex(length) => {
+                match Self::find_first_available_font_metrics(context) {
+                    Some(metrics) => metrics.x_height,
+                    // https://drafts.csswg.org/css-values/#ex
+                    //
+                    //     In the cases where it is impossible or impractical to
+                    //     determine the x-height, a value of 0.5em must be
+                    //     assumed.
+                    //
+                    None => reference_font_size.scale_by(0.5 * length),
+                }
             },
+            FontRelativeLength::Ch(length) => {
+                let wm = context.style().writing_mode;
+
+                // TODO(emilio, #14144): Compute this properly once we support
+                // all the relevant writing-mode related properties, this should
+                // be equivalent to "is the text in the block direction?".
+                let vertical = wm.is_vertical();
+
+                match Self::find_first_available_font_metrics(context) {
+                    Some(metrics) => {
+                        if vertical {
+                            metrics.zero_advance_measure.height
+                        } else {
+                            metrics.zero_advance_measure.width
+                        }
+                    }
+                    // https://drafts.csswg.org/css-values/#ch
+                    //
+                    //     In the cases where it is impossible or impractical to
+                    //     determine the measure of the “0” glyph, it must be
+                    //     assumed to be 0.5em wide by 1em tall. Thus, the ch
+                    //     unit falls back to 0.5em in the general case, and to
+                    //     1em when it would be typeset upright (i.e.
+                    //     writing-mode is vertical-rl or vertical-lr and
+                    //     text-orientation is upright).
+                    //
+                    None => {
+                        if vertical {
+                            reference_font_size.scale_by(length)
+                        } else {
+                            reference_font_size.scale_by(0.5 * length)
+                        }
+                    }
+                }
+            }
             FontRelativeLength::Rem(length) => root_font_size.scale_by(length)
         }
     }
 }
 
 #[derive(Clone, PartialEq, Copy, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum ViewportPercentageLength {
@@ -607,48 +669,16 @@ impl CalcLengthOrPercentage {
         }
 
         match (angle, number) {
             (Some(angle), None) => Ok(Angle(angle)),
             (None, Some(value)) if value == 0. => Ok(Angle(0.)),
             _ => Err(())
         }
     }
-
-    pub fn compute_from_viewport_and_font_size(&self,
-                                               viewport_size: Size2D<Au>,
-                                               font_size: Au,
-                                               root_font_size: Au)
-                                               -> computed::CalcLengthOrPercentage
-    {
-        let mut length = None;
-
-        if let Some(absolute) = self.absolute {
-            length = Some(length.unwrap_or(Au(0)) + absolute);
-        }
-
-        for val in &[self.vw, self.vh, self.vmin, self.vmax] {
-            if let Some(val) = *val {
-                length = Some(length.unwrap_or(Au(0)) +
-                    val.to_computed_value(viewport_size));
-            }
-        }
-
-        for val in &[self.ch, self.em, self.ex, self.rem] {
-            if let Some(val) = *val {
-                length = Some(length.unwrap_or(Au(0)) + val.to_computed_value(
-                    font_size, root_font_size));
-            }
-        }
-
-        computed::CalcLengthOrPercentage {
-            length: length,
-            percentage: self.percentage.map(|p| p.0),
-        }
-    }
 }
 
 impl HasViewportPercentage for CalcLengthOrPercentage {
     fn has_viewport_percentage(&self) -> bool {
         self.vw.is_some() || self.vh.is_some() ||
             self.vmin.is_some() || self.vmax.is_some()
     }
 }
--- a/servo/components/style/values/specified/position.rs
+++ b/servo/components/style/values/specified/position.rs
@@ -2,16 +2,17 @@
  * 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/. */
 
 //! CSS handling for the specified value of
 //! [`position`][position]s
 //!
 //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
 
+use app_units::Au;
 use cssparser::{Parser, Token};
 use parser::Parse;
 use std::fmt;
 use style_traits::ToCss;
 use values::HasViewportPercentage;
 use values::computed::{CalcLengthOrPercentage, Context};
 use values::computed::{LengthOrPercentage as ComputedLengthOrPercentage, ToComputedValue};
 use values::computed::position as computed_position;
@@ -285,19 +286,19 @@ impl ToComputedValue for Position {
         let horiz_keyword = self.horiz_keyword.unwrap_or(Keyword::Left);
         let vert_keyword = self.vert_keyword.unwrap_or(Keyword::Top);
 
         // Construct horizontal computed LengthOrPercentage
         let horizontal = match horiz_keyword {
             Keyword::Right => {
                 if let Some(x) = self.horiz_position {
                     let (length, percentage) = match x {
-                        LengthOrPercentage::Percentage(Percentage(y)) => (None, Some(1.0 - y)),
-                        LengthOrPercentage::Length(y) => (Some(-y.to_computed_value(context)), Some(1.0)),
-                        _ => (None, None),
+                        LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)),
+                        LengthOrPercentage::Length(y) => (-y.to_computed_value(context), Some(1.0)),
+                        _ => (Au(0), None),
                     };
                     ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage {
                         length: length,
                         percentage: percentage
                     })
                 } else {
                     ComputedLengthOrPercentage::Percentage(1.0)
                 }
@@ -311,19 +312,19 @@ impl ToComputedValue for Position {
             },
         };
 
         // Construct vertical computed LengthOrPercentage
         let vertical = match vert_keyword {
             Keyword::Bottom => {
                 if let Some(x) = self.vert_position {
                     let (length, percentage) = match x {
-                        LengthOrPercentage::Percentage(Percentage(y)) => (None, Some(1.0 - y)),
-                        LengthOrPercentage::Length(y) => (Some(-y.to_computed_value(context)), Some(1.0)),
-                        _ => (None, None),
+                        LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)),
+                        LengthOrPercentage::Length(y) => (-y.to_computed_value(context), Some(1.0)),
+                        _ => (Au(0), None),
                     };
                     ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage {
                         length: length,
                         percentage: percentage
                     })
                 } else {
                     ComputedLengthOrPercentage::Percentage(1.0)
                 }
--- a/servo/components/style/viewport.rs
+++ b/servo/components/style/viewport.rs
@@ -628,16 +628,17 @@ impl MaybeNew for ViewportConstraints {
                                            Au::from_f32_px(initial_viewport.height));
 
 
         let context = Context {
             is_root_element: false,
             viewport_size: initial_viewport,
             inherited_style: ComputedValues::initial_values(),
             style: ComputedValues::initial_values().clone(),
+            font_metrics_provider: None, // TODO: Should have!
         };
 
         // DEVICE-ADAPT § 9.3 Resolving 'extend-to-zoom'
         let extend_width;
         let extend_height;
         if let Some(extend_zoom) = max!(initial_zoom, max_zoom) {
             let scale_factor = 1. / extend_zoom;
             extend_width = Some(initial_viewport.width.scale_by(scale_factor));
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -143,16 +143,17 @@ pub extern "C" fn Servo_RestyleWithAdded
 
     // FIXME (bug 1303229): Use the actual viewport size here
     let computed = apply_declarations(Size2D::new(Au(0), Au(0)),
                                       /* is_root_element = */ false,
                                       declarations,
                                       previous_style,
                                       None,
                                       Box::new(StdoutErrorReporter),
+                                      None,
                                       CascadeFlags::empty());
     Arc::new(computed).into_strong()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleWorkerThreadCount() -> u32 {
     *NUM_THREADS as u32
 }