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 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
/* 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:
//!
//! https://drafts.csswg.org/mediaqueries/#typedef-media-query
use crate::parser::ParserContext;
use crate::queries::{FeatureFlags, FeatureType, QueryCondition};
use crate::str::string_as_ascii_lowercase;
use crate::values::CustomIdent;
use crate::Atom;
use cssparser::Parser;
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, ToCss};
/// <https://drafts.csswg.org/mediaqueries/#mq-prefix>
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
pub enum Qualifier {
/// Hide a media query from legacy UAs:
/// <https://drafts.csswg.org/mediaqueries/#mq-only>
Only,
/// Negate a media query:
/// <https://drafts.csswg.org/mediaqueries/#mq-not>
Not,
}
/// <https://drafts.csswg.org/mediaqueries/#media-types>
#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
pub struct MediaType(pub CustomIdent);
impl MediaType {
/// The `screen` media type.
pub fn screen() -> Self {
MediaType(CustomIdent(atom!("screen")))
}
/// The `print` media type.
pub fn print() -> Self {
MediaType(CustomIdent(atom!("print")))
}
fn parse(name: &str) -> Result<Self, ()> {
// From https://drafts.csswg.org/mediaqueries/#mq-syntax:
//
// The <media-type> production does not include the keywords only, not, and, or, and layer.
//
// Here we also perform the to-ascii-lowercase part of the serialization
// algorithm: https://drafts.csswg.org/cssom/#serializing-media-queries
match_ignore_ascii_case! { name,
"not" | "or" | "and" | "only" | "layer" => Err(()),
_ => Ok(MediaType(CustomIdent(Atom::from(string_as_ascii_lowercase(name))))),
}
}
}
/// A [media query][mq].
///
/// [mq]: https://drafts.csswg.org/mediaqueries/
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub struct MediaQuery {
/// The qualifier for this query.
pub qualifier: Option<Qualifier>,
/// The media type for this query, that can be known, unknown, or "all".
pub media_type: MediaQueryType,
/// The condition that this media query contains. This cannot have `or`
/// in the first level.
pub condition: Option<QueryCondition>,
}
impl ToCss for MediaQuery {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if let Some(qual) = self.qualifier {
qual.to_css(dest)?;
dest.write_char(' ')?;
}
match self.media_type {
MediaQueryType::All => {
// We need to print "all" if there's a qualifier, or there's
// just an empty list of expressions.
//
// Otherwise, we'd serialize media queries like "(min-width:
// 40px)" in "all (min-width: 40px)", which is unexpected.
if self.qualifier.is_some() || self.condition.is_none() {
dest.write_str("all")?;
}
},
MediaQueryType::Concrete(MediaType(ref desc)) => desc.to_css(dest)?,
}
let condition = match self.condition {
Some(ref c) => c,
None => return Ok(()),
};
if self.media_type != MediaQueryType::All || self.qualifier.is_some() {
dest.write_str(" and ")?;
}
condition.to_css(dest)
}
}
impl MediaQuery {
/// Return a media query that never matches, used for when we fail to parse
/// a given media query.
pub fn never_matching() -> Self {
Self {
qualifier: Some(Qualifier::Not),
media_type: MediaQueryType::All,
condition: None,
}
}
/// Returns whether this media query depends on the viewport.
pub fn is_viewport_dependent(&self) -> bool {
self.condition.as_ref().map_or(false, |c| {
return c
.cumulative_flags()
.contains(FeatureFlags::VIEWPORT_DEPENDENT);
})
}
/// Parse a media query given css input.
///
/// Returns an error if any of the expressions is unknown.
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let (qualifier, explicit_media_type) = input
.try_parse(|input| -> Result<_, ()> {
let qualifier = input.try_parse(Qualifier::parse).ok();
let ident = input.expect_ident().map_err(|_| ())?;
let media_type = MediaQueryType::parse(&ident)?;
Ok((qualifier, Some(media_type)))
})
.unwrap_or_default();
let condition = if explicit_media_type.is_none() {
Some(QueryCondition::parse(context, input, FeatureType::Media)?)
} else if input.try_parse(|i| i.expect_ident_matching("and")).is_ok() {
Some(QueryCondition::parse_disallow_or(
context,
input,
FeatureType::Media,
)?)
} else {
None
};
let media_type = explicit_media_type.unwrap_or(MediaQueryType::All);
Ok(Self {
qualifier,
media_type,
condition,
})
}
}
/// <http://dev.w3.org/csswg/mediaqueries-3/#media0>
#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
pub enum MediaQueryType {
/// A media type that matches every device.
All,
/// A specific media type.
Concrete(MediaType),
}
impl MediaQueryType {
fn parse(ident: &str) -> Result<Self, ()> {
match_ignore_ascii_case! { ident,
"all" => return Ok(MediaQueryType::All),
_ => (),
};
// If parseable, accept this type as a concrete type.
MediaType::parse(ident).map(MediaQueryType::Concrete)
}
/// Returns whether this media query type matches a MediaType.
pub fn matches(&self, other: MediaType) -> bool {
match *self {
MediaQueryType::All => true,
MediaQueryType::Concrete(ref known_type) => *known_type == other,
}
}
}