1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
/* 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::values::computed;
use cssparser::{Delimiter, Parser};
use cssparser::{ParserInput, Token};
use selectors::kleene_value::KleeneValue;
/// 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<MediaQuery>,
}
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:
///
/// <https://drafts.csswg.org/mediaqueries/#error-handling>
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.
/// <https://drafts.csswg.org/cssom/#dom-medialist-appendmedium>
///
/// 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.
/// <https://drafts.csswg.org/cssom/#dom-medialist-deletemedium>
///
/// 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()
}
}