Bug 1351200 - Part 4: stylo: Store font metrics provider in thread local style context; r?emilio draft
authorManish Goregaokar <manishearth@gmail.com>
Tue, 04 Apr 2017 11:11:27 -0700
changeset 557491 125c876459666b5cde9a50ed1753ace721519720
parent 557490 7b022f16c9320eebaded1fd15c98c5077e3fae45
child 557492 d15126d2ed219c86705476e5046265a643e1fbf8
push id52748
push userbmo:manishearth@gmail.com
push dateThu, 06 Apr 2017 23:02:15 +0000
reviewersemilio
bugs1351200
milestone55.0a1
Bug 1351200 - Part 4: stylo: Store font metrics provider in thread local style context; r?emilio MozReview-Commit-ID: 7WBduQ6lBTC
servo/components/layout/animation.rs
servo/components/script/layout_wrapper.rs
servo/components/script_layout_interface/wrapper_traits.rs
servo/components/style/animation.rs
servo/components/style/context.rs
servo/components/style/dom.rs
servo/components/style/font_metrics.rs
servo/components/style/gecko/media_queries.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/matching.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/servo/media_queries.rs
servo/components/style/stylist.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/specified/length.rs
servo/components/style/viewport.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/layout/animation.rs
+++ b/servo/components/layout/animation.rs
@@ -8,16 +8,17 @@ use context::LayoutContext;
 use flow::{self, Flow};
 use gfx::display_list::OpaqueNode;
 use ipc_channel::ipc::IpcSender;
 use msg::constellation_msg::PipelineId;
 use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg};
 use std::collections::HashMap;
 use std::sync::mpsc::Receiver;
 use style::animation::{Animation, update_style_for_animation};
+use style::font_metrics::DummyProvider;
 use style::selector_parser::RestyleDamage;
 use style::timer::Timer;
 
 /// Processes any new animations that were discovered after style recalculation.
 /// Also expire any old animations that have completed, inserting them into
 /// `expired_animations`.
 pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>,
                               script_chan: &IpcSender<ConstellationControlMsg>,
@@ -138,17 +139,18 @@ pub fn recalc_style_for_animations(conte
                                                         Vec<Animation>>) {
     let mut damage = RestyleDamage::empty();
     flow.mutate_fragments(&mut |fragment| {
         if let Some(ref animations) = animations.get(&fragment.node) {
             for animation in animations.iter() {
                 let old_style = fragment.style.clone();
                 update_style_for_animation(&context.style_context,
                                            animation,
-                                           &mut fragment.style);
+                                           &mut fragment.style,
+                                           &DummyProvider);
                 damage |= RestyleDamage::compute(&old_style, &fragment.style);
             }
         }
     });
 
     let base = flow::mut_base(flow);
     base.restyle_damage.insert(damage);
     for kid in base.children.iter_mut() {
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -62,16 +62,17 @@ use std::sync::atomic::Ordering;
 use style;
 use style::attr::AttrValue;
 use style::computed_values::display;
 use style::context::{QuirksMode, SharedStyleContext};
 use style::data::ElementData;
 use style::dom::{DescendantsBit, DirtyDescendants, LayoutIterator, NodeInfo, OpaqueNode};
 use style::dom::{PresentationalHintsSynthetizer, TElement, TNode, UnsafeNode};
 use style::element_state::*;
+use style::font_metrics::DummyProvider;
 use style::properties::{ComputedValues, PropertyDeclarationBlock};
 use style::selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl};
 use style::shared_lock::{SharedRwLock as StyleSharedRwLock, Locked as StyleLocked};
 use style::sink::Push;
 use style::str::is_whitespace;
 use style::stylist::ApplicableDeclarationBlock;
 
 #[derive(Copy, Clone)]
