vendor servo commit d3875f6ec52fdb6bbe49719af6bff299c792ae0b draft
authorVCS Sync <vcs-sync@mozilla.com>
Fri, 30 Dec 2016 15:01:30 +0000
changeset 458234 72e3502af23a9eeba6d1dbc2325dc502d992106b
parent 458233 1a07d21e2b3c995341e5c5c56c100cf5de6d83ca
child 458235 a95447d95bfb6d7947699c6720baac6822a59826
push id40933
push userbmo:slyu@mozilla.com
push dateTue, 10 Jan 2017 06:53:24 +0000
milestone53.0a1
vendor servo commit d3875f6ec52fdb6bbe49719af6bff299c792ae0b
servo/components/style/media_queries.rs
servo/tests/unit/style/media_queries.rs
--- a/servo/components/style/media_queries.rs
+++ b/servo/components/style/media_queries.rs
@@ -6,17 +6,18 @@
 //!
 //! [mq]: https://drafts.csswg.org/mediaqueries/
 
 use Atom;
 use app_units::Au;
 use cssparser::{Delimiter, Parser, Token};
 use euclid::size::{Size2D, TypedSize2D};
 use serialize_comma_separated_list;
-use std::fmt::{self, Write};
+use std::ascii::AsciiExt;
+use std::fmt;
 use style_traits::{ToCss, ViewportPx};
 use values::computed::{self, ToComputedValue};
 use values::specified;
 
 
 #[derive(Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct MediaList {
@@ -80,88 +81,148 @@ pub enum Expression {
 /// http://dev.w3.org/csswg/mediaqueries-3/#media0
 #[derive(PartialEq, Eq, Copy, Clone, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum Qualifier {
     Only,
     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"),
+        }
+    }
+}
+
 #[derive(Clone, Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct MediaQuery {
     pub qualifier: Option<Qualifier>,
     pub media_type: MediaQueryType,
     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,
                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
     {
-        if self.qualifier == Some(Qualifier::Not) {
-            try!(write!(dest, "not "));
+        if let Some(qual) = self.qualifier {
+            try!(qual.to_css(dest));
+            try!(write!(dest, " "));
         }
 
-        let mut type_ = String::new();
         match self.media_type {
-            MediaQueryType::All => try!(write!(type_, "all")),
-            MediaQueryType::MediaType(MediaType::Screen) => try!(write!(type_, "screen")),
-            MediaQueryType::MediaType(MediaType::Print) => try!(write!(type_, "print")),
-            MediaQueryType::MediaType(MediaType::Unknown(ref desc)) => try!(write!(type_, "{}", desc)),
-        };
+            MediaQueryType::All => {
+                // We need to print "all" if there's a qualifier, or there's
+                // just an empty list of expressions.
+                //
+                // Otherwise, we'd serialize media queries like "(min-width:
+                // 40px)" in "all (min-width: 40px)", which is unexpected.
+                if self.qualifier.is_some() || self.expressions.is_empty() {
+                    try!(write!(dest, "all"));
+                }
+            },
+            MediaQueryType::Known(MediaType::Screen) => try!(write!(dest, "screen")),
+            MediaQueryType::Known(MediaType::Print) => try!(write!(dest, "print")),
+            MediaQueryType::Unknown(ref desc) => try!(write!(dest, "{}", desc)),
+        }
+
         if self.expressions.is_empty() {
-            return write!(dest, "{}", type_)
-        } else if type_ != "all" || self.qualifier == Some(Qualifier::Not) {
-            try!(write!(dest, "{} and ", type_));
+            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));
-            if i == self.expressions.len() - 1 {
-                try!(write!(dest, ")"));
-            } else {
-                try!(write!(dest, ") and "));
+            try!(write!(dest, ")"));
+            if i != self.expressions.len() - 1 {
+                try!(write!(dest, " and "));
             }
         }
         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
-    MediaType(MediaType),
+    Known(MediaType),
+    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 {
+        match *self {
+            MediaQueryType::All => true,
+            MediaQueryType::Known(ref known_type) => known_type == other,
+            MediaQueryType::Unknown(..) => false,
+        }
+    }
 }
 
 #[derive(PartialEq, Eq, Clone, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum MediaType {
     Screen,
     Print,
