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