servo: Merge #14833 - style: Isolate the soon-to-be style-backend-specific code from the media_query module (from emilio:mq-refactor); r=heycam,bz
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sat, 07 Jan 2017 05:03:41 -0800
changeset 340498 5eefb2520621433f04fe1d77bd4cb6697a21df26
parent 340497 2164abd410eee9c96aa0755e5c8d9d7f0c2663cd
child 340499 424942d26b0508cbb11d96fc215f469771276846
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam, bz
servo: Merge #14833 - style: Isolate the soon-to-be style-backend-specific code from the media_query module (from emilio:mq-refactor); r=heycam,bz The idea here is having (for convenience) different `Expression` and `Device` representations for Gecko. Both will be implemented in rust, but will use `nsMediaFeatures` as a source of media features instead of the Servo bits. Does it sound good? r? @heycam or @Manishearth Source-Repo: https://github.com/servo/servo Source-Revision: fdddeb1c9229b6f9bc202cb2df1a3701eec47e05
servo/components/style/gecko/mod.rs
servo/components/style/lib.rs
servo/components/style/media_queries.rs
servo/components/style/servo/media_queries.rs
servo/components/style/servo/mod.rs
servo/components/style/stylist.rs
servo/components/style/viewport.rs
servo/tests/unit/style/media_queries.rs
servo/tests/unit/style/viewport.rs
--- a/servo/components/style/gecko/mod.rs
+++ b/servo/components/style/gecko/mod.rs
@@ -1,16 +1,21 @@
 /* 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;
-
-pub mod conversions;
-pub mod selector_parser;
-pub mod values;
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -105,17 +105,16 @@ pub mod error_reporting;
 pub mod font_face;
 pub mod font_metrics;
 #[cfg(feature = "gecko")] #[allow(unsafe_code)] pub mod gecko;
 #[cfg(feature = "gecko")] #[allow(unsafe_code)] pub mod gecko_bindings;
 pub mod keyframes;
 #[allow(missing_docs)] // TODO.
 pub mod logical_geometry;
 pub mod matching;
-#[allow(missing_docs)]
 pub mod media_queries;
 pub mod owning_handle;
 pub mod parallel;
 pub mod parser;
 pub mod restyle_hints;
 pub mod rule_tree;
 pub mod scoped_tls;
 pub mod selector_parser;
--- a/servo/components/style/media_queries.rs
+++ b/servo/components/style/media_queries.rs
@@ -2,32 +2,32 @@
  * 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/. */
 
 //! [Media queries][mq].
 //!
 //! [mq]: https://drafts.csswg.org/mediaqueries/
 
 use Atom;
-use app_units::Au;
 use cssparser::{Delimiter, Parser, Token};
-use euclid::size::{Size2D, TypedSize2D};
-use properties::ComputedValues;
 use serialize_comma_separated_list;
 use std::ascii::AsciiExt;
 use std::fmt;
+use style_traits::ToCss;
+
+#[cfg(feature = "servo")]
+pub use servo::media_queries::{Device, Expression};
 #[cfg(feature = "gecko")]
-use std::sync::Arc;
-use style_traits::{ToCss, ViewportPx};
-use values::computed::{self, ToComputedValue};
-use values::specified;
+pub use gecko::media_queries::{Device, Expression};
 
+/// A type that encapsulates a media query list.
 #[derive(Debug, PartialEq)]
 #[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
         where W: fmt::Write
     {
         serialize_comma_separated_list(dest, &self.media_queries)
@@ -35,111 +35,75 @@ impl ToCss for MediaList {
 }
 
 impl Default for MediaList {
     fn default() -> MediaList {
         MediaList { media_queries: vec![] }
     }
 }
 
-#[derive(PartialEq, Eq, Copy, Clone, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub enum Range<T> {
-    Min(T),
-    Max(T),
-    Eq(T),
-}
-
-impl Range<specified::Length> {
-    fn to_computed_range(&self, viewport_size: Size2D<Au>, default_values: &ComputedValues) -> Range<Au> {
-        // http://dev.w3.org/csswg/mediaqueries3/#units
-        // em units are relative to the initial font-size.
-        let context = computed::Context {
-            is_root_element: false,
-            viewport_size: viewport_size,
-            inherited_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
-        };
-
-        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))
-        }
-    }
-}
-
-impl<T: Ord> Range<T> {
-    fn evaluate(&self, value: T) -> bool {
-        match *self {
-            Range::Min(ref width) => { value >= *width },
-            Range::Max(ref width) => { value <= *width },
-            Range::Eq(ref width) => { value == *width },
-        }
-    }
-}
-
-/// http://dev.w3.org/csswg/mediaqueries-3/#media1
-#[derive(PartialEq, Copy, Clone, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub enum Expression {
-    /// http://dev.w3.org/csswg/mediaqueries-3/#width
-    Width(Range<specified::Length>),
-}
-
-/// http://dev.w3.org/csswg/mediaqueries-3/#media0
+/// https://drafts.csswg.org/mediaqueries/#mq-prefix
 #[derive(PartialEq, Eq, Copy, Clone, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum Qualifier {
+    /// Hide a media query from legacy UAs:
+    /// https://drafts.csswg.org/mediaqueries/#mq-only
     Only,
+    /// Negate a media query:
+    /// https://drafts.csswg.org/mediaqueries/#mq-not
     Not,
 }
 
 impl ToCss for Qualifier {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
         where W: fmt::Write
     {
         match *self {
             Qualifier::Not => write!(dest, "not"),
             Qualifier::Only => write!(dest, "only"),
         }
     }
 }
 