-    Unknown(Atom),
+}
+
+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>,
 }
@@ -212,33 +273,30 @@ impl MediaQuery {
         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 {
             None
         };
 
-        let media_type;
-        if let Ok(ident) = input.try(|input| input.expect_ident()) {
-            media_type = match_ignore_ascii_case! { ident,
-                "screen" => MediaQueryType::MediaType(MediaType::Screen),
-                "print" => MediaQueryType::MediaType(MediaType::Print),
-                "all" => MediaQueryType::All,
-                _ => MediaQueryType::MediaType(MediaType::Unknown(Atom::from(&*ident)))
+        let media_type = match input.try(|input| input.expect_ident()) {
+            Ok(ident) => MediaQueryType::parse(&*ident),
+            Err(()) => {
+                // Media type is only optional if qualifier is not specified.
+                if qualifier.is_some() {
+                    return Err(())
+                }
+
+                // Without a media type, require at least one expression.
+                expressions.push(try!(Expression::parse(input)));
+
+                MediaQueryType::All
             }
-        } else {
-            // Media type is only optional if qualifier is not specified.
-            if qualifier.is_some() {
-                return Err(())
-            }
-            media_type = MediaQueryType::All;
-            // Without a media type, require at least one expression
-            expressions.push(try!(Expression::parse(input)));
-        }
+        };
 
         // Parse any subsequent expressions
         loop {
             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)))
         }
@@ -248,20 +306,18 @@ impl MediaQuery {
 pub fn parse_media_query_list(input: &mut Parser) -> MediaList {
     if input.is_exhausted() {
         return Default::default()
     }
 
     let mut media_queries = vec![];
     loop {
         media_queries.push(
-            input.parse_until_before(Delimiter::Comma, MediaQuery::parse)
-                 .unwrap_or(MediaQuery::new(Some(Qualifier::Not),
-                                            MediaQueryType::All,
-                                            vec!())));
+            input.parse_until_before(Delimiter::Comma, MediaQuery::parse).ok()
+                 .unwrap_or_else(MediaQuery::never_matching));
         match input.next() {
             Ok(Token::Comma) => {},
             Ok(_) => unreachable!(),
             Err(()) => break,
         }
     }
     MediaList {
         media_queries: media_queries,
@@ -270,22 +326,17 @@ pub fn parse_media_query_list(input: &mu
 
 impl MediaList {
     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| {
-            // Check if media matches. Unknown media never matches.
-            let media_match = match mq.media_type {
-                MediaQueryType::MediaType(MediaType::Unknown(_)) => false,
-                MediaQueryType::MediaType(ref media_type) => *media_type == device.media_type,
-                MediaQueryType::All => true,
-            };
+            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).evaluate(viewport_size.width),
                 }
             });
--- a/servo/tests/unit/style/media_queries.rs
+++ b/servo/tests/unit/style/media_queries.rs
@@ -69,87 +69,87 @@ fn test_mq_empty() {
 }
 
 #[test]
 fn test_mq_screen() {
     test_media_rule("@media screen { }", |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::MediaType(MediaType::Screen), css.to_owned());
+        assert!(q.media_type == MediaQueryType::Known(MediaType::Screen), css.to_owned());
         assert!(q.expressions.len() == 0, css.to_owned());
     });
 
     test_media_rule("@media only screen { }", |list, css| {
         assert!(list.media_queries.len() == 1, css.to_owned());
         let q = &list.media_queries[0];
         assert!(q.qualifier == Some(Qualifier::Only), css.to_owned());
-        assert!(q.media_type == MediaQueryType::MediaType(MediaType::Screen), css.to_owned());
+        assert!(q.media_type == MediaQueryType::Known(MediaType::Screen), css.to_owned());
         assert!(q.expressions.len() == 0, css.to_owned());
     });
 
     test_media_rule("@media not screen { }", |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::MediaType(MediaType::Screen), css.to_owned());
