style: Add parsing for <general-enclosed> in queries conditions

See https://drafts.csswg.org/mediaqueries-5/#typedef-general-enclosed

Differential Revision: https://phabricator.services.mozilla.com/D158662
This commit is contained in:
Ziran Sun 2022-11-30 10:19:07 +00:00 committed by Martin Robinson
parent a298c296e4
commit bee44a5259
7 changed files with 164 additions and 30 deletions

View file

@ -13,6 +13,7 @@ use crate::values::computed;
use cssparser::{Parser, Token};
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
use core::ops::Not;
/// A binary `and` or `or` operator.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
@ -29,6 +30,29 @@ enum AllowOr {
No,
}
/// https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss)]
pub enum KleeneValue {
/// True
True,
/// False
False,
/// Either true or false, but were not sure which yet.
Unknown,
}
impl Not for KleeneValue {
type Output = Self;
fn not(self) -> Self {
match self {
Self::True => Self::False,
Self::False => Self::True,
Self::Unknown => Self::Unknown,
}
}
}
/// Represents a condition.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum QueryCondition {
@ -40,6 +64,8 @@ pub enum QueryCondition {
Operation(Box<[QueryCondition]>, Operator),
/// A condition wrapped in parenthesis.
InParens(Box<QueryCondition>),
/// [ <function-token> <any-value>? ) ] | [ ( <any-value>? ) ]
GeneralEnclosed(String),
}
impl ToCss for QueryCondition {
@ -71,10 +97,17 @@ impl ToCss for QueryCondition {
}
Ok(())
},
QueryCondition::GeneralEnclosed(ref s) => dest.write_str(&s),
}
}
}
/// <https://drafts.csswg.org/css-syntax-3/#typedef-any-value>
fn consume_any_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> {
input.expect_no_error_token().map_err(|err| err.into())
}
/// TODO: style() case needs to be handled.
impl QueryCondition {
/// Parse a single condition.
pub fn parse<'i, 't>(
@ -82,7 +115,19 @@ impl QueryCondition {
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> {
Self::parse_internal(context, input, feature_type, AllowOr::Yes)
input.skip_whitespace();
let state = input.state();
let start = input.position();
match *input.next()? {
Token::Function(_) => {
input.parse_nested_block(consume_any_value)?;
return Ok(QueryCondition::GeneralEnclosed(input.slice_from(start).to_owned()));
},
_ => {
input.reset(&state);
Self::parse_internal(context, input, feature_type, AllowOr::Yes)
},
}
}
fn visit<F>(&self, visitor: &mut F)
@ -92,6 +137,7 @@ impl QueryCondition {
visitor(self);
match *self {
Self::Feature(..) => {},
Self::GeneralEnclosed(..) => {},
Self::Not(ref cond) => cond.visit(visitor),
Self::Operation(ref conds, _op) => {
for cond in conds.iter() {
@ -132,21 +178,12 @@ impl QueryCondition {
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 {
if input.try_parse(|i| i.expect_ident_matching("not")).is_ok() {
let inner_condition = Self::parse_in_parens(context, input, feature_type)?;
return Ok(QueryCondition::Not(Box::new(inner_condition)));
}
// ParenthesisBlock.
let first_condition = Self::parse_paren_block(context, input, feature_type)?;
let first_condition = Self::parse_in_parens(context, input, feature_type)?;
let operator = match input.try_parse(Operator::parse) {
Ok(op) => op,
Err(..) => return Ok(first_condition),
@ -192,28 +229,87 @@ impl QueryCondition {
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> {
let start = input.position();
input.parse_nested_block(|input| {
// Base case.
if let Ok(inner) = input.try_parse(|i| Self::parse(context, i, feature_type)) {
if let Ok(inner) = input.try_parse(|i| Self::parse_internal(context, i, feature_type, AllowOr::Yes)) {
return Ok(QueryCondition::InParens(Box::new(inner)));
}
let expr =
QueryFeatureExpression::parse_in_parenthesis_block(context, input, feature_type)?;
Ok(QueryCondition::Feature(expr))
if let Ok(expr) = QueryFeatureExpression::parse_in_parenthesis_block(context, input, feature_type) {
return Ok(QueryCondition::Feature(expr));
}
consume_any_value(input)?;
Ok(QueryCondition::GeneralEnclosed(input.slice_from(start).to_owned()))
})
}
/// Whether this condition matches the device and quirks mode.
pub fn matches(&self, context: &computed::Context) -> bool {
/// https://drafts.csswg.org/mediaqueries/#evaluating
/// https://drafts.csswg.org/mediaqueries/#typedef-general-enclosed
/// Kleene 3-valued logic is adopted here due to the introduction of
/// <general-enclosed>.
pub fn matches(&self, context: &computed::Context) -> KleeneValue {
match *self {
QueryCondition::Feature(ref f) => f.matches(context),
QueryCondition::Feature(ref f) => {
match f.matches(context) {
true => KleeneValue::True,
false => KleeneValue::False,
}
},
QueryCondition::GeneralEnclosed(_) => KleeneValue::Unknown,
QueryCondition::InParens(ref c) => c.matches(context),
QueryCondition::Not(ref c) => !c.matches(context),
QueryCondition::Not(ref c) => {
!c.matches(context)
},
QueryCondition::Operation(ref conditions, op) => {
let mut iter = conditions.iter();
match op {
Operator::And => iter.all(|c| c.matches(context)),
Operator::Or => iter.any(|c| c.matches(context)),
Operator::And => {
if conditions.is_empty() {
return KleeneValue::True;
}
let mut result = iter.next().as_ref().map_or( KleeneValue::True, |c| -> KleeneValue {c.matches(context)});
if result == KleeneValue::False {
return result;
}
while let Some(c) = iter.next() {
match c.matches(context) {
KleeneValue::False => {
return KleeneValue::False;
},
KleeneValue::Unknown => {
result = KleeneValue::Unknown;
},
KleeneValue::True => {},
}
}
return result;
}
Operator::Or => {
if conditions.is_empty() {
return KleeneValue::False;
}
let mut result = iter.next().as_ref().map_or( KleeneValue::False, |c| -> KleeneValue {c.matches(context)});
if result == KleeneValue::True {
return KleeneValue::True;
}
while let Some(c) = iter.next() {
match c.matches(context) {
KleeneValue::True => {
return KleeneValue::True;
},
KleeneValue::Unknown => {
result = KleeneValue::Unknown;
},
KleeneValue::False => {},
}
}
return result;
}
}
},
}

View file

@ -17,6 +17,7 @@ use cssparser::{Parser, Token};
use std::cmp::{Ordering, PartialOrd};
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
use crate::error_reporting::ContextualParseError;
/// Whether we're parsing a media or container query feature.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
@ -502,11 +503,13 @@ impl QueryFeatureExpression {
}
/// Parse a feature expression where we've already consumed the parenthesis.
/// For unknown features, we keeps the warning even though they might be parsed "correctly" because of <general-enclosed>
pub fn parse_in_parenthesis_block<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> {
let start_position = input.position();
let (feature_index, range) =
match input.try_parse(|input| Self::parse_feature_name(context, input, feature_type)) {
Ok(v) => v,
@ -514,6 +517,12 @@ impl QueryFeatureExpression {
if let Ok(expr) = Self::parse_multi_range_syntax(context, input, feature_type) {
return Ok(expr);
}
let location = e.location;
let error = ContextualParseError::UnsupportedRule(
input.slice_from(start_position),
e.clone(),
);
context.log_css_error(location, error);
return Err(e);
},
};

View file

@ -7,7 +7,7 @@
//! [mq]: https://drafts.csswg.org/mediaqueries/
//! [cq]: https://drafts.csswg.org/css-contain-3/#container-rule
mod condition;
pub mod condition;
#[macro_use]
pub mod feature;