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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// This file is part of css. It is subject to the license terms in the COPYRIGHT file found in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT. No part of predicator, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the COPYRIGHT file.
// Copyright © 2017 The developers of css. See the COPYRIGHT file in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT.
use {
super::{Device, MediaQuery, Qualifier},
crate::{parsers::ParserContext, CustomParseError},
cssparser::{Delimiter, ParseError, Parser, ParserInput, ToCss, Token},
std::fmt,
};
/// A type that encapsulates a media query list.
#[derive(Debug, Clone)]
pub struct MediaList {
/// The list of media queries.
pub media_queries: Vec<MediaQuery>,
}
impl ToCss for MediaList {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
if self.media_queries.is_empty() {
return Ok(());
}
let mut iterator = self.media_queries.iter();
iterator.next().unwrap().to_css(dest)?;
for mediaQuery in iterator {
dest.write_char(',')?;
mediaQuery.to_css(dest)?;
}
Ok(())
}
}
impl MediaList {
/// Is this media list empty?
pub fn is_not_empty(&self) -> bool {
!self.media_queries.is_empty()
}
/// Create an empty MediaList.
pub fn empty() -> Self {
Self {
media_queries: vec![],
}
}
/// 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(crate) fn parse_media_query_list<'i: 't, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allowInvalidMediaQueries: bool,
) -> Result<MediaList, ParseError<'i, CustomParseError<'i>>> {
if input.is_exhausted() {
return Ok(MediaList::empty());
}
let mut media_queries = vec![];
loop {
match input.parse_until_before(Delimiter::Comma, |i| {
MediaQuery::parse(context, i)
}) {
Ok(mediaQuery) => media_queries.push(mediaQuery),
Err(error) => {
if !allowInvalidMediaQueries {
return Err(error);
}
media_queries.push(MediaQuery::never_matching());
}
}
match input.next() {
Ok(&Token::Comma) => {}
Ok(_) => unreachable!(),
Err(_) => break,
}
}
Ok(MediaList { media_queries })
}
/// Evaluate a whole `MediaList` against `Device`.
pub fn evaluate<D: Device>(&self, device: &D) -> bool {
// Check if it is an empty media query list or any queries match (OR condition)
// https://drafts.csswg.org/mediaqueries-4/#mq-list
self.is_empty()
|| self.media_queries.iter().any(|mediaQuery| {
use super::MediaQueryType::*;
let media_match = match mediaQuery.media_type {
All => true,
Concrete(mediaType) => device.mediaTypeMatches(mediaType),
};
// Check if all conditions match (AND condition)
let query_match = media_match
&& mediaQuery
.expressions
.iter()
.all(|expression| expression.matches(device));
// Apply the logical NOT qualifier to the result
match mediaQuery.qualifier {
Some(Qualifier::Not) => !query_match,
_ => query_match,
}
})
}
/// Whether this `MediaList` contains no media queries.
pub fn is_empty(&self) -> bool {
self.media_queries.is_empty()
}
/// Append a new media query item to the media list.
/// <https://drafts.csswg.org/cssom/#dom-medialist-appendmedium>
///
/// Returns true if added, false if failed 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()
}
}