+/// A [media query][mq].
+///
+/// [mq]: https://drafts.csswg.org/mediaqueries/
 #[derive(Clone, Debug, PartialEq)]
 #[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>,
 }
 
 impl MediaQuery {
     /// Return a media query that never matches, used for when we fail to parse
     /// a given media query.
     fn never_matching() -> Self {
         Self::new(Some(Qualifier::Not), MediaQueryType::All, vec![])
     }
 
-    pub fn new(qualifier: Option<Qualifier>, media_type: MediaQueryType,
+    /// Trivially constructs a new media query.
+    pub fn new(qualifier: Option<Qualifier>,
+               media_type: MediaQueryType,
                expressions: Vec<Expression>) -> MediaQuery {
         MediaQuery {
             qualifier: qualifier,
             media_type: media_type,
             expressions: expressions,
         }
     }
 }
 
 impl ToCss for MediaQuery {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-        where W: fmt::Write
+        where W: fmt::Write,
     {
         if let Some(qual) = self.qualifier {
             try!(qual.to_css(dest));
             try!(write!(dest, " "));
         }
 
         match self.media_type {
             MediaQueryType::All => {
@@ -160,151 +124,82 @@ impl ToCss for MediaQuery {
         if self.expressions.is_empty() {
             return Ok(());
         }
 
         if self.media_type != MediaQueryType::All || self.qualifier.is_some() {
             try!(write!(dest, " and "));
         }
 
-        for (i, &e) in self.expressions.iter().enumerate() {
-            try!(write!(dest, "("));
-            let (mm, l) = match e {
-                Expression::Width(Range::Min(ref l)) => ("min-", l),
-                Expression::Width(Range::Max(ref l)) => ("max-", l),
-                Expression::Width(Range::Eq(ref l)) => ("", l),
-            };
-            try!(write!(dest, "{}width: ", mm));
-            try!(l.to_css(dest));
-            try!(write!(dest, ")"));
-            if i != self.expressions.len() - 1 {
-                try!(write!(dest, " and "));
-            }
+        try!(self.expressions[0].to_css(dest));
+
+        for expr in self.expressions.iter().skip(1) {
+            try!(write!(dest, " and "));
+            try!(expr.to_css(dest));
         }
         Ok(())
     }
 }
 
 /// http://dev.w3.org/csswg/mediaqueries-3/#media0
 #[derive(PartialEq, Eq, Clone, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum MediaQueryType {
-    All,  // Always true
+    /// A media type that matches every device.
+    All,
+    /// A known media type, that we parse and understand.
     Known(MediaType),
+    /// An unknown media type.
     Unknown(Atom),
 }
 
 impl MediaQueryType {
     fn parse(ident: &str) -> Self {
         if ident.eq_ignore_ascii_case("all") {
             return MediaQueryType::All;
         }
 
         match MediaType::parse(ident) {
             Some(media_type) => MediaQueryType::Known(media_type),
             None => MediaQueryType::Unknown(Atom::from(ident)),
         }
     }
 
-    fn matches(&self, other: &MediaType) -> bool {
+    fn matches(&self, other: MediaType) -> bool {
         match *self {
             MediaQueryType::All => true,
-            MediaQueryType::Known(ref known_type) => known_type == other,
+            MediaQueryType::Known(ref known_type) => *known_type == other,
             MediaQueryType::Unknown(..) => false,
         }
     }
 }
 
+/// https://drafts.csswg.org/mediaqueries/#media-types
 #[derive(PartialEq, Eq, Clone, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum MediaType {
+    /// The "screen" media type.
     Screen,
+    /// The "print" media type.
     Print,
 }
 
 impl MediaType {
     fn parse(name: &str) -> Option<Self> {
         Some(match_ignore_ascii_case! { name,
             "screen" => MediaType::Screen,
             "print" => MediaType::Print,
             _ => return None
         })
     }
 }
-
-#[derive(Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub struct Device {
-    pub media_type: MediaType,
-    pub viewport_size: TypedSize2D<f32, ViewportPx>,
-    #[cfg(feature = "gecko")]
-    pub default_values: Arc<ComputedValues>,
-}
-
-impl 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,
-        }
-    }
-
-    #[cfg(feature = "servo")]
-    pub fn default_values(&self) -> &ComputedValues {
-        ComputedValues::initial_values()
-    }
-
-    #[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(),
-        }
-    }
-
-    #[cfg(feature = "gecko")]
-    pub fn default_values(&self) -> &ComputedValues {
-        &*self.default_values
-    }
-
-    #[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))
-    }
-
-}
-
-impl Expression {
-    fn parse(input: &mut Parser) -> Result<Expression, ()> {
-        try!(input.expect_parenthesis_block());
-        input.parse_nested_block(|input| {
-            let name = try!(input.expect_ident());
-            try!(input.expect_colon());
-            // TODO: Handle other media features
-            match_ignore_ascii_case! { name,
-                "min-width" => {
-                    Ok(Expression::Width(Range::Min(try!(specified::Length::parse_non_negative(input)))))
-                },
-                "max-width" => {
-                    Ok(Expression::Width(Range::Max(try!(specified::Length::parse_non_negative(input)))))
-                },
-                "width" => {
-                    Ok(Expression::Width(Range::Eq(try!(specified::Length::parse_non_negative(input)))))
-                },
-                _ => Err(())
-            }
-        })
-    }
-}
-
 impl MediaQuery {
+    /// Parse a media query given css input.
+    ///
+    /// Returns an error if any of the expressions is unknown.
     pub fn parse(input: &mut Parser) -> Result<MediaQuery, ()> {
         let mut expressions = vec![];
 
         let qualifier = if input.try(|input| input.expect_ident_matching("only")).is_ok() {
             Some(Qualifier::Only)
         } else if input.try(|input| input.expect_ident_matching("not")).is_ok() {
             Some(Qualifier::Not)
         } else {
@@ -331,58 +226,77 @@ impl MediaQuery {
             if input.try(|input| input.expect_ident_matching("and")).is_err() {
                 return Ok(MediaQuery::new(qualifier, media_type, expressions))
             }
             expressions.push(try!(Expression::parse(input)))
         }
     }
 }
 
+/// Parse a media query list from CSS.
+///
+/// Always returns a media query list. If any invalid media query is found, the
+/// media query list is only filled with the equivalent of "not all", see:
+///
+/// https://drafts.csswg.org/mediaqueries/#error-handling
 pub fn parse_media_query_list(input: &mut Parser) -> MediaList {
     if input.is_exhausted() {
         return Default::default()
     }
 
     let mut media_queries = vec![];
+    let mut found_invalid = false;
     loop {
-        media_queries.push(
-            input.parse_until_before(Delimiter::Comma, MediaQuery::parse).ok()
-                 .unwrap_or_else(MediaQuery::never_matching));
+        match input.parse_until_before(Delimiter::Comma, MediaQuery::parse) {
+            Ok(mq) => if !found_invalid {
+                media_queries.push(mq);
+            },
+            Err(..) => if !found_invalid {
+                media_queries.clear();
+                media_queries.push(MediaQuery::never_matching());
+                // Consume the rest of the input as if they were valid
+                // expressions (they might be, they might not), but ignore the
+                // result, this allows correctly parsing invalid media queries.
+                found_invalid = true;
+            },
+        }
+
         match input.next() {
             Ok(Token::Comma) => {},
             Ok(_) => unreachable!(),
             Err(()) => break,
         }
     }
+
+    debug_assert!(!found_invalid || media_queries.len() == 1);
+
     MediaList {
         media_queries: media_queries,
     }
 }
 
 impl MediaList {
+    /// Evaluate a whole `MediaList` against `Device`.
     pub fn evaluate(&self, device: &Device) -> bool {
-        let viewport_size = device.au_viewport_size();
-
         // Check if it is an empty media query list or any queries match (OR condition)
         // https://drafts.csswg.org/mediaqueries-4/#mq-list
         self.media_queries.is_empty() || self.media_queries.iter().any(|mq| {
-            let media_match = mq.media_type.matches(&device.media_type);
+            let media_match = mq.media_type.matches(device.media_type());
 
             // Check if all conditions match (AND condition)
-            let query_match = media_match && mq.expressions.iter().all(|expression| {
-                match *expression {
-                    Expression::Width(ref value) =>
-                        value.to_computed_range(viewport_size, device.default_values()).evaluate(viewport_size.width),
-                }
-            });
+            let query_match =
+                media_match &&
+                mq.expressions.iter()
+                    .all(|expression| expression.matches(&device));
 
             // Apply the logical NOT qualifier to the result
             match mq.qualifier {
                 Some(Qualifier::Not) => !query_match,
                 _ => query_match,
             }
         })
     }
 
+    /// Whether this `MediaList` contains no media queries.
     pub fn is_empty(&self) -> bool {
         self.media_queries.is_empty()
     }
 }
new file mode 100644
--- /dev/null
+++ b/servo/components/style/servo/media_queries.rs
@@ -0,0 +1,224 @@
+/* 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/. */
+
+//! 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))]
+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))
+    }
+
+    /// Returns the viewport size in pixels.
+    #[inline]
+    pub fn px_viewport_size(&self) -> TypedSize2D<f32, ViewportPx> {
+        self.viewport_size
+    }
+
+    /// Take into account a viewport rule taken from the stylesheets.
+    pub fn account_for_viewport_rule(&mut self, constraints: &ViewportConstraints) {
+        self.viewport_size = constraints.size;
+    }
+
+    /// Return the media type of the current device.
+    pub fn media_type(&self) -> MediaType {
+        self.media_type.clone()
+    }
+}
+
+/// A expression kind servo understands and parses.
+///
+/// Only `pub` for unit testing, please don't use it directly!
+#[derive(PartialEq, Copy, Clone, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum ExpressionKind {
+    /// http://dev.w3.org/csswg/mediaqueries-3/#width
+    Width(Range<specified::Length>),
+}
+
+/// A single expression a per:
+///
+/// http://dev.w3.org/csswg/mediaqueries-3/#media1
+#[derive(PartialEq, Clone, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct Expression(ExpressionKind);
+
+impl Expression {
+    /// The kind of expression we're, just for unit testing.
+    ///
+    /// Eventually this will become servo-only.
+    pub fn kind_for_testing(&self) -> &ExpressionKind {
+        &self.0
+    }
+
+    /// Parse a media expression of the form:
+    ///
+    /// ```
+    /// (media-feature: media-value)
+    /// ```
+    ///
+    /// Only supports width and width ranges for now.
+    pub fn parse(input: &mut Parser) -> Result<Self, ()> {
+        try!(input.expect_parenthesis_block());
+        input.parse_nested_block(|input| {
+            let name = try!(input.expect_ident());
+            try!(input.expect_colon());
+            // TODO: Handle other media features
+            Ok(Expression(match_ignore_ascii_case! { name,
+                "min-width" => {
+                    ExpressionKind::Width(Range::Min(try!(specified::Length::parse_non_negative(input))))
+                },
+                "max-width" => {
+                    ExpressionKind::Width(Range::Max(try!(specified::Length::parse_non_negative(input))))
+                },
+                "width" => {
+                    ExpressionKind::Width(Range::Eq(try!(specified::Length::parse_non_negative(input))))
+                },
+                _ => return Err(())
+            }))
+        })
+    }
+
+    /// Evaluate this expression and return whether it matches the current
+    /// device.
+    pub fn matches(&self, device: &Device) -> bool {
+        let viewport_size = device.au_viewport_size();
+        let value = viewport_size.width;
+        match self.0 {
+            ExpressionKind::Width(ref range) => {
+                match range.to_computed_range(viewport_size, device.default_values()) {
+                    Range::Min(ref width) => { value >= *width },
+                    Range::Max(ref width) => { value <= *width },
+                    Range::Eq(ref width) => { value == *width },
+                }
+            }
+        }
+    }
+}
+
+impl ToCss for Expression {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+        where W: fmt::Write,
+    {
+        try!(write!(dest, "("));
+        let (mm, l) = match self.0 {
+            ExpressionKind::Width(Range::Min(ref l)) => ("min-", l),
+            ExpressionKind::Width(Range::Max(ref l)) => ("max-", l),
+            ExpressionKind::Width(Range::Eq(ref l)) => ("", l),
+        };
+        try!(write!(dest, "{}width: ", mm));
+        try!(l.to_css(dest));
+        write!(dest, ")")
+    }
+}
+
+/// An enumeration that represents a ranged value.
+///
+/// Only public for testing, implementation details of `Expression` may change
+/// for Stylo.
+#[derive(PartialEq, Eq, Copy, Clone, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum Range<T> {
+    /// At least the inner value.
+    Min(T),
+    /// At most the inner value.
+    Max(T),
+    /// Exactly the inner value.
+    Eq(T),
+}
+
+impl Range<specified::Length> {
+    fn to_computed_range(&self,
+                         viewport_size: Size2D<Au>,
+                         default_values: &ComputedValues)
+                         -> Range<Au> {
+        // http://dev.w3.org/csswg/mediaqueries3/#units
+        // em units are relative to the initial font-size.
+        let context = computed::Context {
+            is_root_element: false,
+            viewport_size: viewport_size,
+            inherited_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
+        };
+
+        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/servo/mod.rs
+++ b/servo/components/style/servo/mod.rs
@@ -1,10 +1,11 @@
 /* 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/. */
 
 //! Servo-specific bits of the style system.
 //!
 //! These get compiled out on a Gecko build.
 
+pub mod media_queries;
 pub mod restyle_damage;
 pub mod selector_parser;
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -7,18 +7,16 @@
 #![deny(missing_docs)]
 
 use {Atom, LocalName};
 use data::ComputedStyle;
 use dom::{PresentationalHintsSynthetizer, TElement};
 use error_reporting::StdoutErrorReporter;
 use keyframes::KeyframesAnimation;
 use media_queries::Device;
-#[cfg(feature = "servo")]
-use media_queries::MediaType;
 use parking_lot::RwLock;
 use properties::{self, CascadeFlags, ComputedValues, INHERIT_ALL, Importance};
 use properties::{PropertyDeclaration, PropertyDeclarationBlock};
 use quickersort::sort_by;
 use restyle_hints::{RestyleHint, DependencySet};
 use rule_tree::{RuleTree, StrongRuleNode, StyleSource};
 use selector_parser::{ElementExt, SelectorImpl, PseudoElement, Snapshot};
 use selectors::Element;
@@ -31,17 +29,16 @@ use smallvec::VecLike;
 use std::borrow::Borrow;
 use std::collections::HashMap;
 use std::fmt;
 use std::hash::Hash;
 use std::slice;
 use std::sync::Arc;
 use style_traits::viewport::ViewportConstraints;
 use stylesheets::{CssRule, Origin, StyleRule, Stylesheet, UserAgentStylesheets};
-#[cfg(feature = "servo")]
 use viewport::{self, MaybeNew, ViewportRule};
 
 pub use ::fnv::FnvHashMap;
 
 /// This structure holds all the selectors and device characteristics
 /// for a given document. The selectors are converted into `Rule`s
 /// (defined in rust-selectors), and introduced in a `SelectorMap`
 /// depending on the pseudo-element (see `PerPseudoElementSelectorMap`),
@@ -384,30 +381,34 @@ impl Stylist {
     }
 
     /// Set a given device, which may change the styles that apply to the
     /// document.
     ///
     /// This means that we may need to rebuild style data even if the
     /// stylesheets haven't changed.
     ///
-    /// Viewport_Constraints::maybe_new is servo-only (see the comment above it
-    /// explaining why), so we need to be servo-only too, since we call it.
-    #[cfg(feature = "servo")]
+    /// 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?
     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.viewport_size, &cascaded_rule);
+        self.viewport_constraints =
+            ViewportConstraints::maybe_new(&device, &cascaded_rule);
+
         if let Some(ref constraints) = self.viewport_constraints {
-            // FIXME(emilio): creating a device here works, but is not really
-            // appropriate. I should get rid of this while doing the stylo media
-            // query work.
-            device = Device::new(MediaType::Screen, constraints.size);
+            device.account_for_viewport_rule(constraints);
         }
 
         fn mq_eval_changed(rules: &[CssRule], before: &Device, after: &Device) -> bool {
             for rule in rules {
                 if rule.with_nested_rules_and_mq(|rules, mq| {
                     if let Some(mq) = mq {
                         if mq.evaluate(before) != mq.evaluate(after) {
                             return true
@@ -445,17 +446,16 @@ impl Stylist {
     }
 
     /// Returns the applicable CSS declarations for the given element.
     ///
     /// This corresponds to `ElementRuleCollector` in WebKit.
     ///
     /// The returned `StyleRelations` indicate hints about which kind of rules
     /// have matched.
-    #[allow(unsafe_code)]
     pub fn push_applicable_declarations<E, V>(
                                         &self,
                                         element: &E,
                                         parent_bf: Option<&BloomFilter>,
                                         style_attribute: Option<&Arc<RwLock<PropertyDeclarationBlock>>>,
                                         pseudo_element: Option<&PseudoElement>,
                                         applicable_declarations: &mut V,
                                         reason: MatchingReason) -> StyleRelations
@@ -851,17 +851,16 @@ impl SelectorMap {
 
         // Sort only the rules we just added.
         sort_by_key(&mut matching_rules_list[init_len..],
                     |block| (block.specificity, block.source_order));
     }
 
     /// Append to `rule_list` all universal Rules (rules with selector `*|*`) in
     /// `self` sorted by specifity and source order.
-    #[allow(unsafe_code)]
     pub fn get_universal_rules<V>(&self,
                                   matching_rules_list: &mut V)
         where V: VecLike<ApplicableDeclarationBlock>
     {
         if self.empty {
             return
         }
 
@@ -908,17 +907,16 @@ impl SelectorMap {
                                             matching_rules,
                                             relations,
                                             reason,
                                             importance)
         }
     }
 
     /// Adds rules in `rules` that match `element` to the `matching_rules` list.
-    #[allow(unsafe_code)]
     fn get_matching_rules<E, V>(element: &E,
                                 parent_bf: Option<&BloomFilter>,
                                 rules: &[Rule],
                                 matching_rules: &mut V,
                                 relations: &mut StyleRelations,
                                 reason: MatchingReason,
                                 importance: Importance)
         where E: Element<Impl=SelectorImpl>,
--- a/servo/components/style/viewport.rs
+++ b/servo/components/style/viewport.rs
@@ -4,38 +4,31 @@
 
 //! The [`@viewport`][at] at-rule and [`meta`][meta] element.
 //!
 //! [at]: https://drafts.csswg.org/css-device-adapt/#atviewport-rule
 //! [meta]: https://drafts.csswg.org/css-device-adapt/#viewport-meta
 
 #![deny(missing_docs)]
 
-#[cfg(feature = "servo")]
 use app_units::Au;
 use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser, parse_important};
 use cssparser::ToCss as ParserToCss;
-#[cfg(feature = "servo")]
 use euclid::scale_factor::ScaleFactor;
-#[cfg(feature = "servo")]
-use euclid::size::Size2D;
 use euclid::size::TypedSize2D;
 use media_queries::Device;
 use parser::{ParserContext, log_css_error};
-#[cfg(feature = "servo")]
-use properties::ComputedValues;
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::fmt;
 use std::iter::Enumerate;
 use std::str::Chars;
-use style_traits::{ToCss, ViewportPx};
+use style_traits::ToCss;
 use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom};
 use stylesheets::{Stylesheet, Origin};
-#[cfg(feature = "servo")]
 use values::computed::{Context, ToComputedValue};
 use values::specified::{Length, LengthOrPercentageOrAuto, ViewportPercentageLength};
 
 macro_rules! declare_viewport_descriptor {
     ( $( $variant_name: expr => $variant: ident($data: ident), )+ ) => {
          declare_viewport_descriptor_inner!([] [ $( $variant_name => $variant($data), )+ ] 0);
     };
 }
@@ -601,28 +594,23 @@ impl Cascade {
         self.declarations.into_iter().filter_map(|entry| entry.map(|(_, decl)| decl)).collect()
     }
 }
 
 /// Just a helper trait to be able to implement methods on ViewportConstraints.
 pub trait MaybeNew {
     /// Create a ViewportConstraints from a viewport size and a `@viewport`
     /// rule.
-    fn maybe_new(initial_viewport: TypedSize2D<f32, ViewportPx>,
+    fn maybe_new(device: &Device,
                  rule: &ViewportRule)
                  -> Option<ViewportConstraints>;
 }
 
-/// MaybeNew for ViewportConstraints uses ComputedValues::initial_values which
-/// is servo-only (not present in gecko).  Once it has been changed to properly
-/// use per-document initial computed values, or not use the initial computed
-/// values at all, it can go back to being compiled unconditionally.
-#[cfg(feature = "servo")]
 impl MaybeNew for ViewportConstraints {
-    fn maybe_new(initial_viewport: TypedSize2D<f32, ViewportPx>,
+    fn maybe_new(device: &Device,
                  rule: &ViewportRule)
                  -> Option<ViewportConstraints>
     {
         use std::cmp;
 
         if rule.declarations.is_empty() {
             return None
         }
@@ -690,25 +678,24 @@ impl MaybeNew for ViewportConstraints {
         if initial_zoom.is_some() {
             initial_zoom = max!(min_zoom, min!(max_zoom, initial_zoom));
         }
 
         // 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 = Size2D::new(Au::from_f32_px(initial_viewport.width),
-                                           Au::from_f32_px(initial_viewport.height));
+        let initial_viewport = device.au_viewport_size();
 
-
+        // TODO(emilio): Stop cloning `ComputedValues` around!
         let context = Context {
             is_root_element: false,
             viewport_size: initial_viewport,
-            inherited_style: ComputedValues::initial_values(),
-            style: ComputedValues::initial_values().clone(),
+            inherited_style: device.default_values(),
+            style: device.default_values().clone(),
             font_metrics_provider: None, // TODO: Should have!
         };
 
         // DEVICE-ADAPT § 9.3 Resolving 'extend-to-zoom'
         let extend_width;
         let extend_height;
         if let Some(extend_zoom) = max!(initial_zoom, max_zoom) {
             let scale_factor = 1. / extend_zoom;
--- a/servo/tests/unit/style/media_queries.rs
+++ b/servo/tests/unit/style/media_queries.rs
@@ -6,45 +6,51 @@ use app_units::Au;
 use cssparser::{Parser, SourcePosition};
 use euclid::size::TypedSize2D;
 use servo_url::ServoUrl;
 use std::borrow::ToOwned;
 use style::Atom;
 use style::error_reporting::ParseErrorReporter;
 use style::media_queries::*;
 use style::parser::ParserContextExtraData;
+use style::servo::media_queries::*;
 use style::stylesheets::{Stylesheet, Origin, CssRule};
 use style::values::specified;
 use style_traits::ToCss;
 
 pub struct CSSErrorReporterTest;
 
 impl ParseErrorReporter for CSSErrorReporterTest {
      fn report_error(&self, _input: &mut Parser, _position: SourcePosition, _message: &str) {
      }
      fn clone(&self) -> Box<ParseErrorReporter + Send + Sync> {
         Box::new(CSSErrorReporterTest)
      }
 }
 
-fn test_media_rule<F>(css: &str, callback: F) where F: Fn(&MediaList, &str) {
+fn test_media_rule<F>(css: &str, callback: F)
+    where F: Fn(&MediaList, &str),
+{
     let url = ServoUrl::parse("http://localhost").unwrap();
+    let css_str = css.to_owned();
     let stylesheet = Stylesheet::from_str(
         css, url, Origin::Author, Default::default(),
         None, Box::new(CSSErrorReporterTest),
         ParserContextExtraData::default());
     let mut rule_count = 0;
     media_queries(&stylesheet.rules.read().0, &mut |mq| {
         rule_count += 1;
         callback(mq, css);
     });
-    assert!(rule_count > 0);
+    assert!(rule_count > 0, css_str);
 }
 
-fn media_queries<F>(rules: &[CssRule], f: &mut F) where F: FnMut(&MediaList) {
+fn media_queries<F>(rules: &[CssRule], f: &mut F)
+    where F: FnMut(&MediaList),
+{
     for rule in rules {
         rule.with_nested_rules_and_mq(|rules, mq| {
             if let Some(mq) = mq {
                 f(mq)
             }
             media_queries(rules, f)
         })
     }
@@ -195,81 +201,81 @@ fn test_mq_or() {
 #[test]
 fn test_mq_default_expressions() {
     test_media_rule("@media (min-width: 100px) { }", |list, css| {
         assert!(list.media_queries.len() == 1, css.to_owned());
         let q = &list.media_queries[0];
         assert!(q.qualifier == None, css.to_owned());
         assert!(q.media_type == MediaQueryType::All, css.to_owned());
         assert!(q.expressions.len() == 1, css.to_owned());
-        match q.expressions[0] {
-            Expression::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))),
+        match *q.expressions[0].kind_for_testing() {
+            ExpressionKind::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))),
             _ => panic!("wrong expression type"),
         }
     });
 
     test_media_rule("@media (max-width: 43px) { }", |list, css| {
         assert!(list.media_queries.len() == 1, css.to_owned());
         let q = &list.media_queries[0];
         assert!(q.qualifier == None, css.to_owned());
         assert!(q.media_type == MediaQueryType::All, css.to_owned());
         assert!(q.expressions.len() == 1, css.to_owned());
-        match q.expressions[0] {
-            Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))),
+        match *q.expressions[0].kind_for_testing() {
+            ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))),
             _ => panic!("wrong expression type"),
         }
     });
 }
 
 #[test]
 fn test_mq_expressions() {
     test_media_rule("@media screen and (min-width: 100px) { }", |list, css| {
         assert!(list.media_queries.len() == 1, css.to_owned());
         let q = &list.media_queries[0];
         assert!(q.qualifier == None, css.to_owned());
         assert!(q.media_type == MediaQueryType::Known(MediaType::Screen), css.to_owned());
         assert!(q.expressions.len() == 1, css.to_owned());
-        match q.expressions[0] {
-            Expression::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))),
+        match *q.expressions[0].kind_for_testing() {
+            ExpressionKind::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))),
             _ => panic!("wrong expression type"),
         }
     });
 
     test_media_rule("@media print and (max-width: 43px) { }", |list, css| {
         assert!(list.media_queries.len() == 1, css.to_owned());
         let q = &list.media_queries[0];
         assert!(q.qualifier == None, css.to_owned());
         assert!(q.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned());
         assert!(q.expressions.len() == 1, css.to_owned());
-        match q.expressions[0] {
-            Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))),
+        match *q.expressions[0].kind_for_testing() {
+            ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))),
             _ => panic!("wrong expression type"),
         }
     });
 
     test_media_rule("@media print and (width: 43px) { }", |list, css| {
         assert!(list.media_queries.len() == 1, css.to_owned());
         let q = &list.media_queries[0];
         assert!(q.qualifier == None, css.to_owned());
         assert!(q.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned());
         assert!(q.expressions.len() == 1, css.to_owned());
-        match q.expressions[0] {
-            Expression::Width(Range::Eq(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))),
+        match *q.expressions[0].kind_for_testing() {
+            ExpressionKind::Width(Range::Eq(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))),
             _ => panic!("wrong expression type"),
         }
     });
 
     test_media_rule("@media fridge and (max-width: 52px) { }", |list, css| {
         assert!(list.media_queries.len() == 1, css.to_owned());
         let q = &list.media_queries[0];
         assert!(q.qualifier == None, css.to_owned());
         assert!(q.media_type == MediaQueryType::Unknown(Atom::from("fridge")), css.to_owned());
         assert!(q.expressions.len() == 1, css.to_owned());
-        match q.expressions[0] {
-            Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(52))),
+        match *q.expressions[0].kind_for_testing() {
+            ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(52))),
             _ => panic!("wrong expression type"),
         }
     });
 }
 
 #[test]
 fn test_to_css() {
   test_media_rule("@media print and (width: 43px) { }", |list, _| {
@@ -283,147 +289,86 @@ fn test_to_css() {
 #[test]
 fn test_mq_multiple_expressions() {
     test_media_rule("@media (min-width: 100px) and (max-width: 200px) { }", |list, css| {
         assert!(list.media_queries.len() == 1, css.to_owned());
         let q = &list.media_queries[0];
         assert!(q.qualifier == None, css.to_owned());
         assert!(q.media_type == MediaQueryType::All, css.to_owned());
         assert!(q.expressions.len() == 2, css.to_owned());
-        match q.expressions[0] {
-            Expression::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))),
+        match *q.expressions[0].kind_for_testing() {
+            ExpressionKind::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))),
             _ => panic!("wrong expression type"),
         }
-        match q.expressions[1] {
-            Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(200))),
+        match *q.expressions[1].kind_for_testing() {
+            ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(200))),
             _ => panic!("wrong expression type"),
         }
     });
 
     test_media_rule("@media not screen and (min-width: 100px) and (max-width: 200px) { }", |list, css| {
         assert!(list.media_queries.len() == 1, css.to_owned());
         let q = &list.media_queries[0];
         assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
         assert!(q.media_type == MediaQueryType::Known(MediaType::Screen), css.to_owned());
         assert!(q.expressions.len() == 2, css.to_owned());
-        match q.expressions[0] {
-            Expression::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))),
+        match *q.expressions[0].kind_for_testing() {
+            ExpressionKind::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))),
             _ => panic!("wrong expression type"),
         }
