Bug 1331213: Bootstrap a Gecko-side Device, and track it's dirtiness manually in the per-doc data. r?heycam draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sat, 14 Jan 2017 23:34:49 +0100
changeset 461258 13e90965d90698897325de473b3680870b704274
parent 461257 2b7fb9d36d2290486ea49ab9feeda0777ee5b0eb
child 461259 f21fc9b92c2ef7bb92868a63ceb0e35363ed4ce2
push id41620
push userbmo:emilio+bugs@crisal.io
push dateMon, 16 Jan 2017 09:15:13 +0000
reviewersheycam
bugs1331213
milestone53.0a1
Bug 1331213: Bootstrap a Gecko-side Device, and track it's dirtiness manually in the per-doc data. r?heycam The setup is quite different to Servo-land, so add a comment about the different setup. Also, check viewport rules when flushing stylesheets. I believe that the previous behavior is plain wrong, though I haven't taken the time to come up with a test case. In any case, it doesn't hurt any of both back-ends. MozReview-Commit-ID: 46gtTkesOsr Signed-off-by: Emilio Cobos Álvarez <emilio@crisal.io>
layout/base/ServoRestyleManager.cpp
layout/style/ServoBindingList.h
layout/style/ServoBindingTypes.h
layout/style/ServoStyleSet.cpp
layout/style/ServoStyleSet.h
servo/components/style/build_gecko.rs
servo/components/style/gecko/data.rs
servo/components/style/gecko/media_queries.rs
servo/components/style/gecko/mod.rs
servo/components/style/gecko_bindings/bindings.rs
servo/components/style/gecko_bindings/structs_debug.rs
servo/components/style/gecko_bindings/structs_release.rs
servo/components/style/media_queries.rs
servo/components/style/servo/media_queries.rs
servo/components/style/stylist.rs
servo/ports/geckolib/glue.rs
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -83,29 +83,19 @@ ServoRestyleManager::PostRestyleEventFor
 {
   PostRestyleEventInternal(true);
 }
 
 void
 ServoRestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
                                          nsRestyleHint aRestyleHint)
 {
-  NS_WARNING("stylo: ServoRestyleManager::RebuildAllStyleData not implemented");
-  // That said, we do know that rebuilding all style data in Gecko would get rid
-  // of the old ruletree, and hence of the cached-on-the-root default computed
-  // styles.  So we know we need to clear them here.  I think this is the only
-  // way they could get cleared, in fact, though not _all_ calls that come
-  // through here may need to clear them in practice.
-  //
-  // We probably need to do some actual restyling here too, though.  And figure
-  // out whether it actually matters that we may be recomputing the default
-  // styles in too many cases.  For one thing, we do a bunch of eager work here,
-  // whereas we should really just set a bit that says to recompute the default
-  // computed styles before the next time we restyle anything!
-  StyleSet()->RecomputeDefaultComputedStyles();
+  // TODO(emilio, bz): We probably need to do some actual restyling here too.
+  NS_WARNING("stylo: ServoRestyleManager::RebuildAllStyleData is incomplete");
+  StyleSet()->RebuildData();
 }
 
 void
 ServoRestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
                                                   nsRestyleHint aRestyleHint)
 {
   NS_WARNING("stylo: ServoRestyleManager::PostRebuildAllStyleDataEvent not implemented");
 }
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -45,20 +45,19 @@ SERVO_BINDING_FUNC(Servo_StyleSheet_Clea
                    const nsACString* data,
                    ThreadSafeURIHolder* base,
                    ThreadSafeURIHolder* referrer,
                    ThreadSafePrincipalHolder* principal)
 SERVO_BINDING_FUNC(Servo_StyleSheet_HasRules, bool,
                    RawServoStyleSheetBorrowed sheet)
 SERVO_BINDING_FUNC(Servo_StyleSheet_GetRules, ServoCssRulesStrong,
                    RawServoStyleSheetBorrowed sheet)
-SERVO_BINDING_FUNC(Servo_StyleSet_Init, RawServoStyleSetOwned, RawGeckoPresContextBorrowed pres_context)
-SERVO_BINDING_FUNC(Servo_StyleSet_RecomputeDefaultStyles, void,
-                   RawServoStyleSetBorrowed set,
-                   RawGeckoPresContextBorrowed pres_context)
+SERVO_BINDING_FUNC(Servo_StyleSet_Init, RawServoStyleSetOwned, RawGeckoPresContextOwned pres_context)
+SERVO_BINDING_FUNC(Servo_StyleSet_RebuildData, void,
+                   RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_StyleSet_Drop, void, RawServoStyleSetOwned set)
 SERVO_BINDING_FUNC(Servo_StyleSet_AppendStyleSheet, void,
                    RawServoStyleSetBorrowed set, RawServoStyleSheetBorrowed sheet, bool flush)
 SERVO_BINDING_FUNC(Servo_StyleSet_PrependStyleSheet, void,
                    RawServoStyleSetBorrowed set, RawServoStyleSheetBorrowed sheet, bool flush)
 SERVO_BINDING_FUNC(Servo_StyleSet_RemoveStyleSheet, void,
                    RawServoStyleSetBorrowed set, RawServoStyleSheetBorrowed sheet, bool flush)
 SERVO_BINDING_FUNC(Servo_StyleSet_InsertStyleSheetBefore, void,
--- a/layout/style/ServoBindingTypes.h
+++ b/layout/style/ServoBindingTypes.h
@@ -97,16 +97,17 @@ DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawG
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoElement)
 DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoElement)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoDocument)
 DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoDocument)
 DECL_BORROWED_MUT_REF_TYPE_FOR(StyleChildrenIterator)
 DECL_BORROWED_MUT_REF_TYPE_FOR(ServoElementSnapshot)
 DECL_BORROWED_REF_TYPE_FOR(nsCSSValue)
 DECL_BORROWED_MUT_REF_TYPE_FOR(nsCSSValue)
+DECL_OWNED_REF_TYPE_FOR(RawGeckoPresContext)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoPresContext)
 
 #undef DECL_ARC_REF_TYPE_FOR
 #undef DECL_OWNED_REF_TYPE_FOR
 #undef DECL_NULLABLE_OWNED_REF_TYPE_FOR
 #undef DECL_BORROWED_REF_TYPE_FOR
 #undef DECL_NULLABLE_BORROWED_REF_TYPE_FOR
 #undef DECL_BORROWED_MUT_REF_TYPE_FOR
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -544,19 +544,19 @@ ServoStyleSet::AssertTreeIsClean()
   DocumentStyleRootIterator iter(mPresContext->Document());
   while (Element* root = iter.GetNextStyleRoot()) {
     Servo_AssertTreeIsClean(root);
   }
 }
 #endif
 
 void
