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