+        assert!(q.media_type == MediaQueryType::Known(MediaType::Screen), css.to_owned());
         assert!(q.expressions.len() == 0, css.to_owned());
     });
 }
 
 #[test]
 fn test_mq_print() {
     test_media_rule("@media print { }", |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::MediaType(MediaType::Print), css.to_owned());
+        assert!(q.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned());
         assert!(q.expressions.len() == 0, css.to_owned());
     });
 
     test_media_rule("@media only print { }", |list, css| {
         assert!(list.media_queries.len() == 1, css.to_owned());
         let q = &list.media_queries[0];
         assert!(q.qualifier == Some(Qualifier::Only), css.to_owned());
-        assert!(q.media_type == MediaQueryType::MediaType(MediaType::Print), css.to_owned());
+        assert!(q.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned());
         assert!(q.expressions.len() == 0, css.to_owned());
     });
 
     test_media_rule("@media not print { }", |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::MediaType(MediaType::Print), css.to_owned());
+        assert!(q.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned());
         assert!(q.expressions.len() == 0, css.to_owned());
     });
 }
 
 #[test]
 fn test_mq_unknown() {
     test_media_rule("@media fridge { }", |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::MediaType(MediaType::Unknown(Atom::from("fridge"))), css.to_owned());
+        assert!(q.media_type == MediaQueryType::Unknown(Atom::from("fridge")), css.to_owned());
         assert!(q.expressions.len() == 0, css.to_owned());
     });
 
     test_media_rule("@media only glass { }", |list, css| {
         assert!(list.media_queries.len() == 1, css.to_owned());
         let q = &list.media_queries[0];
         assert!(q.qualifier == Some(Qualifier::Only), css.to_owned());
-        assert!(q.media_type == MediaQueryType::MediaType(MediaType::Unknown(Atom::from("glass"))), css.to_owned());
+        assert!(q.media_type == MediaQueryType::Unknown(Atom::from("glass")), css.to_owned());
         assert!(q.expressions.len() == 0, css.to_owned());
     });
 
     test_media_rule("@media not wood { }", |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::MediaType(MediaType::Unknown(Atom::from("wood"))), css.to_owned());
+        assert!(q.media_type == MediaQueryType::Unknown(Atom::from("wood")), css.to_owned());
         assert!(q.expressions.len() == 0, css.to_owned());
     });
 }
 
 #[test]
 fn test_mq_all() {
     test_media_rule("@media all { }", |list, css| {
         assert!(list.media_queries.len() == 1, css.to_owned());
@@ -177,22 +177,22 @@ fn test_mq_all() {
 }
 
 #[test]
 fn test_mq_or() {
     test_media_rule("@media screen, print { }", |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::MediaType(MediaType::Screen), 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 == None, css.to_owned());
-        assert!(q1.media_type == MediaQueryType::MediaType(MediaType::Print), css.to_owned());
+        assert!(q1.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned());
         assert!(q1.expressions.len() == 0, css.to_owned());
     });
 }
 
 #[test]
 fn test_mq_default_expressions() {
     test_media_rule("@media (min-width: 100px) { }", |list, css| {
         assert!(list.media_queries.len() == 1, css.to_owned());
@@ -220,53 +220,53 @@ fn test_mq_default_expressions() {
 }
 
 #[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::MediaType(MediaType::Screen), 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))),
             _ => 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::MediaType(MediaType::Print), 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))),
             _ => 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::MediaType(MediaType::Print), 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))),
             _ => 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::MediaType(MediaType::Unknown(Atom::from("fridge"))), 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))),
             _ => panic!("wrong expression type"),
         }
     });
 }
 
@@ -297,17 +297,17 @@ fn test_mq_multiple_expressions() {
             _ => 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::MediaType(MediaType::Screen), 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))),
             _ => panic!("wrong expression type"),
         }
         match q.expressions[1] {
             Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(200))),
             _ => panic!("wrong expression type"),
@@ -372,25 +372,25 @@ fn test_mq_malformed_expressions() {
     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::MediaType(MediaType::Print), 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::MediaType(MediaType::Screen), 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());
     });
 }