/* 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 condition: //! //! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition use super::{Device, MediaFeatureExpression}; use crate::context::QuirksMode; use crate::parser::ParserContext; use cssparser::{Parser, Token}; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; /// A binary `and` or `or` operator. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] #[allow(missing_docs)] pub enum Operator { And, Or, } /// Whether to allow an `or` condition or not during parsing. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss)] enum AllowOr { Yes, No, } /// Represents a media condition. #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pub enum MediaCondition { /// A simple media feature expression, implicitly parenthesized. Feature(MediaFeatureExpression), /// A negation of a condition. Not(Box), /// A set of joint operations. Operation(Box<[MediaCondition]>, Operator), /// A condition wrapped in parenthesis. InParens(Box), } impl ToCss for MediaCondition { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, { match *self { // NOTE(emilio): MediaFeatureExpression already includes the // parenthesis. MediaCondition::Feature(ref f) => f.to_css(dest), MediaCondition::Not(ref c) => { dest.write_str("not ")?; c.to_css(dest) }, MediaCondition::InParens(ref c) => { dest.write_char('(')?; c.to_css(dest)?; dest.write_char(')') }, MediaCondition::Operation(ref list, op) => { let mut iter = list.iter(); iter.next().unwrap().to_css(dest)?; for item in iter { dest.write_char(' ')?; op.to_css(dest)?; dest.write_char(' ')?; item.to_css(dest)?; } Ok(()) }, } } } impl MediaCondition { /// Parse a single media condition. pub fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { Self::parse_internal(context, input, AllowOr::Yes) } /// Parse a single media condition, disallowing `or` expressions. /// /// To be used from the legacy media query syntax. pub fn parse_disallow_or<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { Self::parse_internal(context, input, AllowOr::No) } fn parse_internal<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, allow_or: AllowOr, ) -> Result> { let location = input.current_source_location(); // FIXME(emilio): This can be cleaner with nll. let is_negation = match *input.next()? { Token::ParenthesisBlock => false, Token::Ident(ref ident) if ident.eq_ignore_ascii_case("not") => true, ref t => return Err(location.new_unexpected_token_error(t.clone())), }; if is_negation { let inner_condition = Self::parse_in_parens(context, input)?; return Ok(MediaCondition::Not(Box::new(inner_condition))); } // ParenthesisBlock. let first_condition = Self::parse_paren_block(context, input)?; let operator = match input.try_parse(Operator::parse) { Ok(op) => op, Err(..) => return Ok(first_condition), }; if allow_or == AllowOr::No && operator == Operator::Or { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } let mut conditions = vec![]; conditions.push(first_condition); conditions.push(Self::parse_in_parens(context, input)?); let delim = match operator { Operator::And => "and", Operator::Or => "or", }; loop { if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() { return Ok(MediaCondition::Operation( conditions.into_boxed_slice(), operator, )); } conditions.push(Self::parse_in_parens(context, input)?); } } /// Parse a media condition in parentheses. pub fn parse_in_parens<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { input.expect_parenthesis_block()?; Self::parse_paren_block(context, input) } fn parse_paren_block<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { input.parse_nested_block(|input| { // Base case. if let Ok(inner) = input.try_parse(|i| Self::parse(context, i)) { return Ok(MediaCondition::InParens(Box::new(inner))); } let expr = MediaFeatureExpression::parse_in_parenthesis_block(context, input)?; Ok(MediaCondition::Feature(expr)) }) } /// Whether this condition matches the device and quirks mode. pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool { match *self { MediaCondition::Feature(ref f) => f.matches(device, quirks_mode), MediaCondition::InParens(ref c) => c.matches(device, quirks_mode), MediaCondition::Not(ref c) => !c.matches(device, quirks_mode), MediaCondition::Operation(ref conditions, op) => { let mut iter = conditions.iter(); match op { Operator::And => iter.all(|c| c.matches(device, quirks_mode)), Operator::Or => iter.any(|c| c.matches(device, quirks_mode)), } }, } } }