-ServoStyleSet::RecomputeDefaultComputedStyles()
+ServoStyleSet::RebuildData()
 {
-  Servo_StyleSet_RecomputeDefaultStyles(mRawSet.get(), mPresContext);
+  Servo_StyleSet_RebuildData(mRawSet.get());
 }
 
 ServoComputedValuesStrong
 ServoStyleSet::RestyleWithAddedDeclaration(RawServoDeclarationBlock* aDeclarations,
                                            const ServoComputedValues* aPreviousStyle)
 {
   return Servo_RestyleWithAddedDeclaration(mRawSet.get(), aDeclarations,
                                            aPreviousStyle);
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -156,20 +156,20 @@ public:
 
 #ifdef DEBUG
   void AssertTreeIsClean();
 #else
   void AssertTreeIsClean() {}
 #endif
 
   /**
-   * Recompute our default computed styles.  This will eagerly create a new set
-   * of default computed style structs.
+   * Rebuild the style data. This will force a stylesheet flush, and also
+   * recompute the default computed styles.
    */
-  void RecomputeDefaultComputedStyles();
+  void RebuildData();
 
   /**
    * Resolve style for the given element, and return it as a
    * ServoComputedValues, not an nsStyleContext.
    */
   already_AddRefed<ServoComputedValues> ResolveServoStyle(dom::Element* aElement);
 
   /**
--- a/servo/components/style/build_gecko.rs
+++ b/servo/components/style/build_gecko.rs
@@ -479,16 +479,17 @@ mod bindings {
             .raw_line("type nsAString_internal = nsAString;")
             .whitelisted_function("Servo_.*")
             .whitelisted_function("Gecko_.*");
         let structs_types = [
             "RawGeckoDocument",
             "RawGeckoElement",
             "RawGeckoNode",
             "RawGeckoPresContext",
+            "RawGeckoPresContextOwned",
             "ThreadSafeURIHolder",
             "ThreadSafePrincipalHolder",
             "CSSPseudoClassType",
             "TraversalRootBehavior",
             "FontFamilyList",
             "FontFamilyType",
             "ServoElementSnapshot",
             "SheetParsingMode",
--- a/servo/components/style/gecko/data.rs
+++ b/servo/components/style/gecko/data.rs
@@ -2,46 +2,47 @@
  * 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/. */
 
 //! Data needed to style a Gecko document.
 
 use animation::Animation;
 use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
 use dom::OpaqueNode;
-use euclid::size::TypedSize2D;
-use gecko_bindings::bindings::RawGeckoPresContextBorrowed;
 use gecko_bindings::bindings::RawServoStyleSet;
+use gecko_bindings::structs::RawGeckoPresContextOwned;
 use gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI};
-use media_queries::{Device, MediaType};
+use media_queries::Device;
 use num_cpus;
 use parking_lot::RwLock;
 use properties::ComputedValues;
 use rayon;
 use std::cmp;
 use std::collections::HashMap;
 use std::env;
 use std::sync::Arc;
 use std::sync::mpsc::{Receiver, Sender, channel};
-use style_traits::ViewportPx;
 use stylesheets::Stylesheet;
 use stylist::Stylist;
 
 /// The container for data that a Servo-backed Gecko document needs to style
 /// itself.
 pub struct PerDocumentStyleDataImpl {
     /// Rule processor.
     pub stylist: Arc<Stylist>,
 
     /// List of stylesheets, mirrored from Gecko.
     pub stylesheets: Vec<Arc<Stylesheet>>,
 
     /// Whether the stylesheets list above has changed since the last restyle.
     pub stylesheets_changed: bool,
 
+    /// Whether the device has changed since the last restyle.
+    pub device_changed: bool,
+
     // FIXME(bholley): Hook these up to something.
     /// Unused. Will go away when we actually implement transitions and
     /// animations properly.
     pub new_animations_sender: Sender<Animation>,
     /// Unused. Will go away when we actually implement transitions and
     /// animations properly.
     pub new_animations_receiver: Receiver<Animation>,
     /// Unused. Will go away when we actually implement transitions and
@@ -52,19 +53,16 @@ pub struct PerDocumentStyleDataImpl {
     pub expired_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
 
     /// The worker thread pool.
     /// FIXME(bholley): This shouldn't be per-document.
     pub work_queue: Option<rayon::ThreadPool>,
 
     /// The number of threads of the work queue.
     pub num_threads: usize,
-
-    /// Default computed values for this document.
-    pub default_computed_values: Arc<ComputedValues>
 }
 
 /// The data itself is an `AtomicRefCell`, which guarantees the proper semantics
 /// and unexpected races while trying to mutate it.
 pub struct PerDocumentStyleData(AtomicRefCell<PerDocumentStyleDataImpl>);
 
 lazy_static! {
     /// The number of layout threads, computed statically.
@@ -73,71 +71,80 @@ lazy_static! {
             Ok(num) => num,
             _ => cmp::max(num_cpus::get() * 3 / 4, 1),
         }
     };
 }
 
 impl PerDocumentStyleData {
     /// Create a dummy `PerDocumentStyleData`.
-    pub fn new(pres_context: RawGeckoPresContextBorrowed) -> Self {
-        // FIXME(bholley): Real window size.
-        let window_size: TypedSize2D<f32, ViewportPx> = TypedSize2D::new(800.0, 600.0);
-        let default_computed_values = ComputedValues::default_values(pres_context);
-
-        // FIXME(bz): We're going to need to either update the computed values
-        // in the Stylist's Device or give the Stylist a new Device when our
-        // default_computed_values changes.
-        let device = Device::new(MediaType::Screen, window_size, &default_computed_values);
+    pub fn new(pres_context: RawGeckoPresContextOwned) -> Self {
+        let device = Device::new(pres_context);
 
         let (new_anims_sender, new_anims_receiver) = channel();
 
         PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl {
             stylist: Arc::new(Stylist::new(device)),
             stylesheets: vec![],
             stylesheets_changed: true,
+            device_changed: true,
             new_animations_sender: new_anims_sender,
             new_animations_receiver: new_anims_receiver,
             running_animations: Arc::new(RwLock::new(HashMap::new())),
             expired_animations: Arc::new(RwLock::new(HashMap::new())),
             work_queue: if *NUM_THREADS <= 1 {
                 None
             } else {
                 let configuration =
                     rayon::Configuration::new().set_num_threads(*NUM_THREADS);
                 rayon::ThreadPool::new(configuration).ok()
             },
             num_threads: *NUM_THREADS,
-            default_computed_values: default_computed_values,
         }))
     }
 
     /// Get an immutable reference to this style data.
     pub fn borrow(&self) -> AtomicRef<PerDocumentStyleDataImpl> {
         self.0.borrow()
     }
 
     /// Get an mutable reference to this style data.
     pub fn borrow_mut(&self) -> AtomicRefMut<PerDocumentStyleDataImpl> {
         self.0.borrow_mut()
     }