-        match q.expressions[1] {
-            Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(200))),
+        match *q.expressions[1].kind_for_testing() {
+            ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(200))),
             _ => panic!("wrong expression type"),
         }
     });
 }
 
 #[test]
 fn test_mq_malformed_expressions() {
-    test_media_rule("@media (min-width: 100blah) and (max-width: 200px) { }", |list, css| {
-        assert!(list.media_queries.len() == 1, css.to_owned());
-        let q = &list.media_queries[0];
-        assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
-        assert!(q.media_type == MediaQueryType::All, css.to_owned());
-        assert!(q.expressions.len() == 0, css.to_owned());
-    });
-
-    test_media_rule("@media screen and (height: 200px) { }", |list, css| {
-        assert!(list.media_queries.len() == 1, css.to_owned());
-        let q = &list.media_queries[0];
-        assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
-        assert!(q.media_type == MediaQueryType::All, css.to_owned());
-        assert!(q.expressions.len() == 0, css.to_owned());
-    });
-
-    test_media_rule("@media (min-width: 30em foo bar) {}", |list, css| {
-        assert!(list.media_queries.len() == 1, css.to_owned());
-        let q = &list.media_queries[0];
-        assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
-        assert!(q.media_type == MediaQueryType::All, css.to_owned());
-        assert!(q.expressions.len() == 0, css.to_owned());
-    });
-
-    test_media_rule("@media not {}", |list, css| {
-        assert!(list.media_queries.len() == 1, css.to_owned());
-        let q = &list.media_queries[0];
-        assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
-        assert!(q.media_type == MediaQueryType::All, css.to_owned());
-        assert!(q.expressions.len() == 0, css.to_owned());
-    });
-
-    test_media_rule("@media not (min-width: 300px) {}", |list, css| {
+    fn check_malformed_expr(list: &MediaList, css: &str) {
         assert!(list.media_queries.len() == 1, css.to_owned());
         let q = &list.media_queries[0];
         assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
         assert!(q.media_type == MediaQueryType::All, css.to_owned());
         assert!(q.expressions.len() == 0, css.to_owned());
-    });
-
-    test_media_rule("@media , {}", |list, css| {
-        assert!(list.media_queries.len() == 2, css.to_owned());
-        let q = &list.media_queries[0];
-        assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
-        assert!(q.media_type == MediaQueryType::All, css.to_owned());
-        assert!(q.expressions.len() == 0, css.to_owned());
-        let q = &list.media_queries[1];
-        assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
-        assert!(q.media_type == MediaQueryType::All, css.to_owned());
-        assert!(q.expressions.len() == 0, css.to_owned());
-    });
+    }
 
-    test_media_rule("@media screen 4px, print {}", |list, css| {
-        assert!(list.media_queries.len() == 2, css.to_owned());
-        let q0 = &list.media_queries[0];
-        assert!(q0.qualifier == Some(Qualifier::Not), css.to_owned());
-        assert!(q0.media_type == MediaQueryType::All, css.to_owned());
-        assert!(q0.expressions.len() == 0, css.to_owned());
-        let q1 = &list.media_queries[1];
-        assert!(q1.qualifier == None, css.to_owned());
-        assert!(q1.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned());
-        assert!(q1.expressions.len() == 0, css.to_owned());
-    });
-
-    test_media_rule("@media screen, {}", |list, css| {
-        assert!(list.media_queries.len() == 2, css.to_owned());
-        let q0 = &list.media_queries[0];
-        assert!(q0.qualifier == None, css.to_owned());
-        assert!(q0.media_type == MediaQueryType::Known(MediaType::Screen), css.to_owned());
-        assert!(q0.expressions.len() == 0, css.to_owned());
-        let q1 = &list.media_queries[1];
-        assert!(q1.qualifier == Some(Qualifier::Not), css.to_owned());
-        assert!(q1.media_type == MediaQueryType::All, css.to_owned());
-        assert!(q1.expressions.len() == 0, css.to_owned());
-    });
+    for rule in &[
+        "@media (min-width: 100blah) and (max-width: 200px) { }",
+        "@media screen and (height: 200px) { }",
+        "@media (min-width: 30em foo bar) {}",
+        "@media not {}",
+        "@media not (min-width: 300px) {}",
+        "@media , {}",
+        "@media screen 4px, print {}",
+        "@media screen, {}",
+    ] {
+        test_media_rule(rule, check_malformed_expr);
+    }
 }
 
 #[test]
 fn test_matching_simple() {
-    let device = Device {
-        media_type: MediaType::Screen,
-        viewport_size: TypedSize2D::new(200.0, 100.0),
-    };
+    let device = Device::new(MediaType::Screen, TypedSize2D::new(200.0, 100.0));
 
     media_query_test(&device, "@media not all { a { color: red; } }", 0);
     media_query_test(&device, "@media not screen { a { color: red; } }", 0);
     media_query_test(&device, "@media not print { a { color: red; } }", 1);
 
     media_query_test(&device, "@media unknown { a { color: red; } }", 0);
     media_query_test(&device, "@media not unknown { a { color: red; } }", 1);
 
     media_query_test(&device, "@media { a { color: red; } }", 1);
     media_query_test(&device, "@media screen { a { color: red; } }", 1);
     media_query_test(&device, "@media print { a { color: red; } }", 0);
 }
 
 #[test]
 fn test_matching_width() {
-    let device = Device {
-        media_type: MediaType::Screen,
-        viewport_size: TypedSize2D::new(200.0, 100.0),
-    };
+    let device = Device::new(MediaType::Screen, TypedSize2D::new(200.0, 100.0));
 
     media_query_test(&device, "@media { a { color: red; } }", 1);
 
     media_query_test(&device, "@media (min-width: 50px) { a { color: red; } }", 1);
     media_query_test(&device, "@media (min-width: 150px) { a { color: red; } }", 1);
     media_query_test(&device, "@media (min-width: 300px) { a { color: red; } }", 0);
 
     media_query_test(&device, "@media screen and (min-width: 50px) { a { color: red; } }", 1);
@@ -454,17 +399,14 @@ fn test_matching_width() {
     media_query_test(
         &device, "@media not screen and (min-width: 16em) and (max-width: 19.75em) { a { color: red; } }", 1);
     media_query_test(
         &device, "@media not screen and (min-width: 3em) and (max-width: 250px) { a { color: red; } }", 0);
 }
 
 #[test]
 fn test_matching_invalid() {
-    let device = Device {
-        media_type: MediaType::Screen,
-        viewport_size: TypedSize2D::new(200.0, 100.0),
-    };
+    let device = Device::new(MediaType::Screen, TypedSize2D::new(200.0, 100.0));
 
     media_query_test(&device, "@media fridge { a { color: red; } }", 0);
     media_query_test(&device, "@media screen and (height: 100px) { a { color: red; } }", 0);
     media_query_test(&device, "@media not print and (width: 100) { a { color: red; } }", 0);
 }
--- a/servo/tests/unit/style/viewport.rs
+++ b/servo/tests/unit/style/viewport.rs
@@ -283,66 +283,64 @@ fn constrain_viewport() {
 
     macro_rules! from_css {
         ($css:expr) => {
             &ViewportRule::parse(&mut Parser::new($css), &context).unwrap()
         }
     }
 
     let initial_viewport = TypedSize2D::new(800., 600.);
-    assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("")),
-               None);
+    let device = Device::new(MediaType::Screen, initial_viewport);
+    assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("")), None);
 
