/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! A media query list: //! //! https://drafts.csswg.org/mediaqueries/#typedef-media-query-list use super::{Device, MediaQuery, Qualifier}; use crate::context::QuirksMode; use crate::error_reporting::ContextualParseError; use crate::parser::ParserContext; use crate::queries::condition::KleeneValue; use crate::values::computed; use cssparser::{Delimiter, Parser}; use cssparser::{ParserInput, Token}; /// A type that encapsulates a media query list. #[derive(Clone, MallocSizeOf, ToCss, ToShmem)] #[css(comma, derive_debug)] pub struct MediaList { /// The list of media queries. #[css(iterable)] pub media_queries: Vec, } impl MediaList { /// Parse a media query list from CSS. /// /// Always returns a media query list. If any invalid media query is /// found, the media query list is only filled with the equivalent of /// "not all", see: /// /// pub fn parse(context: &ParserContext, input: &mut Parser) -> Self { if input.is_exhausted() { return Self::empty(); } let mut media_queries = vec![]; loop { let start_position = input.position(); match input.parse_until_before(Delimiter::Comma, |i| MediaQuery::parse(context, i)) { Ok(mq) => { media_queries.push(mq); }, Err(err) => { media_queries.push(MediaQuery::never_matching()); let location = err.location; let error = ContextualParseError::InvalidMediaRule( input.slice_from(start_position), err, ); context.log_css_error(location, error); }, } match input.next() { Ok(&Token::Comma) => {}, Ok(_) => unreachable!(), Err(_) => break, } } MediaList { media_queries } } /// Create an empty MediaList. pub fn empty() -> Self { MediaList { media_queries: vec![], } } /// Evaluate a whole `MediaList` against `Device`. pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool { // Check if it is an empty media query list or any queries match. // https://drafts.csswg.org/mediaqueries-4/#mq-list if self.media_queries.is_empty() { return true; } computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { self.media_queries.iter().any(|mq| { let mut query_match = if mq.media_type.matches(device.media_type()) { mq.condition .as_ref() .map_or(KleeneValue::True, |c| c.matches(context)) } else { KleeneValue::False }; // Apply the logical NOT qualifier to the result if matches!(mq.qualifier, Some(Qualifier::Not)) { query_match = !query_match; } query_match.to_bool(/* unknown = */ false) }) }) } /// Whether this `MediaList` contains no media queries. pub fn is_empty(&self) -> bool { self.media_queries.is_empty() } /// Whether this `MediaList` depends on the viewport size. pub fn is_viewport_dependent(&self) -> bool { self.media_queries.iter().any(|q| q.is_viewport_dependent()) } /// Append a new media query item to the media list. /// /// /// Returns true if added, false if fail to parse the medium string. pub fn append_medium(&mut self, context: &ParserContext, new_medium: &str) -> bool { let mut input = ParserInput::new(new_medium); let mut parser = Parser::new(&mut input); let new_query = match MediaQuery::parse(&context, &mut parser) { Ok(query) => query, Err(_) => { return false; }, }; // This algorithm doesn't actually matches the current spec, // but it matches the behavior of Gecko and Edge. // See https://github.com/w3c/csswg-drafts/issues/697 self.media_queries.retain(|query| query != &new_query); self.media_queries.push(new_query); true } /// Delete a media query from the media list. /// /// /// Returns true if found and deleted, false otherwise. pub fn delete_medium(&mut self, context: &ParserContext, old_medium: &str) -> bool { let mut input = ParserInput::new(old_medium); let mut parser = Parser::new(&mut input); let old_query = match MediaQuery::parse(context, &mut parser) { Ok(query) => query, Err(_) => { return false; }, }; let old_len = self.media_queries.len(); self.media_queries.retain(|query| query != &old_query); old_len != self.media_queries.len() } }