media_query.rs (6424B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! A media query: 6 //! 7 //! https://drafts.csswg.org/mediaqueries/#typedef-media-query 8 9 use crate::derives::*; 10 use crate::parser::ParserContext; 11 use crate::queries::{FeatureFlags, FeatureType, QueryCondition}; 12 use crate::str::string_as_ascii_lowercase; 13 use crate::values::CustomIdent; 14 use crate::Atom; 15 use cssparser::{match_ignore_ascii_case, Parser}; 16 use std::fmt::{self, Write}; 17 use style_traits::{CssWriter, ParseError, ToCss}; 18 19 /// <https://drafts.csswg.org/mediaqueries/#mq-prefix> 20 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] 21 pub enum Qualifier { 22 /// Hide a media query from legacy UAs: 23 /// <https://drafts.csswg.org/mediaqueries/#mq-only> 24 Only, 25 /// Negate a media query: 26 /// <https://drafts.csswg.org/mediaqueries/#mq-not> 27 Not, 28 } 29 30 /// <https://drafts.csswg.org/mediaqueries/#media-types> 31 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] 32 pub struct MediaType(pub CustomIdent); 33 34 impl MediaType { 35 /// The `screen` media type. 36 pub fn screen() -> Self { 37 MediaType(CustomIdent(atom!("screen"))) 38 } 39 40 /// The `print` media type. 41 pub fn print() -> Self { 42 MediaType(CustomIdent(atom!("print"))) 43 } 44 45 fn parse(name: &str) -> Result<Self, ()> { 46 // From https://drafts.csswg.org/mediaqueries/#mq-syntax: 47 // 48 // The <media-type> production does not include the keywords only, not, and, or, and layer. 49 // 50 // Here we also perform the to-ascii-lowercase part of the serialization 51 // algorithm: https://drafts.csswg.org/cssom/#serializing-media-queries 52 match_ignore_ascii_case! { name, 53 "not" | "or" | "and" | "only" | "layer" => Err(()), 54 _ => Ok(MediaType(CustomIdent(Atom::from(string_as_ascii_lowercase(name))))), 55 } 56 } 57 } 58 59 /// A [media query][mq]. 60 /// 61 /// [mq]: https://drafts.csswg.org/mediaqueries/ 62 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] 63 pub struct MediaQuery { 64 /// The qualifier for this query. 65 pub qualifier: Option<Qualifier>, 66 /// The media type for this query, that can be known, unknown, or "all". 67 pub media_type: MediaQueryType, 68 /// The condition that this media query contains. This cannot have `or` 69 /// in the first level. 70 pub condition: Option<QueryCondition>, 71 } 72 73 impl ToCss for MediaQuery { 74 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 75 where 76 W: Write, 77 { 78 if let Some(qual) = self.qualifier { 79 qual.to_css(dest)?; 80 dest.write_char(' ')?; 81 } 82 83 match self.media_type { 84 MediaQueryType::All => { 85 // We need to print "all" if there's a qualifier, or there's 86 // just an empty list of expressions. 87 // 88 // Otherwise, we'd serialize media queries like "(min-width: 89 // 40px)" in "all (min-width: 40px)", which is unexpected. 90 if self.qualifier.is_some() || self.condition.is_none() { 91 dest.write_str("all")?; 92 } 93 }, 94 MediaQueryType::Concrete(MediaType(ref desc)) => desc.to_css(dest)?, 95 } 96 97 let condition = match self.condition { 98 Some(ref c) => c, 99 None => return Ok(()), 100 }; 101 102 if self.media_type != MediaQueryType::All || self.qualifier.is_some() { 103 dest.write_str(" and ")?; 104 } 105 106 condition.to_css(dest) 107 } 108 } 109 110 impl MediaQuery { 111 /// Return a media query that never matches, used for when we fail to parse 112 /// a given media query. 113 pub fn never_matching() -> Self { 114 Self { 115 qualifier: Some(Qualifier::Not), 116 media_type: MediaQueryType::All, 117 condition: None, 118 } 119 } 120 121 /// Returns whether this media query depends on the viewport. 122 pub fn is_viewport_dependent(&self) -> bool { 123 self.condition.as_ref().map_or(false, |c| { 124 return c 125 .cumulative_flags() 126 .contains(FeatureFlags::VIEWPORT_DEPENDENT); 127 }) 128 } 129 130 /// Parse a media query given css input. 131 /// 132 /// Returns an error if any of the expressions is unknown. 133 pub fn parse<'i, 't>( 134 context: &ParserContext, 135 input: &mut Parser<'i, 't>, 136 ) -> Result<Self, ParseError<'i>> { 137 let (qualifier, explicit_media_type) = input 138 .try_parse(|input| -> Result<_, ()> { 139 let qualifier = input.try_parse(Qualifier::parse).ok(); 140 let ident = input.expect_ident().map_err(|_| ())?; 141 let media_type = MediaQueryType::parse(&ident)?; 142 Ok((qualifier, Some(media_type))) 143 }) 144 .unwrap_or_default(); 145 146 let condition = if explicit_media_type.is_none() { 147 Some(QueryCondition::parse(context, input, FeatureType::Media)?) 148 } else if input.try_parse(|i| i.expect_ident_matching("and")).is_ok() { 149 Some(QueryCondition::parse_disallow_or( 150 context, 151 input, 152 FeatureType::Media, 153 )?) 154 } else { 155 None 156 }; 157 158 let media_type = explicit_media_type.unwrap_or(MediaQueryType::All); 159 Ok(Self { 160 qualifier, 161 media_type, 162 condition, 163 }) 164 } 165 } 166 167 /// <http://dev.w3.org/csswg/mediaqueries-3/#media0> 168 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] 169 pub enum MediaQueryType { 170 /// A media type that matches every device. 171 All, 172 /// A specific media type. 173 Concrete(MediaType), 174 } 175 176 impl MediaQueryType { 177 fn parse(ident: &str) -> Result<Self, ()> { 178 match_ignore_ascii_case! { ident, 179 "all" => return Ok(MediaQueryType::All), 180 _ => (), 181 }; 182 183 // If parseable, accept this type as a concrete type. 184 MediaType::parse(ident).map(MediaQueryType::Concrete) 185 } 186 187 /// Returns whether this media query type matches a MediaType. 188 pub fn matches(&self, other: MediaType) -> bool { 189 match *self { 190 MediaQueryType::All => true, 191 MediaQueryType::Concrete(ref known_type) => *known_type == other, 192 } 193 } 194 }