media_list.rs (5848B)
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 9 use super::{Device, MediaQuery, Qualifier}; 10 use crate::context::QuirksMode; 11 use crate::derives::*; 12 use crate::error_reporting::ContextualParseError; 13 use crate::parser::ParserContext; 14 use crate::stylesheets::CustomMediaEvaluator; 15 use crate::values::computed; 16 use cssparser::{Delimiter, Parser}; 17 use cssparser::{ParserInput, Token}; 18 use 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)] 23 pub struct MediaList { 24 /// The list of media queries. 25 #[css(iterable)] 26 pub media_queries: Vec<MediaQuery>, 27 } 28 29 impl 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 }