style/media_queries/
media_query.rs

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
9use crate::derives::*;
10use crate::parser::ParserContext;
11use crate::queries::{FeatureFlags, FeatureType, QueryCondition};
12use crate::str::string_as_ascii_lowercase;
13use crate::values::CustomIdent;
14use crate::Atom;
15use cssparser::{match_ignore_ascii_case, Parser};
16use std::fmt::{self, Write};
17use 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)]
21pub 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)]
32pub struct MediaType(pub CustomIdent);
33
34impl 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)]
63pub 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
73impl 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
110impl 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)]
169pub enum MediaQueryType {
170    /// A media type that matches every device.
171    All,
172    /// A specific media type.
173    Concrete(MediaType),
174}
175
176impl 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}