diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /servo/components/style/media_queries/media_condition.rs | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servo/components/style/media_queries/media_condition.rs')
-rw-r--r-- | servo/components/style/media_queries/media_condition.rs | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/servo/components/style/media_queries/media_condition.rs b/servo/components/style/media_queries/media_condition.rs new file mode 100644 index 0000000000..f735704556 --- /dev/null +++ b/servo/components/style/media_queries/media_condition.rs @@ -0,0 +1,185 @@ +/* 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<MediaCondition>), + /// A set of joint operations. + Operation(Box<[MediaCondition]>, Operator), + /// A condition wrapped in parenthesis. + InParens(Box<MediaCondition>), +} + +impl ToCss for MediaCondition { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> 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, ParseError<'i>> { + 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, ParseError<'i>> { + Self::parse_internal(context, input, AllowOr::No) + } + + fn parse_internal<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + allow_or: AllowOr, + ) -> Result<Self, ParseError<'i>> { + 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<Self, ParseError<'i>> { + input.expect_parenthesis_block()?; + Self::parse_paren_block(context, input) + } + + fn parse_paren_block<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + 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)), + } + }, + } + } +} |