/* 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}; /// #[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 { // From https://drafts.csswg.org/mediaqueries/#mq-syntax: // // The 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, /// 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, } impl ToCss for MediaQuery { fn to_css(&self, dest: &mut CssWriter) -> 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> { 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 { 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, } } }