Skip to main content

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