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