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