+
 }
 
 impl PerDocumentStyleDataImpl {
     /// Recreate the style data if the stylesheets have changed.
     pub fn flush_stylesheets(&mut self) {
-        // The stylist wants to be flushed if either the stylesheets change or the
-        // device dimensions change. When we add support for media queries, we'll
-        // need to detect the latter case and trigger a flush as well.
+        let mut stylist = if self.device_changed || self.stylesheets_changed {
+            Some(Arc::get_mut(&mut self.stylist).unwrap())
+        } else {
+            None
+        };
+
+        if self.device_changed {
+            Arc::get_mut(&mut stylist.as_mut().unwrap().device).unwrap().reset();
+            self.device_changed = false;
+            // Force a stylesheet flush if the device has changed.
+            self.stylesheets_changed = true;
+        }
+
         if self.stylesheets_changed {
-            let _ = Arc::get_mut(&mut self.stylist).unwrap()
-                                                   .update(&self.stylesheets, None, true);
+            let _ = stylist.unwrap().update(&self.stylesheets, None, true);
             self.stylesheets_changed = false;
         }
     }
+
+    /// Get the default computed values for this document.
+    pub fn default_computed_values(&self) -> &Arc<ComputedValues> {
+        debug_assert!(!self.device_changed, "A device flush was pending");
+        self.stylist.device.default_values_arc()
+    }
 }
 
 unsafe impl HasFFI for PerDocumentStyleData {
     type FFIType = RawServoStyleSet;
 }
 unsafe impl HasSimpleFFI for PerDocumentStyleData {}
 unsafe impl HasBoxFFI for PerDocumentStyleData {}
 
