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