@@ -368,16 +369,19 @@ impl<'le> PresentationalHintsSynthetizer
             self.element.synthesize_presentational_hints_for_legacy_attributes(hints);
         }
     }
 }
 
 impl<'le> TElement for ServoLayoutElement<'le> {
     type ConcreteNode = ServoLayoutNode<'le>;
 
+    // We don't support font metrics in Servo yet
+    type FontMetricsProvider = DummyProvider;
+
     fn as_node(&self) -> ServoLayoutNode<'le> {
         ServoLayoutNode::from_layout_js(self.element.upcast())
     }
 
     fn style_attribute(&self) -> Option<&Arc<StyleLocked<PropertyDeclarationBlock>>> {
         unsafe {
             (*self.element.style_attribute()).as_ref()
         }
--- a/servo/components/script_layout_interface/wrapper_traits.rs
+++ b/servo/components/script_layout_interface/wrapper_traits.rs
@@ -16,16 +16,17 @@ use range::Range;
 use servo_url::ServoUrl;
 use std::fmt::Debug;
 use std::sync::Arc;
 use style::computed_values::display;
 use style::context::SharedStyleContext;
 use style::data::ElementData;
 use style::dom::{LayoutIterator, NodeInfo, PresentationalHintsSynthetizer, TNode};
 use style::dom::OpaqueNode;
+use style::font_metrics::DummyProvider;
 use style::properties::{CascadeFlags, ServoComputedValues};
 use style::selector_parser::{PseudoElement, PseudoElementCascadeType, SelectorImpl};
 
 #[derive(Copy, PartialEq, Clone, Debug)]
 pub enum PseudoElementType<T> {
     Normal,
     Before(T),
     After(T),
@@ -406,34 +407,36 @@ pub trait ThreadSafeLayoutElement: Clone
                                 .borrow()
                                 .styles().pseudos.contains_key(&style_pseudo) {
                             let mut data = self.get_style_data().unwrap().borrow_mut();
                             let new_style =
                                 context.stylist.precomputed_values_for_pseudo(
                                     &context.guards,
                                     &style_pseudo,
                                     Some(data.styles().primary.values()),
-                                    CascadeFlags::empty());
+                                    CascadeFlags::empty(),
+                                    &DummyProvider);
                             data.styles_mut().pseudos
                                 .insert(style_pseudo.clone(), new_style);
                         }
                     }
                     PseudoElementCascadeType::Lazy => {
                         if !self.get_style_data()
                                 .unwrap()
                                 .borrow()
                                 .styles().pseudos.contains_key(&style_pseudo) {
                             let mut data = self.get_style_data().unwrap().borrow_mut();
                             let new_style =
                                 context.stylist
                                        .lazily_compute_pseudo_element_style(
                                            &context.guards,
                                            unsafe { &self.unsafe_get() },
                                            &style_pseudo,
-                                           data.styles().primary.values());
+                                           data.styles().primary.values(),
+                                           &DummyProvider);
                             data.styles_mut().pseudos
                                 .insert(style_pseudo.clone(), new_style.unwrap());
                         }
                     }
                 }
 
                 self.get_style_data().unwrap().borrow()
                     .styles().pseudos.get(&style_pseudo)
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -5,16 +5,17 @@
 //! CSS transitions and animations.
 #![deny(missing_docs)]
 
 use Atom;
 use bezier::Bezier;
 use context::SharedStyleContext;
 use dom::{OpaqueNode, UnsafeNode};
 use euclid::point::Point2D;
+use font_metrics::FontMetricsProvider;
 use keyframes::{KeyframesStep, KeyframesStepValue};
 use properties::{self, CascadeFlags, ComputedValues, Importance};
 use properties::animated_properties::{AnimatedProperty, TransitionProperty};
 use properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection;
 use properties::longhands::animation_iteration_count::single_value::computed_value::T as AnimationIterationCount;
 use properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState;
 use properties::longhands::transition_timing_function::single_value::computed_value::StartEnd;
 use properties::longhands::transition_timing_function::single_value::computed_value::T as TransitionTimingFunction;