new file mode 100644
--- /dev/null
+++ b/servo/components/style/gecko/media_queries.rs
@@ -0,0 +1,358 @@
+/* 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/. */
+
+//! Gecko's media-query device and expression representation.
+
+use app_units::Au;
+use cssparser::{Parser, Token};
+use euclid::Size2D;
+use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
+use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
+use gecko_bindings::structs::RawGeckoPresContextOwned;
+use gecko_bindings::bindings;
+use media_queries::MediaType;
+use properties::ComputedValues;
+use std::ascii::AsciiExt;
+use std::fmt;
+use std::sync::Arc;
+use string_cache::Atom;
+use style_traits::ToCss;
+use style_traits::viewport::ViewportConstraints;
+use values::{CSSFloat, specified};
+
+/// The `Device` in Gecko wraps a pres context, has a default values computed,
+/// and contains all the viewport rule state.
+pub struct Device {
+    /// NB: The pres context lifetime is tied to the styleset, who owns the
+    /// stylist, and thus the `Device`, so having a raw pres context pointer
+    /// here is fine.
+    pres_context: RawGeckoPresContextOwned,
+    default_values: Arc<ComputedValues>,
+    viewport_override: Option<ViewportConstraints>,
+}
+
+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 }),
+            viewport_override: None,
+        }
+    }
+
+    /// Tells the device that a new viewport rule has been found, and stores the
+    /// relevant viewport constraints.
+    pub fn account_for_viewport_rule(&mut self,
+                                     constraints: &ViewportConstraints) {
+        self.viewport_override = Some(constraints.clone());
+    }
+
+    /// Returns the default computed values as a reference, in order to match
+    /// Servo.
+    pub fn default_values(&self) -> &ComputedValues {
+        &*self.default_values
+    }
+
+    /// Returns the default computed values as an `Arc`, in order to avoid
+    /// clones.
+    pub fn default_values_arc(&self) -> &Arc<ComputedValues> {
+        &self.default_values
+    }
+
+    /// Recreates all the temporary state that the `Device` stores.
+    ///
+    /// This includes the viewport override from `@viewport` rules, and also the
+    /// default computed values.
+    pub fn reset(&mut self) {
+        // NB: A following stylesheet flush will populate this if appropriate.
+        self.viewport_override = None;
+        self.default_values = ComputedValues::default_values(unsafe { &*self.pres_context });
+    }
+
+    /// Returns the current media type of the device.
+    pub fn media_type(&self) -> MediaType {
+        // TODO
+        MediaType::Screen
+    }
+
+    /// Returns the current viewport size in app units.
+    pub fn au_viewport_size(&self) -> Size2D<Au> {
+        self.viewport_override.as_ref().map(|v| {
+            Size2D::new(Au::from_f32_px(v.size.width),
+                        Au::from_f32_px(v.size.height))
+        }).unwrap_or_else(|| {
+            // TODO(emilio): Grab from pres context.
+            Size2D::new(Au(0), Au(0))
+        })
+    }
+}
+
+unsafe impl Sync for Device {}
+unsafe impl Send for Device {}
+
+/// A expression for gecko contains a reference to the media feature, the value
+/// the media query contained, and the range to evaluate.
+#[derive(Debug, Clone)]
+pub struct Expression {
+    feature: &'static nsMediaFeature,
+    value: MediaExpressionValue,
+    range: nsMediaExpression_Range
+}
+
+impl ToCss for Expression {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+        where W: fmt::Write,
+    {
+        dest.write_str("(")?;
+        match self.range {
+            nsMediaExpression_Range::eMin => dest.write_str("min-")?,
+            nsMediaExpression_Range::eMax => dest.write_str("max-")?,
+            nsMediaExpression_Range::eEqual => {},
+        }
+        // NB: CSSStringWriter not needed, features are under control.
+        write!(dest, "{}", Atom::from(unsafe { *self.feature.mName }))?;
+        dest.write_str(": ")?;
+
+        self.value.to_css(dest)?;
+        dest.write_str(")")
+    }
+}
+
+/// A resolution.
+#[derive(Debug, Clone)]
+pub enum Resolution {
+    /// Dots per inch.
+    Dpi(CSSFloat),
+    /// Dots per pixel.
+    Dppx(CSSFloat),
+    /// Dots per centimeter.
+    Dpcm(CSSFloat),
+}
+
+impl Resolution {
+    fn parse(input: &mut Parser) -> Result<Self, ()> {
+        let (value, unit) = match try!(input.next()) {
+            Token::Dimension(value, unit) => {
+                (value.value, unit)
+            },
+            _ => return Err(()),
+        };
+
+        Ok(match_ignore_ascii_case! { unit,
+            "dpi" => Resolution::Dpi(value),
+            "dppx" => Resolution::Dppx(value),
+            "dpcm" => Resolution::Dpcm(value),
+            _ => return Err(())
+        })
+    }
+}
+
+impl ToCss for Resolution {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+        where W: fmt::Write,
+    {
+        match *self {
+            Resolution::Dpi(v) => write!(dest, "{}dpi", v),
+            Resolution::Dppx(v) => write!(dest, "{}dppx", v),
+            Resolution::Dpcm(v) => write!(dest, "{}dpcm", v),
+        }
+    }
+}
+
+/// A value found or expected in a media expression.
+#[derive(Debug, Clone)]
+pub enum MediaExpressionValue {
+    /// A length.
+    Length(specified::Length),
+    /// A (non-negative) integer.
+    Integer(u32),
+    /// A floating point value.
+    Float(CSSFloat),
+    /// A boolean value, specified as an integer (i.e., either 0 or 1).
+    BoolInteger(bool),
+    /// Two integers separated by '/', with optional whitespace on either side
+    /// of the '/'.
+    IntRatio(u32, u32),
+    /// A resolution.
+    Resolution(Resolution),
+    /// An enumerated index into the variant keyword table.
+    Enumerated(u32),
+    /// An identifier.
+    Ident(Atom),
+}
+
+impl ToCss for MediaExpressionValue {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+        where W: fmt::Write,
+    {
+        match *self {
+            MediaExpressionValue::Length(ref l) => l.to_css(dest),
+            MediaExpressionValue::Integer(v) => write!(dest, "{}", v),
+            MediaExpressionValue::Float(v) => write!(dest, "{}", v),
+            MediaExpressionValue::BoolInteger(v) => {
+                dest.write_str(if v { "1" } else { "0" })
+            },
+            MediaExpressionValue::IntRatio(a, b) => {
+                write!(dest, "{}/{}", a, b)
+            },
+            MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
+            MediaExpressionValue::Enumerated(..) |
+            MediaExpressionValue::Ident(..) => {
+                // TODO(emilio)
+                unimplemented!()
+            }
+        }
+    }
+}
+
+fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool {
+    string.len() > prefix.len() &&
+      string[0..prefix.len()].eq_ignore_ascii_case(prefix)
+}
+
+#[allow(warnings)]
+fn find_feature<F>(mut f: F) -> Option<&'static nsMediaFeature>
+    where F: FnMut(&'static nsMediaFeature) -> bool,
+{
+    // FIXME(emilio): With build-time bindgen, we would be able to use
+    // structs::nsMediaFeatures_features. That would unfortunately break MSVC
+    // builds, or require one bindings file per platform.
+    //
+    // I'm not into any of those, so meanwhile let's use a FFI function.
+    unsafe {
+        let mut features = bindings::Gecko_GetMediaFeatures();
+        while !(*features).mName.is_null() {
+            if f(&*features) {
+                return Some(&*features);
+            }
+            features = features.offset(1);
+        }
+    }
+
+    None
+}
+
+impl Expression {
+    /// Trivially construct a new expression.
+    fn new(feature: &'static nsMediaFeature,
+           value: MediaExpressionValue,
+           range: nsMediaExpression_Range) -> Self {
+        Expression {
+            feature: feature,
+            value: value,
+            range: range,
+        }
+    }
+
+    /// Parse a media expression of the form:
+    ///
+    /// ```
+    /// (media-feature: media-value)
+    /// ```
+    #[allow(warnings)]
+    pub fn parse(input: &mut Parser) -> Result<Self, ()> {
+        try!(input.expect_parenthesis_block());
+        input.parse_nested_block(|input| {
+            let ident = try!(input.expect_ident());
+            try!(input.expect_colon());
+
+            let mut flags = 0;
+            let mut feature_name = &*ident;
+
+            // TODO(emilio): this is under a pref in Gecko.
+            if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
+                feature_name = &feature_name[8..];
+                flags |= nsMediaFeature_RequirementFlags::eHasWebkitPrefix as u8;
+            }
+
+            let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
+                feature_name = &feature_name[4..];
+                nsMediaExpression_Range::eMin
+            } else if starts_with_ignore_ascii_case(feature_name, "max-") {
+                feature_name = &feature_name[4..];
+                nsMediaExpression_Range::eMax
+            } else {
+                nsMediaExpression_Range::eEqual
+            };
+
+            let atom = Atom::from(feature_name);
+            let feature =
+                match find_feature(|f| atom.as_ptr() == unsafe { *f.mName }) {
+                    Some(f) => f,
+                    None => return Err(()),
+                };
+
+            if (feature.mReqFlags & !flags) != 0 {
+                return Err(());
+            }
+
+            if range != nsMediaExpression_Range::eEqual &&
+                feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed {
+                return Err(());
+            }
+
+            let value = match feature.mValueType {
+                nsMediaFeature_ValueType::eLength => {
+                    MediaExpressionValue::Length(
+                        specified::Length::parse_non_negative(input)?)
+                },
+                nsMediaFeature_ValueType::eInteger => {
+                    let i = input.expect_integer()?;
+                    if i < 0 {
+                        return Err(())
+                    }
+                    MediaExpressionValue::Integer(i as u32)
+                }
+                nsMediaFeature_ValueType::eBoolInteger => {
+                    let i = input.expect_integer()?;
+                    if i < 0 || i > 1 {
+                        return Err(())
+                    }
+                    MediaExpressionValue::BoolInteger(i == 1)
+                }
+                nsMediaFeature_ValueType::eFloat => {
+                    MediaExpressionValue::Float(input.expect_number()?)
+                }
+                nsMediaFeature_ValueType::eIntRatio => {
+                    let a = input.expect_integer()?;
+                    if a <= 0 {
+                        return Err(())
+                    }
+
+                    input.expect_delim('/')?;
+
+                    let b = input.expect_integer()?;
+                    if b <= 0 {
+                        return Err(())
+                    }
+                    MediaExpressionValue::IntRatio(a as u32, b as u32)
+                }
+                nsMediaFeature_ValueType::eResolution => {
+                    MediaExpressionValue::Resolution(Resolution::parse(input)?)
+                }
+                nsMediaFeature_ValueType::eEnumerated => {
+                    let index = unsafe {
+                        let _table = feature.mData.mKeywordTable.as_ref();
+                        0
+                    };
+                    MediaExpressionValue::Enumerated(index)
+                }
+                nsMediaFeature_ValueType::eIdent => {
+                    MediaExpressionValue::Ident(input.expect_ident()?.into())
+                }
+            };
+
+            Ok(Expression::new(feature, value, range))
+        })
+    }
+
+    /// Returns whether this media query evaluates to true for the given
+    /// device.
+    pub fn matches(&self, _device: &Device) -> bool {
+        // TODO
+        false
+    }
+}
--- a/servo/components/style/gecko/mod.rs
+++ b/servo/components/style/gecko/mod.rs
@@ -1,21 +1,16 @@
 /* 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/. */
 
 //! Gecko-specific style-system bits.
 
 pub mod conversions;
 pub mod data;
