Source code

Revision control

Copy as Markdown

Other Tools

/* 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:
//!
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};
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
pub enum Qualifier {
/// Hide a media query from legacy UAs:
Only,
/// Negate a media query:
Not,
}
#[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, ()> {
//
// 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
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].
///
#[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,
})
}
}
#[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,
}
}
}