style/media_queries/
media_list.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 list:
6//!
7//! https://drafts.csswg.org/mediaqueries/#typedef-media-query-list
8
9use super::{Device, MediaQuery, Qualifier};
10use crate::context::QuirksMode;
11use crate::error_reporting::ContextualParseError;
12use crate::parser::ParserContext;
13use crate::values::computed;
14use cssparser::{Delimiter, Parser};
15use cssparser::{ParserInput, Token};
16use selectors::kleene_value::KleeneValue;
17
18/// A type that encapsulates a media query list.
19#[derive(Clone, MallocSizeOf, ToCss, ToShmem)]
20#[css(comma, derive_debug)]
21pub struct MediaList {
22    /// The list of media queries.
23    #[css(iterable)]
24    pub media_queries: Vec<MediaQuery>,
25}
26
27impl MediaList {
28    /// Parse a media query list from CSS.
29    ///
30    /// Always returns a media query list. If any invalid media query is
31    /// found, the media query list is only filled with the equivalent of
32    /// "not all", see:
33    ///
34    /// <https://drafts.csswg.org/mediaqueries/#error-handling>
35    pub fn parse(context: &ParserContext, input: &mut Parser) -> Self {
36        if input.is_exhausted() {
37            return Self::empty();
38        }
39
40        let mut media_queries = vec![];
41        loop {
42            let start_position = input.position();
43            match input.parse_until_before(Delimiter::Comma, |i| MediaQuery::parse(context, i)) {
44                Ok(mq) => {
45                    media_queries.push(mq);
46                },
47                Err(err) => {
48                    media_queries.push(MediaQuery::never_matching());
49                    let location = err.location;
50                    let error = ContextualParseError::InvalidMediaRule(
51                        input.slice_from(start_position),
52                        err,
53                    );
54                    context.log_css_error(location, error);
55                },
56            }
57
58            match input.next() {
59                Ok(&Token::Comma) => {},
60                Ok(_) => unreachable!(),
61                Err(_) => break,
62            }
63        }
64
65        MediaList { media_queries }
66    }
67
68    /// Create an empty MediaList.
69    pub fn empty() -> Self {
70        MediaList {
71            media_queries: vec![],
72        }
73    }
74
75    /// Evaluate a whole `MediaList` against `Device`.
76    pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
77        // Check if it is an empty media query list or any queries match.
78        // https://drafts.csswg.org/mediaqueries-4/#mq-list
79        if self.media_queries.is_empty() {
80            return true;
81        }
82
83        computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
84            self.media_queries.iter().any(|mq| {
85                let mut query_match = if mq.media_type.matches(device.media_type()) {
86                    mq.condition
87                        .as_ref()
88                        .map_or(KleeneValue::True, |c| c.matches(context))
89                } else {
90                    KleeneValue::False
91                };
92
93                // Apply the logical NOT qualifier to the result
94                if matches!(mq.qualifier, Some(Qualifier::Not)) {
95                    query_match = !query_match;
96                }
97                query_match.to_bool(/* unknown = */ false)
98            })
99        })
100    }
101
102    /// Whether this `MediaList` contains no media queries.
103    pub fn is_empty(&self) -> bool {
104        self.media_queries.is_empty()
105    }
106
107    /// Whether this `MediaList` depends on the viewport size.
108    pub fn is_viewport_dependent(&self) -> bool {
109        self.media_queries.iter().any(|q| q.is_viewport_dependent())
110    }
111
112    /// Append a new media query item to the media list.
113    /// <https://drafts.csswg.org/cssom/#dom-medialist-appendmedium>
114    ///
115    /// Returns true if added, false if fail to parse the medium string.
116    pub fn append_medium(&mut self, context: &ParserContext, new_medium: &str) -> bool {
117        let mut input = ParserInput::new(new_medium);
118        let mut parser = Parser::new(&mut input);
119        let new_query = match MediaQuery::parse(&context, &mut parser) {
120            Ok(query) => query,
121            Err(_) => {
122                return false;
123            },
124        };
125        // This algorithm doesn't actually matches the current spec,
126        // but it matches the behavior of Gecko and Edge.
127        // See https://github.com/w3c/csswg-drafts/issues/697
128        self.media_queries.retain(|query| query != &new_query);
129        self.media_queries.push(new_query);
130        true
131    }
132
133    /// Delete a media query from the media list.
134    /// <https://drafts.csswg.org/cssom/#dom-medialist-deletemedium>
135    ///
136    /// Returns true if found and deleted, false otherwise.
137    pub fn delete_medium(&mut self, context: &ParserContext, old_medium: &str) -> bool {
138        let mut input = ParserInput::new(old_medium);
139        let mut parser = Parser::new(&mut input);
140        let old_query = match MediaQuery::parse(context, &mut parser) {
141            Ok(query) => query,
142            Err(_) => {
143                return false;
144            },
145        };
146        let old_len = self.media_queries.len();
147        self.media_queries.retain(|query| query != &old_query);
148        old_len != self.media_queries.len()
149    }
150}