-
-// TODO(emilio): Implement Gecko media query parsing and evaluation using
-// nsMediaFeatures.
-#[path = "../servo/media_queries.rs"]
 pub mod media_queries;
-
 pub mod restyle_damage;
 pub mod selector_parser;
 pub mod snapshot;
 pub mod snapshot_helpers;
 pub mod traversal;
 pub mod values;
 pub mod wrapper;
--- a/servo/components/style/gecko_bindings/bindings.rs
+++ b/servo/components/style/gecko_bindings/bindings.rs
@@ -2,16 +2,17 @@
 
 pub use nsstring::{nsACString, nsAString};
 type nsACString_internal = nsACString;
 type nsAString_internal = nsAString;
 use gecko_bindings::structs::RawGeckoDocument;
 use gecko_bindings::structs::RawGeckoElement;
 use gecko_bindings::structs::RawGeckoNode;
 use gecko_bindings::structs::RawGeckoPresContext;
+use gecko_bindings::structs::RawGeckoPresContextOwned;
 use gecko_bindings::structs::ThreadSafeURIHolder;
 use gecko_bindings::structs::ThreadSafePrincipalHolder;
 use gecko_bindings::structs::CSSPseudoClassType;
 use gecko_bindings::structs::TraversalRootBehavior;
 use gecko_bindings::structs::FontFamilyList;
 use gecko_bindings::structs::FontFamilyType;
 use gecko_bindings::structs::ServoElementSnapshot;
 use gecko_bindings::structs::SheetParsingMode;