-    let initial_viewport = TypedSize2D::new(800., 600.);
-    assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 320px auto")),
+    assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("width: 320px auto")),
                Some(ViewportConstraints {
                    size: initial_viewport,
 
                    initial_zoom: ScaleFactor::new(1.),
                    min_zoom: None,
                    max_zoom: None,
 
                    user_zoom: UserZoom::Zoom,
                    orientation: Orientation::Auto
                }));
 
-    let initial_viewport = TypedSize2D::new(200., 150.);
-    assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 320px auto")),
-               Some(ViewportConstraints {
-                   size: TypedSize2D::new(320., 240.),
-
-                   initial_zoom: ScaleFactor::new(1.),
-                   min_zoom: None,
-                   max_zoom: None,
-
-                   user_zoom: UserZoom::Zoom,
-                   orientation: Orientation::Auto
-               }));
-
-    let initial_viewport = TypedSize2D::new(800., 600.);
-    assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 320px auto")),
+    assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("width: 320px auto")),
                Some(ViewportConstraints {
                    size: initial_viewport,
 
                    initial_zoom: ScaleFactor::new(1.),
                    min_zoom: None,
                    max_zoom: None,
 
                    user_zoom: UserZoom::Zoom,
                    orientation: Orientation::Auto
                }));
 
-    let initial_viewport = TypedSize2D::new(800., 600.);
-    assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 800px; height: 600px;\
+    assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("width: 800px; height: 600px;\
                                                                      zoom: 1;\
                                                                      user-zoom: zoom;\
                                                                      orientation: auto;")),
                Some(ViewportConstraints {
                    size: initial_viewport,
 
                    initial_zoom: ScaleFactor::new(1.),
                    min_zoom: None,
                    max_zoom: None,
 
                    user_zoom: UserZoom::Zoom,
                    orientation: Orientation::Auto
                }));
+
+    let initial_viewport = TypedSize2D::new(200., 150.);
+    let device = Device::new(MediaType::Screen, initial_viewport);
+    assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("width: 320px auto")),
+               Some(ViewportConstraints {
+                   size: TypedSize2D::new(320., 240.),
+
+                   initial_zoom: ScaleFactor::new(1.),
+                   min_zoom: None,
+                   max_zoom: None,
+
+                   user_zoom: UserZoom::Zoom,
+                   orientation: Orientation::Auto
+               }));
 }