@@ -405,17 +406,18 @@ pub fn start_transitions_if_applicable(n
     }
 
     had_animations
 }
 
 fn compute_style_for_animation_step(context: &SharedStyleContext,
                                     step: &KeyframesStep,
                                     previous_style: &ComputedValues,
-                                    style_from_cascade: &ComputedValues)
+                                    style_from_cascade: &ComputedValues,
+                                    font_metrics_provider: &FontMetricsProvider)
                                     -> ComputedValues {
     match step.value {
         KeyframesStepValue::ComputedValues => style_from_cascade.clone(),
         KeyframesStepValue::Declarations { block: ref declarations } => {
             let guard = declarations.read_with(context.guards.author);
 
             // No !important in keyframes.
             debug_assert!(guard.declarations().iter()
@@ -428,17 +430,17 @@ fn compute_style_for_animation_step(cont
             let computed =
                 properties::apply_declarations(&context.stylist.device,
                                                /* is_root = */ false,
                                                iter,
                                                previous_style,
                                                previous_style,
                                                /* cascade_info = */ None,
                                                &*context.error_reporter,
-                                               /* Metrics provider */ None,
+                                               font_metrics_provider,
                                                CascadeFlags::empty());
             computed
         }
     }
 }
 
 /// Triggers animations for a given node looking at the animation property
 /// values.
@@ -529,17 +531,18 @@ pub fn update_style_for_animation_frame(
     frame.property_animation.update(Arc::make_mut(&mut new_style), progress);
 
     true
 }
 /// Updates a single animation and associated style based on the current time.
 /// If `damage` is provided, inserts the appropriate restyle damage.
 pub fn update_style_for_animation(context: &SharedStyleContext,
                                   animation: &Animation,
-                                  style: &mut Arc<ComputedValues>) {
+                                  style: &mut Arc<ComputedValues>,
+                                  font_metrics_provider: &FontMetricsProvider) {
     debug!("update_style_for_animation: entering");
     debug_assert!(!animation.is_expired());
 
     match *animation {
         Animation::Transition(_, _, start_time, ref frame, _) => {
             debug!("update_style_for_animation: transition found");
             let now = context.timer.seconds();
             let mut new_style = (*style).clone();
@@ -653,31 +656,33 @@ pub fn update_style_for_animation(contex
                 _ => unreachable!(),
             };
             let relative_progress = (now - last_keyframe_ended_at) / relative_duration;
 
             // TODO: How could we optimise it? Is it such a big deal?
             let from_style = compute_style_for_animation_step(context,
                                                               last_keyframe,
                                                               &**style,
-                                                              &state.cascade_style);
+                                                              &state.cascade_style,
+                                                              font_metrics_provider);
 
             // NB: The spec says that the timing function can be overwritten
             // from the keyframe style.
             let mut timing_function = style.get_box().animation_timing_function_mod(index);
             if last_keyframe.declared_timing_function {
                 // NB: animation_timing_function can never be empty, always has
                 // at least the default value (`ease`).
                 timing_function = from_style.get_box().animation_timing_function_at(0);
             }
 
             let target_style = compute_style_for_animation_step(context,
                                                                 target_keyframe,
                                                                 &from_style,
-                                                                &state.cascade_style);
+                                                                &state.cascade_style,
+                                                                font_metrics_provider);
 
             let mut new_style = (*style).clone();
 
             for transition_property in &animation.properties_changed {
                 debug!("update_style_for_animation: scanning prop {:?} for animation \"{}\"",
                        transition_property, name);
                 match PropertyAnimation::from_transition_property(*transition_property,
                                                                   timing_function,
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -7,16 +7,17 @@
 
 use animation::Animation;
 use app_units::Au;
 use bloom::StyleBloom;
 use data::ElementData;
 use dom::{OpaqueNode, TNode, TElement, SendElement};
 use error_reporting::ParseErrorReporter;
 use euclid::Size2D;
+use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")] use gecko_bindings::structs;
 use matching::StyleSharingCandidateCache;
 use parking_lot::RwLock;
 #[cfg(feature = "gecko")] use selector_parser::PseudoElement;
 use selectors::matching::ElementSelectorFlags;
 use servo_config::opts;
 use shared_lock::StylesheetGuards;
 use std::collections::HashMap;
@@ -272,28 +273,32 @@ pub struct ThreadLocalStyleContext<E: TE
     /// A set of tasks to be run (on the parent thread) in sequential mode after
     /// the rest of the styling is complete. This is useful for infrequently-needed
     /// non-threadsafe operations.
     pub tasks: Vec<SequentialTask<E>>,
     /// Statistics about the traversal.
     pub statistics: TraversalStatistics,
     /// Information related to the current element, non-None during processing.
     current_element_info: Option<CurrentElementInfo>,
+    /// Font metrics
+    font_metrics_provider: E::FontMetricsProvider,
 }
 
+
 impl<E: TElement> ThreadLocalStyleContext<E> {
     /// Creates a new `ThreadLocalStyleContext` from a shared one.
     pub fn new(shared: &SharedStyleContext) -> Self {
         ThreadLocalStyleContext {
             style_sharing_candidate_cache: StyleSharingCandidateCache::new(),
             bloom_filter: StyleBloom::new(),
             new_animations_sender: shared.local_context_creation_data.lock().unwrap().new_animations_sender.clone(),
             tasks: Vec::new(),
             statistics: TraversalStatistics::default(),
             current_element_info: None,
+            font_metrics_provider: E::FontMetricsProvider::create_from(shared),
         }
     }
 
     /// Notes when the style system starts traversing an element.
     pub fn begin_element(&mut self, element: E, data: &ElementData) {
         debug_assert!(self.current_element_info.is_none());
         self.current_element_info = Some(CurrentElementInfo {
             element: element.as_node().opaque(),
@@ -311,16 +316,21 @@ impl<E: TElement> ThreadLocalStyleContex
 
     /// Returns true if the current element being traversed is being styled for
     /// the first time.
     ///
     /// Panics if called while no element is being traversed.
     pub fn is_initial_style(&self) -> bool {
         self.current_element_info.as_ref().unwrap().is_initial_style
     }
+
+    /// Get the contained font metrics provider
+    pub fn font_metrics_provider(&self) -> &FontMetricsProvider {
+        &self.font_metrics_provider
+    }
 }
 
 impl<E: TElement> Drop for ThreadLocalStyleContext<E> {
     fn drop(&mut self) {
         debug_assert!(self.current_element_info.is_none());
 
         // Execute any enqueued sequential tasks.
         debug_assert!(thread_state::get() == thread_state::LAYOUT);
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -7,16 +7,17 @@
 #![allow(unsafe_code)]
 #![deny(missing_docs)]
 
 use {Atom, Namespace, LocalName};
 use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
 #[cfg(feature = "gecko")] use context::UpdateAnimationsTasks;
 use data::ElementData;
 use element_state::ElementState;
+use font_metrics::FontMetricsProvider;
 use properties::{ComputedValues, PropertyDeclarationBlock};
 use selector_parser::{ElementExt, PreExistingComputedValues, PseudoElement};
 use selectors::matching::ElementSelectorFlags;
 use shared_lock::Locked;
 use sink::Push;
 use std::fmt;
 use std::fmt::Debug;
 use std::ops::Deref;
@@ -273,16 +274,22 @@ pub trait PresentationalHintsSynthetizer
 pub struct AnimationRules(pub Option<Arc<Locked<PropertyDeclarationBlock>>>,
                           pub Option<Arc<Locked<PropertyDeclarationBlock>>>);
 
 /// The element trait, the main abstraction the style crate acts over.
 pub trait TElement : PartialEq + Debug + Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer {
     /// The concrete node type.
     type ConcreteNode: TNode<ConcreteElement = Self>;
 
+    /// Type of the font metrics provider
+    ///
+    /// XXXManishearth It would be better to make this a type parameter on
+    /// ThreadLocalStyleContext and StyleContext
+    type FontMetricsProvider: FontMetricsProvider;
+
     /// Get this element as a node.
     fn as_node(&self) -> Self::ConcreteNode;
 
     /// While doing a reflow, the element at the root has no parent, as far as we're
     /// concerned. This method returns `None` at the reflow root.
     fn layout_parent_element(self, reflow_root: OpaqueNode) -> Option<Self> {
         if self.as_node().opaque() == reflow_root {
             None
--- a/servo/components/style/font_metrics.rs
+++ b/servo/components/style/font_metrics.rs
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Access to font metrics from the style system.
 
 #![deny(missing_docs)]
 
 use Atom;
 use app_units::Au;
+use context::SharedStyleContext;
 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 {
     /// The x-height of the font.
@@ -27,18 +28,49 @@ pub enum FontMetricsQueryResult {
     /// The font is available, but we may or may not have found any font metrics
     /// for it.
     Available(Option<FontMetrics>),
     /// The font is not available.
     NotAvailable,
 }
 
 /// A trait used to represent something capable of providing us font metrics.
-pub trait FontMetricsProvider: Send + Sync + fmt::Debug {
+pub trait FontMetricsProvider: Send + 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 in the case we are repeatedly unable to find font metrics?
     /// That is not too common in practice though.
     fn query(&self, _font_name: &Atom) -> FontMetricsQueryResult {
         FontMetricsQueryResult::NotAvailable
     }
+
+    /// Get default size of a given language and generic family
+    fn get_size(&self, _font_name: &Atom, _font_family: u8) -> Au {
+        unimplemented!()
+    }
+
+    /// Construct from a shared style context
+    fn create_from(context: &SharedStyleContext) -> Self where Self: Sized;
 }
+
+#[derive(Debug)]
+/// Dummy font metrics provider, for use by Servo
+/// and in cases where gecko doesn't need font metrics
+pub struct DummyProvider;
+
+impl FontMetricsProvider for DummyProvider {
+    fn create_from(_: &SharedStyleContext) -> Self {
+        DummyProvider
+    }
+}
+
+#[cfg(feature = "gecko")]
+/// Construct a font metrics provider for the current product
+pub fn get_metrics_provider_for_product() -> ::gecko::wrapper::GeckoFontMetricsProvider {
+    ::gecko::wrapper::GeckoFontMetricsProvider::new()
+}
+
+#[cfg(feature = "servo")]
+/// Construct a font metrics provider for the current product
+pub fn get_metrics_provider_for_product() -> DummyProvider {
+    DummyProvider
+}
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.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/. */
 
 //! Gecko's media-query device and expression representation.
 
 use app_units::Au;
 use cssparser::{CssStringWriter, Parser, Token};
 use euclid::Size2D;
+use font_metrics::get_metrics_provider_for_product;
 use gecko_bindings::bindings;
 use gecko_bindings::structs::{nsCSSValue, nsCSSUnit, nsStringBuffer};
 use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
 use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
 use gecko_bindings::structs::RawGeckoPresContextOwned;
 use media_queries::MediaType;
 use properties::ComputedValues;
 use std::ascii::AsciiExt;
@@ -494,27 +495,29 @@ impl Expression {
         use std::cmp::Ordering;
 
         debug_assert!(self.range == nsMediaExpression_Range::eEqual ||
                       self.feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed,
                       "Whoops, wrong range");
 
         let default_values = device.default_computed_values();
 
+        let provider = get_metrics_provider_for_product();
+
         // http://dev.w3.org/csswg/mediaqueries3/#units
         // em units are relative to the initial font-size.
         let context = computed::Context {
             is_root_element: false,
             device: device,
             inherited_style: default_values,
             layout_parent_style: default_values,
             // This cloning business is kind of dumb.... It's because Context
             // insists on having an actual ComputedValues inside itself.
             style: default_values.clone(),
-            font_metrics_provider: None,
+            font_metrics_provider: &provider,
         };
 
         let required_value = match self.value {
             Some(ref v) => v,
             None => {
                 // If there's no value, always match unless it's a zero length
                 // or a zero integer or boolean.
                 return match *actual_value {
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -10,17 +10,17 @@
 //! This really follows the Servo pattern in
 //! `components/script/layout_wrapper.rs`.
 //!
 //! This theoretically should live in its own crate, but now it lives in the
 //! style system it's kind of pointless in the Stylo case, and only Servo forces
 //! the separation between the style system implementation and everything else.
 
 use atomic_refcell::AtomicRefCell;
-use context::UpdateAnimationsTasks;
+use context::{SharedStyleContext, UpdateAnimationsTasks};
 use data::ElementData;
 use dom::{self, AnimationRules, DescendantsBit, LayoutIterator, NodeInfo, TElement, TNode, UnsafeNode};
 use dom::{OpaqueNode, PresentationalHintsSynthetizer};
 use element_state::ElementState;
 use error_reporting::StdoutErrorReporter;
 use gecko::global_style_data::GLOBAL_STYLE_DATA;
 use gecko::selector_parser::{SelectorImpl, NonTSPseudoClass, PseudoElement};
 use gecko::snapshot_helpers;
@@ -55,16 +55,17 @@ use properties::PropertyDeclarationBlock
 use properties::animated_properties::AnimationValueMap;
 use rule_tree::CascadeLevel as ServoCascadeLevel;
 use selector_parser::{ElementExt, Snapshot};
 use selectors::Element;
 use selectors::matching::{ElementSelectorFlags, StyleRelations};
 use selectors::parser::{AttrSelector, NamespaceConstraint};
 use shared_lock::Locked;
 use sink::Push;
+use std::cell::RefCell;
 use std::fmt;
 use std::ptr;
 use std::sync::Arc;
 use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
 use stylesheets::UrlExtraData;
 use stylist::ApplicableDeclarationBlock;
 
 /// A simple wrapper over a non-null Gecko node (`nsINode`) pointer.
@@ -421,18 +422,44 @@ fn get_animation_rule(element: &GeckoEle
         let shared_lock = &GLOBAL_STYLE_DATA.shared_lock;
         Some(Arc::new(shared_lock.wrap(
             PropertyDeclarationBlock::from_animation_value_map(&animation_values.read()))))
     } else {
         None
     }
 }
 
+#[derive(Debug)]
+/// Gecko font metrics provider
+pub struct GeckoFontMetricsProvider {
+    /// Cache of base font sizes for each language
+    ///
+    /// Should have at most 31 elements (the number of language groups). Usually
+    /// will have 1.
+    pub font_size_cache: RefCell<Vec<(Atom, ::gecko_bindings::structs::FontSizePrefs)>>,
+}
+
+impl GeckoFontMetricsProvider {
+    /// Construct
+    pub fn new() -> Self {
+        GeckoFontMetricsProvider {
+            font_size_cache: RefCell::new(Vec::new()),
+        }
+    }
+}
+
+impl ::font_metrics::FontMetricsProvider for GeckoFontMetricsProvider {
+    fn create_from(_: &SharedStyleContext) -> Self {
+        GeckoFontMetricsProvider::new()
+    }
+}
+
 impl<'le> TElement for GeckoElement<'le> {
     type ConcreteNode = GeckoNode<'le>;
+    type FontMetricsProvider = GeckoFontMetricsProvider;
 
     fn as_node(&self) -> Self::ConcreteNode {
         unsafe { GeckoNode(&*(self.0 as *const _ as *const RawGeckoNode)) }
     }
 
     fn style_attribute(&self) -> Option<&Arc<Locked<PropertyDeclarationBlock>>> {
         let declarations = unsafe { Gecko_GetStyleAttrDeclarationBlock(self.0) };
         declarations.map(|s| s.as_arc_opt()).unwrap_or(None)
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -10,16 +10,17 @@
 use {Atom, LocalName};
 use animation::{self, Animation, PropertyAnimation};
 use atomic_refcell::AtomicRefMut;
 use cache::{LRUCache, LRUCacheMutIterator};
 use cascade_info::CascadeInfo;
 use context::{SequentialTask, SharedStyleContext, StyleContext};
 use data::{ComputedStyle, ElementData, ElementStyles, RestyleData};
 use dom::{AnimationRules, SendElement, TElement, TNode};
+use font_metrics::FontMetricsProvider;
 use properties::{CascadeFlags, ComputedValues, SHAREABLE, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade};
 use properties::longhands::display::computed_value as display;
 use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_CSS_ANIMATIONS, RestyleHint};
 use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode};
 use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
 use selectors::MatchAttr;
 use selectors::bloom::BloomFilter;
 use selectors::matching::{ElementSelectorFlags, StyleRelations};
@@ -532,16 +533,17 @@ trait PrivateMatchMethods: TElement {
         let values =
             Arc::new(cascade(&shared_context.stylist.device,
                              rule_node,
                              &shared_context.guards,
                              inherited_values,
                              layout_parent_style,
                              Some(&mut cascade_info),
                              &*shared_context.error_reporter,
+                             context.thread_local.font_metrics_provider(),
                              cascade_flags));
 
         cascade_info.finish(&self.as_node());
         values
     }
 
     fn cascade_internal(&self,
                         context: &StyleContext<Self>,
@@ -680,17 +682,18 @@ trait PrivateMatchMethods: TElement {
                           context: &mut StyleContext<Self>,
                           old_values: &mut Option<Arc<ComputedValues>>,
                           new_values: &mut Arc<ComputedValues>,
                           _pseudo: Option<&PseudoElement>,
                           possibly_expired_animations: &mut Vec<PropertyAnimation>) {
         let shared_context = context.shared;
         if let Some(ref mut old) = *old_values {
             self.update_animations_for_cascade(shared_context, old,
-                                               possibly_expired_animations);
+                                               possibly_expired_animations,
+                                               context.thread_local.font_metrics_provider());
         }
 
         let new_animations_sender = &context.thread_local.new_animations_sender;
         let this_opaque = self.as_node().opaque();
         // Trigger any present animations if necessary.
         animation::maybe_start_animations(&shared_context,
                                           new_animations_sender,
                                           this_opaque, &new_values);
@@ -748,17 +751,18 @@ trait PrivateMatchMethods: TElement {
             let d = self.compute_restyle_damage(&old_values, &new_values, pseudo);
             restyle.damage |= d;
         }
     }
 
     fn update_animations_for_cascade(&self,
                                      context: &SharedStyleContext,
                                      style: &mut Arc<ComputedValues>,
-                                     possibly_expired_animations: &mut Vec<PropertyAnimation>) {
+                                     possibly_expired_animations: &mut Vec<PropertyAnimation>,
+                                     font_metrics: &FontMetricsProvider) {
         // Finish any expired transitions.
         let this_opaque = self.as_node().opaque();
         animation::complete_expired_transitions(this_opaque, style, context);
 
         // Merge any running transitions into the current style, and cancel them.
         let had_running_animations = context.running_animations
                                             .read()
                                             .get(&this_opaque)
@@ -775,17 +779,18 @@ trait PrivateMatchMethods: TElement {
                 // updated by layout, because other restyle due to script might
                 // be triggered by layout before the animation tick.
                 //
                 // See #12171 and the associated PR for an example where this
                 // happened while debugging other release panic.
                 if !running_animation.is_expired() {
                     animation::update_style_for_animation(context,
                                                           running_animation,
-                                                          style);
+                                                          style,
+                                                          font_metrics);
                     if let Animation::Transition(_, _, _, ref frame, _) = *running_animation {
                         possibly_expired_animations.push(frame.property_animation.clone())
                     }
                 }
             }
         }
     }
 
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -1946,16 +1946,17 @@ bitflags! {
 ///
 pub fn cascade(device: &Device,
                rule_node: &StrongRuleNode,
                guards: &StylesheetGuards,
                parent_style: Option<<&ComputedValues>,
                layout_parent_style: Option<<&ComputedValues>,
                cascade_info: Option<<&mut CascadeInfo>,
                error_reporter: &ParseErrorReporter,
+               font_metrics_provider: &FontMetricsProvider,
                flags: CascadeFlags)
                -> ComputedValues {
     debug_assert_eq!(parent_style.is_some(), layout_parent_style.is_some());
     let (is_root_element, inherited_style, layout_parent_style) = match parent_style {
         Some(parent_style) => {
             (false,
              parent_style,
              layout_parent_style.unwrap())
@@ -1990,31 +1991,31 @@ pub fn cascade(device: &Device,
     };
     apply_declarations(device,
                        is_root_element,
                        iter_declarations,
                        inherited_style,
                        layout_parent_style,
                        cascade_info,
                        error_reporter,
-                       None,
+                       font_metrics_provider,
                        flags)
 }
 
 /// NOTE: This function expects the declaration with more priority to appear
 /// first.
 #[allow(unused_mut)] // conditionally compiled code for "position"
 pub fn apply_declarations<'a, F, I>(device: &Device,
                                     is_root_element: bool,
                                     iter_declarations: F,
                                     inherited_style: &ComputedValues,
                                     layout_parent_style: &ComputedValues,
                                     mut cascade_info: Option<<&mut CascadeInfo>,
                                     error_reporter: &ParseErrorReporter,
-                                    font_metrics_provider: Option<<&FontMetricsProvider>,
+                                    font_metrics_provider: &FontMetricsProvider,
                                     flags: CascadeFlags)
                                     -> ComputedValues
     where F: Fn() -> I,
           I: Iterator<Item = &'a PropertyDeclaration>,
 {
     let default_style = device.default_computed_values();
     let inherited_custom_properties = inherited_style.custom_properties();
     let mut custom_properties = None;
--- a/servo/components/style/servo/media_queries.rs
+++ b/servo/components/style/servo/media_queries.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/. */
 
 //! Servo's media-query device and expression representation.
 
 use app_units::Au;
 use cssparser::Parser;
 use euclid::{Size2D, TypedSize2D};
+use font_metrics::DummyProvider;
 use media_queries::MediaType;
 use properties::ComputedValues;
 use std::fmt;
 use style_traits::{CSSPixel, ToCss};
 use style_traits::viewport::ViewportConstraints;
 use values::computed::{self, ToComputedValue};
 use values::specified;
 
@@ -180,17 +181,20 @@ impl Range<specified::Length> {
         let context = computed::Context {
             is_root_element: false,
             device: device,
             inherited_style: default_values,
             layout_parent_style: default_values,
             // This cloning business is kind of dumb.... It's because Context
             // insists on having an actual ComputedValues inside itself.
             style: default_values.clone(),
-            font_metrics_provider: None
+            // Servo doesn't support font metrics
+            // A real provider will be needed here once we do; since
+            // ch units can exist in media queries.
+            font_metrics_provider: &DummyProvider,
         };
 
         match *self {
             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(width.to_computed_value(&context))
         }
     }
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -5,16 +5,17 @@
 //! Selector matching.
 
 #![deny(missing_docs)]
 
 use {Atom, LocalName};
 use data::ComputedStyle;
 use dom::{AnimationRules, PresentationalHintsSynthetizer, TElement};
 use error_reporting::StdoutErrorReporter;
+use font_metrics::FontMetricsProvider;
 use keyframes::KeyframesAnimation;
 use media_queries::Device;
 use pdqsort::sort_by;
 use properties::{self, CascadeFlags, ComputedValues};
 #[cfg(feature = "servo")]
 use properties::INHERIT_ALL;
 use properties::PropertyDeclarationBlock;
 use restyle_hints::{RestyleHint, DependencySet};
@@ -338,17 +339,18 @@ impl Stylist {
     /// If `inherit_all` is true, then all properties are inherited from the
     /// parent; otherwise, non-inherited properties are reset to their initial
     /// values. The flow constructor uses this flag when constructing anonymous
     /// flows.
     pub fn precomputed_values_for_pseudo(&self,
                                          guards: &StylesheetGuards,
                                          pseudo: &PseudoElement,
                                          parent: Option<&Arc<ComputedValues>>,
-                                         cascade_flags: CascadeFlags)
+                                         cascade_flags: CascadeFlags,
+                                         font_metrics: &FontMetricsProvider)
                                          -> ComputedStyle {
         debug_assert!(SelectorImpl::pseudo_element_cascade_type(pseudo).is_precomputed());
 
         let rule_node = match self.precomputed_pseudo_element_decls.get(pseudo) {
             Some(declarations) => {
                 // FIXME(emilio): When we've taken rid of the cascade we can just
                 // use into_iter.
                 self.rule_tree.insert_ordered_rules(
@@ -374,27 +376,30 @@ impl Stylist {
         let computed =
             properties::cascade(&self.device,
                                 &rule_node,
                                 guards,
                                 parent.map(|p| &**p),
                                 parent.map(|p| &**p),
                                 None,
                                 &StdoutErrorReporter,
+                                font_metrics,
                                 cascade_flags);
         ComputedStyle::new(rule_node, Arc::new(computed))
     }
 
     /// Returns the style for an anonymous box of the given type.
     #[cfg(feature = "servo")]
     pub fn style_for_anonymous(&self,
                                guards: &StylesheetGuards,
                                pseudo: &PseudoElement,
                                parent_style: &Arc<ComputedValues>)
                                -> Arc<ComputedValues> {
+        use font_metrics::DummyProvider;
+
         // For most (but not all) pseudo-elements, we inherit all values from the parent.
         let inherit_all = match *pseudo {
             PseudoElement::ServoText |
             PseudoElement::ServoInputText => false,
             PseudoElement::ServoAnonymousBlock |
             PseudoElement::ServoAnonymousTable |
             PseudoElement::ServoAnonymousTableCell |
             PseudoElement::ServoAnonymousTableRow |
@@ -409,32 +414,35 @@ impl Stylist {
             PseudoElement::DetailsContent => {
                 unreachable!("That pseudo doesn't represent an anonymous box!")
             }
         };
         let mut cascade_flags = CascadeFlags::empty();
         if inherit_all {
             cascade_flags.insert(INHERIT_ALL);
         }
-        self.precomputed_values_for_pseudo(guards, &pseudo, Some(parent_style), cascade_flags)
+        self.precomputed_values_for_pseudo(guards, &pseudo, Some(parent_style), cascade_flags,
+                                           // Servo doesn't support metrics yet
+                                           &DummyProvider)
             .values.unwrap()
     }
 
     /// Computes a pseudo-element style lazily during layout.
     ///
     /// This can only be done for a certain set of pseudo-elements, like
     /// :selection.
     ///
     /// Check the documentation on lazy pseudo-elements in
     /// docs/components/style.md
     pub fn lazily_compute_pseudo_element_style<E>(&self,
                                                   guards: &StylesheetGuards,
                                                   element: &E,
                                                   pseudo: &PseudoElement,
-                                                  parent: &Arc<ComputedValues>)
+                                                  parent: &Arc<ComputedValues>,
+                                                  font_metrics: &FontMetricsProvider)
                                                   -> Option<ComputedStyle>
         where E: TElement +
                  fmt::Debug +
                  PresentationalHintsSynthetizer
     {
         debug_assert!(SelectorImpl::pseudo_element_cascade_type(pseudo).is_lazy());
         if self.pseudos_map.get(pseudo).is_none() {
             return None;
@@ -487,16 +495,17 @@ impl Stylist {
         let computed =
             properties::cascade(&self.device,
                                 &rule_node,
                                 guards,
                                 Some(&**parent),
                                 Some(&**parent),
                                 None,
                                 &StdoutErrorReporter,
+                                font_metrics,
                                 CascadeFlags::empty());
 
         Some(ComputedStyle::new(rule_node, Arc::new(computed)))
     }
 
     /// Set a given device, which may change the styles that apply to the
     /// document.
     ///
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -52,19 +52,17 @@ pub struct Context<'a> {
 
     /// 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,
 
     /// A font metrics provider, used to access font metrics to implement
     /// font-relative units.
-    ///
-    /// TODO(emilio): This should be required, see #14079.
-    pub font_metrics_provider: Option<&'a FontMetricsProvider>,
+    pub font_metrics_provider: &'a FontMetricsProvider,
 }
 
 impl<'a> Context<'a> {
     /// Whether the current element is the root element.
     pub fn is_root_element(&self) -> bool { self.is_root_element }
     /// The current viewport size.
     pub fn viewport_size(&self) -> Size2D<Au> { self.device.au_viewport_size() }
     /// The style we're inheriting from.
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -68,21 +68,19 @@ impl ToCss for FontRelativeLength {
     }
 }
 
 impl FontRelativeLength {
     /// Gets the first available font metrics from the current context's
     /// font-family list.
     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;
-                }
+        for family in context.style().get_font().font_family_iter() {
+            if let Available(metrics) = context.font_metrics_provider.query(family.atom()) {
+                return metrics;
             }
         }
 
         None
     }
 
     /// Computes the font-relative length. We use the use_inherited flag to
     /// special-case the computation of font-size.
--- a/servo/components/style/viewport.rs
+++ b/servo/components/style/viewport.rs
@@ -8,16 +8,17 @@
 //! [meta]: https://drafts.csswg.org/css-device-adapt/#viewport-meta
 
 #![deny(missing_docs)]
 
 use app_units::Au;
 use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser, parse_important};
 use cssparser::ToCss as ParserToCss;
 use euclid::size::TypedSize2D;
+use font_metrics::get_metrics_provider_for_product;
 use media_queries::Device;
 use parser::{ParserContext, log_css_error};
 use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::fmt;
 use std::iter::Enumerate;
 use std::str::Chars;
@@ -682,24 +683,26 @@ impl MaybeNew for ViewportConstraints {
         }
 
         // DEVICE-ADAPT § 6.2.3 Resolve non-auto lengths to pixel lengths
         //
         // Note: DEVICE-ADAPT § 5. states that relative length values are
         // resolved against initial values
         let initial_viewport = device.au_viewport_size();
 
+        let provider = get_metrics_provider_for_product();
+
         // TODO(emilio): Stop cloning `ComputedValues` around!
         let context = Context {
             is_root_element: false,
             device: device,
             inherited_style: device.default_computed_values(),
             layout_parent_style: device.default_computed_values(),
             style: device.default_computed_values().clone(),
-            font_metrics_provider: None, // TODO: Should have!
+            font_metrics_provider: &provider,
         };
 
         // 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
@@ -15,16 +15,17 @@ use std::ptr;
 use std::sync::{Arc, Mutex};
 use style::arc_ptr_eq;
 use style::context::{QuirksMode, SharedStyleContext, StyleContext};
 use style::context::{ThreadLocalStyleContext, ThreadLocalStyleContextCreationInfo};
 use style::data::{ElementData, ElementStyles, RestyleData};
 use style::dom::{AnimationOnlyDirtyDescendants, DirtyDescendants};
 use style::dom::{ShowSubtreeData, TElement, TNode};
 use style::error_reporting::StdoutErrorReporter;
+use style::font_metrics::get_metrics_provider_for_product;
 use style::gecko::data::{PerDocumentStyleData, PerDocumentStyleDataImpl};
 use style::gecko::global_style_data::GLOBAL_STYLE_DATA;
 use style::gecko::restyle_damage::GeckoRestyleDamage;
 use style::gecko::selector_parser::{SelectorImpl, PseudoElement};
 use style::gecko::traversal::RecalcStyleOnly;
 use style::gecko::wrapper::GeckoElement;
 use style::gecko_bindings::bindings;
 use style::gecko_bindings::bindings::{RawGeckoKeyframeListBorrowed, RawGeckoKeyframeListBorrowedMut};
@@ -686,18 +687,19 @@ pub extern "C" fn Servo_ComputedValues_G
     let pseudo = PseudoElement::from_atom_unchecked(atom, /* anon_box = */ true);
 
 
     let maybe_parent = ComputedValues::arc_from_borrowed(&parent_style_or_null);
     let mut cascade_flags = CascadeFlags::empty();
     if skip_display_fixup {
         cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP);
     }
+    let metrics = get_metrics_provider_for_product();
     data.stylist.precomputed_values_for_pseudo(&guards, &pseudo, maybe_parent,
-                                               cascade_flags)
+                                               cascade_flags, &metrics)
         .values.unwrap()
         .into_strong()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ResolvePseudoStyle(element: RawGeckoElementBorrowed,
                                            pseudo_tag: *mut nsIAtom, is_probe: bool,
                                            raw_data: RawServoStyleSetBorrowed)
@@ -733,20 +735,22 @@ fn get_pseudo_style(guard: &SharedRwLock
     let pseudo = PseudoElement::from_atom_unchecked(Atom::from(pseudo_tag), false);
     match SelectorImpl::pseudo_element_cascade_type(&pseudo) {
         PseudoElementCascadeType::Eager => styles.pseudos.get(&pseudo).map(|s| s.values().clone()),
         PseudoElementCascadeType::Precomputed => unreachable!("No anonymous boxes"),
         PseudoElementCascadeType::Lazy => {
             let d = doc_data.borrow_mut();
             let base = styles.primary.values();
             let guards = StylesheetGuards::same(guard);
+            let metrics = get_metrics_provider_for_product();
             d.stylist.lazily_compute_pseudo_element_style(&guards,
                                                           &element,
                                                           &pseudo,
-                                                          base)
+                                                          base,
+                                                          &metrics)
                      .map(|s| s.values().clone())
         },
     }
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ComputedValues_Inherit(
   raw_data: RawServoStyleSetBorrowed,
@@ -1546,24 +1550,25 @@ pub extern "C" fn Servo_GetComputedKeyfr
 
 
     let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
 
     let style = ComputedValues::as_arc(&style);
     let parent_style = parent_style.as_ref().map(|r| &**ComputedValues::as_arc(&r));
 
     let default_values = data.default_computed_values();
+    let metrics = get_metrics_provider_for_product();
 
     let context = Context {
         is_root_element: false,
         device: &data.stylist.device,
         inherited_style: parent_style.unwrap_or(default_values),
         layout_parent_style: parent_style.unwrap_or(default_values),
         style: (**style).clone(),
-        font_metrics_provider: None,
+        font_metrics_provider: &metrics,
     };
 
     for (index, keyframe) in keyframes.iter().enumerate() {
         let ref mut animation_values = computed_keyframes[index];
 
         let mut seen = LonghandIdSet::new();
 
         // mServoDeclarationBlock is null in the case where we have an invalid css property.