@@ -1095,24 +1096,21 @@ extern "C" {
     pub fn Servo_StyleSheet_HasRules(sheet: RawServoStyleSheetBorrowed)
      -> bool;
 }
 extern "C" {
     pub fn Servo_StyleSheet_GetRules(sheet: RawServoStyleSheetBorrowed)
      -> ServoCssRulesStrong;
 }
 extern "C" {
-    pub fn Servo_StyleSet_Init(pres_context: RawGeckoPresContextBorrowed)
+    pub fn Servo_StyleSet_Init(pres_context: RawGeckoPresContextOwned)
      -> RawServoStyleSetOwned;
 }
 extern "C" {
-    pub fn Servo_StyleSet_RecomputeDefaultStyles(set:
-                                                     RawServoStyleSetBorrowed,
-                                                 pres_context:
-                                                     RawGeckoPresContextBorrowed);
+    pub fn Servo_StyleSet_RebuildData(set: RawServoStyleSetBorrowed);
 }
 extern "C" {
     pub fn Servo_StyleSet_AppendStyleSheet(set: RawServoStyleSetBorrowed,
                                            sheet: RawServoStyleSheetBorrowed,
                                            flush: bool);
 }
 extern "C" {
     pub fn Servo_StyleSet_PrependStyleSheet(set: RawServoStyleSetBorrowed,
--- a/servo/components/style/gecko_bindings/structs_debug.rs
+++ b/servo/components/style/gecko_bindings/structs_debug.rs
@@ -12858,17 +12858,19 @@ pub mod root {
     pub type RawGeckoDocument = root::nsIDocument;
     pub type RawGeckoPresContext = [u64; 162usize];
     pub type RawGeckoNodeBorrowed = *const root::RawGeckoNode;
     pub type RawGeckoNodeBorrowedOrNull = *const root::RawGeckoNode;
     pub type RawGeckoElementBorrowed = *const root::RawGeckoElement;
     pub type RawGeckoElementBorrowedOrNull = *const root::RawGeckoElement;
     pub type RawGeckoDocumentBorrowed = *const root::RawGeckoDocument;
     pub type RawGeckoDocumentBorrowedOrNull = *const root::RawGeckoDocument;
+    pub type RawGeckoPresContextOwned = *mut [u64; 162usize];
     pub type RawGeckoPresContextBorrowed = *const [u64; 162usize];
+    pub type RawGeckoPresContextBorrowedMut = *mut [u64; 162usize];
     #[repr(u32)]
     #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
     pub enum nsCSSTokenSerializationType {
         eCSSTokenSerialization_Nothing = 0,
         eCSSTokenSerialization_Whitespace = 1,
         eCSSTokenSerialization_AtKeyword_or_Hash = 2,
         eCSSTokenSerialization_Number = 3,
         eCSSTokenSerialization_Dimension = 4,
@@ -14304,19 +14306,16 @@ pub mod root {
     /**
  * <div rustbindgen replaces="nsTArray"></div>
  */
     #[repr(C)]
     #[derive(Debug)]
     pub struct nsTArray<T> {
         pub mBuffer: *mut T,
     }
-    pub type ThreadSafePrincipalHolder =
-        root::nsMainThreadPtrHolder<root::nsIPrincipal>;
-    pub type ThreadSafeURIHolder = root::nsMainThreadPtrHolder<root::nsIURI>;
     #[repr(C)]
     #[derive(Debug, Copy)]
     pub struct nsMediaFeature {
         pub mName: *mut *mut root::nsIAtom,
         pub mRangeType: root::nsMediaFeature_RangeType,
         pub mValueType: root::nsMediaFeature_ValueType,
         pub mReqFlags: u8,
         pub mData: root::nsMediaFeature__bindgen_ty_1,
@@ -14368,16 +14367,19 @@ pub mod root {
     #[test]
     fn bindgen_test_layout_nsMediaFeature() {
         assert_eq!(::std::mem::size_of::<nsMediaFeature>() , 40usize);
         assert_eq!(::std::mem::align_of::<nsMediaFeature>() , 8usize);
     }
     impl Clone for nsMediaFeature {
         fn clone(&self) -> Self { *self }
     }
+    pub type ThreadSafePrincipalHolder =
+        root::nsMainThreadPtrHolder<root::nsIPrincipal>;
+    pub type ThreadSafeURIHolder = root::nsMainThreadPtrHolder<root::nsIURI>;
     pub type nsMediaFeatureValueGetter =
         ::std::option::Option<unsafe extern "C" fn(aPresContext:
                                                        *mut root::nsPresContext,
                                                    aFeature:
                                                        *const root::nsMediaFeature,
                                                    aResult:
                                                        *mut root::nsCSSValue)
                                   -> root::nsresult>;
--- a/servo/components/style/gecko_bindings/structs_release.rs
+++ b/servo/components/style/gecko_bindings/structs_release.rs
@@ -12784,17 +12784,19 @@ pub mod root {
     pub type RawGeckoDocument = root::nsIDocument;
     pub type RawGeckoPresContext = [u64; 158usize];
     pub type RawGeckoNodeBorrowed = *const root::RawGeckoNode;
     pub type RawGeckoNodeBorrowedOrNull = *const root::RawGeckoNode;
     pub type RawGeckoElementBorrowed = *const root::RawGeckoElement;
     pub type RawGeckoElementBorrowedOrNull = *const root::RawGeckoElement;
     pub type RawGeckoDocumentBorrowed = *const root::RawGeckoDocument;
     pub type RawGeckoDocumentBorrowedOrNull = *const root::RawGeckoDocument;
+    pub type RawGeckoPresContextOwned = *mut [u64; 158usize];
     pub type RawGeckoPresContextBorrowed = *const [u64; 158usize];
+    pub type RawGeckoPresContextBorrowedMut = *mut [u64; 158usize];
     #[repr(u32)]
     #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
     pub enum nsCSSTokenSerializationType {
         eCSSTokenSerialization_Nothing = 0,
         eCSSTokenSerialization_Whitespace = 1,
         eCSSTokenSerialization_AtKeyword_or_Hash = 2,
         eCSSTokenSerialization_Number = 3,
         eCSSTokenSerialization_Dimension = 4,
@@ -14230,19 +14232,16 @@ pub mod root {
     /**
  * <div rustbindgen replaces="nsTArray"></div>
  */
     #[repr(C)]
     #[derive(Debug)]
     pub struct nsTArray<T> {
         pub mBuffer: *mut T,
     }
-    pub type ThreadSafePrincipalHolder =
-        root::nsMainThreadPtrHolder<root::nsIPrincipal>;
-    pub type ThreadSafeURIHolder = root::nsMainThreadPtrHolder<root::nsIURI>;
     #[repr(C)]
     #[derive(Debug, Copy)]
     pub struct nsMediaFeature {
         pub mName: *mut *mut root::nsIAtom,
         pub mRangeType: root::nsMediaFeature_RangeType,
         pub mValueType: root::nsMediaFeature_ValueType,
         pub mReqFlags: u8,
         pub mData: root::nsMediaFeature__bindgen_ty_1,
@@ -14294,16 +14293,19 @@ pub mod root {
     #[test]
     fn bindgen_test_layout_nsMediaFeature() {
         assert_eq!(::std::mem::size_of::<nsMediaFeature>() , 40usize);
         assert_eq!(::std::mem::align_of::<nsMediaFeature>() , 8usize);
     }
     impl Clone for nsMediaFeature {
         fn clone(&self) -> Self { *self }
     }
+    pub type ThreadSafePrincipalHolder =
+        root::nsMainThreadPtrHolder<root::nsIPrincipal>;
+    pub type ThreadSafeURIHolder = root::nsMainThreadPtrHolder<root::nsIURI>;
     pub type nsMediaFeatureValueGetter =
         ::std::option::Option<unsafe extern "C" fn(aPresContext:
                                                        *mut root::nsPresContext,
                                                    aFeature:
                                                        *const root::nsMediaFeature,
                                                    aResult:
                                                        *mut root::nsCSSValue)
                                   -> root::nsresult>;
--- a/servo/components/style/media_queries.rs
+++ b/servo/components/style/media_queries.rs
@@ -14,17 +14,17 @@ use std::fmt;
 use style_traits::ToCss;
 
 #[cfg(feature = "servo")]
 pub use servo::media_queries::{Device, Expression};
 #[cfg(feature = "gecko")]
 pub use gecko::media_queries::{Device, Expression};
 
 /// A type that encapsulates a media query list.
-#[derive(Debug, PartialEq)]
+#[derive(Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct MediaList {
     /// The list of media queries.
     pub media_queries: Vec<MediaQuery>
 }
 
 impl ToCss for MediaList {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
@@ -61,17 +61,17 @@ impl ToCss for Qualifier {
             Qualifier::Only => write!(dest, "only"),
         }
     }
 }
 
 /// A [media query][mq].
 ///
 /// [mq]: https://drafts.csswg.org/mediaqueries/
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct MediaQuery {
     /// The qualifier for this query.
     pub qualifier: Option<Qualifier>,
     /// The media type for this query, that can be known, unknown, or "all".
     pub media_type: MediaQueryType,
     /// The set of expressions that this media query contains.
     pub expressions: Vec<Expression>,
--- a/servo/components/style/servo/media_queries.rs
+++ b/servo/components/style/servo/media_queries.rs
@@ -5,78 +5,49 @@
 //! Servo's media-query device and expression representation.
 
 use app_units::Au;
 use cssparser::Parser;
 use euclid::{Size2D, TypedSize2D};
 use media_queries::MediaType;
 use properties::ComputedValues;
 use std::fmt;
-#[cfg(feature = "gecko")]
-use std::sync::Arc;
 use style_traits::{ToCss, ViewportPx};
 use style_traits::viewport::ViewportConstraints;
 use values::computed::{self, ToComputedValue};
 use values::specified;
 
 /// A device is a structure that represents the current media a given document
 /// is displayed in.
 ///
 /// This is the struct against which media queries are evaluated.
-#[derive(Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Debug, HeapSizeOf)]
 pub struct Device {
     /// The current media type used by de device.
     media_type: MediaType,
     /// The current viewport size, in viewport pixels.
     viewport_size: TypedSize2D<f32, ViewportPx>,
-    /// A set of default computed values for this document.
-    ///
-    /// This allows handling zoom correctly, among other things. Gecko-only for
-    /// now, see #14773.
-    #[cfg(feature = "gecko")]
-    default_values: Arc<ComputedValues>,
 }
 
 impl Device {
     /// Trivially construct a new `Device`.
-    #[cfg(feature = "servo")]
     pub fn new(media_type: MediaType,
                viewport_size: TypedSize2D<f32, ViewportPx>)
                -> Device {
         Device {
             media_type: media_type,
             viewport_size: viewport_size,
         }
     }
 
-    /// Trivially construct a new `Device`.
-    #[cfg(feature = "gecko")]
-    pub fn new(media_type:
-               MediaType, viewport_size: TypedSize2D<f32, ViewportPx>,
-               default_values: &Arc<ComputedValues>) -> Device {
-        Device {
-            media_type: media_type,
-            viewport_size: viewport_size,
-            default_values: default_values.clone(),
-        }
-    }
-
     /// Return the default computed values for this device.
-    #[cfg(feature = "servo")]
     pub fn default_values(&self) -> &ComputedValues {
         ComputedValues::initial_values()
     }
 
-    /// Return the default computed values for this device.
-    #[cfg(feature = "gecko")]
-    pub fn default_values(&self) -> &ComputedValues {
-        &*self.default_values
-    }
-
     /// Returns the viewport size of the current device in app units, needed,
     /// among other things, to resolve viewport units.
     #[inline]
     pub fn au_viewport_size(&self) -> Size2D<Au> {
         Size2D::new(Au::from_f32_px(self.viewport_size.width),
                     Au::from_f32_px(self.viewport_size.height))
     }
 
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -44,16 +44,34 @@ pub use ::fnv::FnvHashMap;
 /// depending on the pseudo-element (see `PerPseudoElementSelectorMap`),
 /// and stylesheet origin (see the fields of `PerPseudoElementSelectorMap`).
 ///
 /// This structure is effectively created once per pipeline, in the
 /// LayoutThread corresponding to that pipeline.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct Stylist {
     /// Device that the stylist is currently evaluating against.
+    ///
+    /// This field deserves a bigger comment due to the different use that Gecko
+    /// and Servo give to it (that we should eventually unify).
+    ///
+    /// With Gecko, the device is never changed. Gecko manually tracks whether
+    /// the device data should be reconstructed, and "resets" the state of the
+    /// device.
+    ///
+    /// That's why we need to look for viewport rules on a style update (though
+    /// given new viewport rules may appear, this may have been a previous Servo
+    /// bug).
+    ///
+    /// On Servo, on the other hand, the device is a really cheap representation
+    /// that is recreated each time some constraint changes and calling
+    /// `set_device`.
+    ///
+    /// In both cases, the device is actually _owned_ by the Stylist, and it's
+    /// only an `Arc` so we can implement `add_stylesheet` more idiomatically.
     pub device: Arc<Device>,
 
     /// Viewport constraints based on the current device.
     viewport_constraints: Option<ViewportConstraints>,
 
     /// If true, the quirks-mode stylesheet is applied.
     quirks_mode: bool,
 
@@ -141,16 +159,28 @@ impl Stylist {
     pub fn update(&mut self,
                   doc_stylesheets: &[Arc<Stylesheet>],
                   ua_stylesheets: Option<&UserAgentStylesheets>,
                   stylesheets_changed: bool) -> bool {
         if !(self.is_device_dirty || stylesheets_changed) {
             return false;
         }
 
+        let cascaded_rule = ViewportRule {
+            declarations: viewport::Cascade::from_stylesheets(doc_stylesheets, &self.device).finish(),
+        };
+
+        self.viewport_constraints =
+            ViewportConstraints::maybe_new(&self.device, &cascaded_rule);
+
+        if let Some(ref constraints) = self.viewport_constraints {
+            Arc::get_mut(&mut self.device).unwrap()
+                .account_for_viewport_rule(constraints);
+        }
+
         self.element_map = PerPseudoElementSelectorMap::new();
         self.pseudos_map = Default::default();
         self.animations = Default::default();
         SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
             self.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new());
         });
 
         self.precomputed_pseudo_element_decls = Default::default();
@@ -389,16 +419,23 @@ impl Stylist {
     /// Also, the device that arrives here may need to take the viewport rules
     /// into account.
     ///
     /// TODO(emilio): Probably should be unified with `update`, right now I
     /// don't think we take into account dynamic updates to viewport rules.
     ///
     /// Probably worth to make the stylist own a single `Device`, and have a
     /// `update_device` function?
+    ///
+    /// feature = "servo" because gecko only has one device, and manually tracks
+    /// when the device is dirty.
+    ///
+    /// FIXME(emilio): The semantics of the device for Servo and Gecko are
+    /// different enough we may want to unify them.
+    #[cfg(feature = "servo")]
     pub fn set_device(&mut self, mut device: Device, stylesheets: &[Arc<Stylesheet>]) {
         let cascaded_rule = ViewportRule {
             declarations: viewport::Cascade::from_stylesheets(stylesheets, &device).finish(),
         };
 
         self.viewport_constraints =
             ViewportConstraints::maybe_new(&device, &cascaded_rule);
 
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -31,25 +31,25 @@ use style::gecko::wrapper::GeckoElement;
 use style::gecko_bindings::bindings::{RawServoDeclarationBlockBorrowed, RawServoDeclarationBlockStrong};
 use style::gecko_bindings::bindings::{RawServoStyleRuleBorrowed, RawServoStyleRuleStrong};
 use style::gecko_bindings::bindings::{RawServoStyleSetBorrowed, RawServoStyleSetOwned};
 use style::gecko_bindings::bindings::{RawServoStyleSheetBorrowed, ServoComputedValuesBorrowed};
 use style::gecko_bindings::bindings::{RawServoStyleSheetStrong, ServoComputedValuesStrong};
 use style::gecko_bindings::bindings::{ServoCssRulesBorrowed, ServoCssRulesStrong};
 use style::gecko_bindings::bindings::{nsACString, nsAString};
 use style::gecko_bindings::bindings::RawGeckoElementBorrowed;
-use style::gecko_bindings::bindings::RawGeckoPresContextBorrowed;
 use style::gecko_bindings::bindings::RawServoImportRuleBorrowed;
 use style::gecko_bindings::bindings::ServoComputedValuesBorrowedOrNull;
 use style::gecko_bindings::bindings::nsTArrayBorrowed_uintptr_t;
 use style::gecko_bindings::structs;
 use style::gecko_bindings::structs::{SheetParsingMode, nsIAtom, nsCSSPropertyID};
 use style::gecko_bindings::structs::{ThreadSafePrincipalHolder, ThreadSafeURIHolder};
 use style::gecko_bindings::structs::{nsRestyleHint, nsChangeHint};
 use style::gecko_bindings::structs::Loader;
+use style::gecko_bindings::structs::RawGeckoPresContextOwned;
 use style::gecko_bindings::structs::ServoStyleSheet;
 use style::gecko_bindings::structs::nsresult;
 use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasArcFFI, HasBoxFFI};
 use style::gecko_bindings::sugar::ownership::{HasSimpleFFI, Strong};
 use style::gecko_bindings::sugar::refptr::{GeckoArcPrincipal, GeckoArcURI};
 use style::parallel;
 use style::parser::{ParserContext, ParserContextExtraData};
 use style::properties::{CascadeFlags, ComputedValues, Importance, PropertyDeclaration};
@@ -102,17 +102,17 @@ fn create_shared_context(per_doc_data: &
         stylist: per_doc_data.stylist.clone(),
         running_animations: per_doc_data.running_animations.clone(),
         expired_animations: per_doc_data.expired_animations.clone(),
         error_reporter: Box::new(StdoutErrorReporter),
         local_context_creation_data: Mutex::new(local_context_data),
         timer: Timer::new(),
         // FIXME Find the real QuirksMode information for this document
         quirks_mode: QuirksMode::NoQuirks,
-        default_computed_values: per_doc_data.default_computed_values.clone(),
+        default_computed_values: per_doc_data.default_computed_values().clone(),
     }
 }
 
 fn traverse_subtree(element: GeckoElement, raw_data: RawServoStyleSetBorrowed,
                     unstyled_children_only: bool) {
     // When new content is inserted in a display:none subtree, we will call into
     // servo to try to style it. Detect that here and bail out.
     if let Some(parent) = element.parent_element() {
@@ -172,17 +172,17 @@ pub extern "C" fn Servo_RestyleWithAdded
 
     let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
 
     // 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,
-                                      &data.default_computed_values,
+                                      data.default_computed_values(),
                                       None,
                                       Box::new(StdoutErrorReporter),
                                       None,
                                       CascadeFlags::empty());
     Arc::new(computed).into_strong()
 }
 
 #[no_mangle]
@@ -518,17 +518,17 @@ pub extern "C" fn Servo_ComputedValues_G
      -> ServoComputedValuesStrong {
     let data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
     let atom = Atom::from(pseudo_tag);
     let pseudo = PseudoElement::from_atom_unchecked(atom, /* anon_box = */ true);
 
 
     let maybe_parent = ComputedValues::arc_from_borrowed(&parent_style_or_null);
     data.stylist.precomputed_values_for_pseudo(&pseudo, maybe_parent,
-                                               &data.default_computed_values, false)
+                                               data.default_computed_values(), false)
         .values
         .into_strong()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ResolvePseudoStyle(element: RawGeckoElementBorrowed,
                                            pseudo_tag: *mut nsIAtom, is_probe: bool,
                                            raw_data: RawServoStyleSetBorrowed)
@@ -539,17 +539,17 @@ pub extern "C" fn Servo_ResolvePseudoSty
     let doc_data = PerDocumentStyleData::from_ffi(raw_data);
 
     // FIXME(bholley): Assert against this.
     if data.get_styles().is_none() {
         error!("Calling Servo_ResolvePseudoStyle on unstyled element");
         return if is_probe {
             Strong::null()
         } else {
-            doc_data.borrow().default_computed_values.clone().into_strong()
+            doc_data.borrow().default_computed_values().clone().into_strong()
         };
     }
 
     match get_pseudo_style(element, pseudo_tag, data.styles(), doc_data) {
         Some(values) => values.into_strong(),
         None if !is_probe => data.styles().primary.values.clone().into_strong(),
         None => Strong::null(),
     }
@@ -561,61 +561,61 @@ fn get_pseudo_style(element: GeckoElemen
 {
     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;
-            d.stylist.lazily_compute_pseudo_element_style(&element, &pseudo, base, &d.default_computed_values)
+            d.stylist.lazily_compute_pseudo_element_style(&element, &pseudo, base, &d.default_computed_values())
                      .map(|s| s.values.clone())
         },
     }
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ComputedValues_Inherit(
   raw_data: RawServoStyleSetBorrowed,
   parent_style: ServoComputedValuesBorrowedOrNull)
      -> ServoComputedValuesStrong {
     let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
     let maybe_arc = ComputedValues::arc_from_borrowed(&parent_style);
     let style = if let Some(reference) = maybe_arc.as_ref() {
-        ComputedValues::inherit_from(reference, &data.default_computed_values)
+        ComputedValues::inherit_from(reference, &data.default_computed_values())
     } else {
-        data.default_computed_values.clone()
+        data.default_computed_values().clone()
     };
     style.into_strong()
 }
 
 #[no_mangle]
-pub extern "C" fn Servo_ComputedValues_AddRef(ptr: ServoComputedValuesBorrowed) -> () {
+pub extern "C" fn Servo_ComputedValues_AddRef(ptr: ServoComputedValuesBorrowed) {
     unsafe { ComputedValues::addref(ptr) };
 }
 
 #[no_mangle]
-pub extern "C" fn Servo_ComputedValues_Release(ptr: ServoComputedValuesBorrowed) -> () {
+pub extern "C" fn Servo_ComputedValues_Release(ptr: ServoComputedValuesBorrowed) {
     unsafe { ComputedValues::release(ptr) };
 }
 
+/// See the comment in `Device` to see why it's ok to pass an owned reference to
+/// the pres context (hint: the context outlives the StyleSet, that holds the
+/// device alive).
 #[no_mangle]
-pub extern "C" fn Servo_StyleSet_Init(pres_context: RawGeckoPresContextBorrowed)
+pub extern "C" fn Servo_StyleSet_Init(pres_context: RawGeckoPresContextOwned)
   -> RawServoStyleSetOwned {
     let data = Box::new(PerDocumentStyleData::new(pres_context));
     data.into_ffi()
 }
 
 #[no_mangle]
-pub extern "C" fn Servo_StyleSet_RecomputeDefaultStyles(
-  raw_data: RawServoStyleSetBorrowed,
-  pres_context: RawGeckoPresContextBorrowed) {
+pub extern "C" fn Servo_StyleSet_RebuildData(raw_data: RawServoStyleSetBorrowed) {
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
-    data.default_computed_values = ComputedValues::default_values(pres_context);
-    // FIXME(bz): We need to update our Stylist's Device's computed values, but how?
+    data.device_changed = true;
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_Drop(data: RawServoStyleSetOwned) -> () {
     let _ = data.into_box::<PerDocumentStyleData>();
 }
 
 
@@ -936,17 +936,17 @@ pub extern "C" fn Servo_ResolveStyle(ele
 {
     let element = GeckoElement(element);
     debug!("Servo_ResolveStyle: {:?}", element);
     let data = unsafe { element.ensure_data() }.borrow_mut();
 
     if !data.has_current_styles() {
         error!("Resolving style on unstyled element with lazy computation forbidden.");
         let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();
-        return per_doc_data.default_computed_values.clone().into_strong();
+        return per_doc_data.default_computed_values().clone().into_strong();
     }
 
     data.styles().primary.values.clone().into_strong()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
                                            pseudo_tag: *mut nsIAtom,