From bee44a52597d2b5ffee6248ab368ba86bb173258 Mon Sep 17 00:00:00 2001 From: Ziran Sun Date: Wed, 30 Nov 2022 10:19:07 +0000 Subject: [PATCH] style: Add parsing for in queries conditions See https://drafts.csswg.org/mediaqueries-5/#typedef-general-enclosed Differential Revision: https://phabricator.services.mozilla.com/D158662 --- components/style/media_queries/media_list.rs | 23 ++- components/style/queries/condition.rs | 138 +++++++++++++++--- .../style/queries/feature_expression.rs | 9 ++ components/style/queries/mod.rs | 2 +- .../style/stylesheets/container_rule.rs | 3 +- components/style/stylist.rs | 9 +- .../values/specified/source_size_list.rs | 10 +- 7 files changed, 164 insertions(+), 30 deletions(-) diff --git a/components/style/media_queries/media_list.rs b/components/style/media_queries/media_list.rs index f97b718cf5c..35f0b5a6c6f 100644 --- a/components/style/media_queries/media_list.rs +++ b/components/style/media_queries/media_list.rs @@ -11,6 +11,7 @@ use crate::context::QuirksMode; use crate::error_reporting::ContextualParseError; use crate::parser::ParserContext; use crate::values::computed; +use crate::queries::condition::KleeneValue; use cssparser::{Delimiter, Parser}; use cssparser::{ParserInput, Token}; @@ -84,13 +85,27 @@ impl MediaList { let media_match = mq.media_type.matches(device.media_type()); // Check if the media condition match. - let query_match = - media_match && mq.condition.as_ref().map_or(true, |c| c.matches(context)); + let query_match = match media_match { + true => mq.condition.as_ref().map_or(KleeneValue::True, |c| c.matches(context)), + false => KleeneValue::False, + }; // Apply the logical NOT qualifier to the result match mq.qualifier { - Some(Qualifier::Not) => !query_match, - _ => query_match, + Some(Qualifier::Not) => { + match query_match { + KleeneValue::False => true, + KleeneValue::True => false, + KleeneValue::Unknown => false, + } + }, + _ => { + match query_match { + KleeneValue::False => false, + KleeneValue::True => true, + KleeneValue::Unknown => false, + } + }, } }) }) diff --git a/components/style/queries/condition.rs b/components/style/queries/condition.rs index 01f7ff1bfd7..e9219bd74ba 100644 --- a/components/style/queries/condition.rs +++ b/components/style/queries/condition.rs @@ -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 we’re 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), + /// [ ? ) ] | [ ( ? ) ] + GeneralEnclosed(String), } impl ToCss for QueryCondition { @@ -71,10 +97,17 @@ impl ToCss for QueryCondition { } Ok(()) }, + QueryCondition::GeneralEnclosed(ref s) => dest.write_str(&s), } } } +/// +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::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(&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> { 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> { + 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 + /// . + 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; + } } }, } diff --git a/components/style/queries/feature_expression.rs b/components/style/queries/feature_expression.rs index 8bea95cb2e0..5f46dc1eba7 100644 --- a/components/style/queries/feature_expression.rs +++ b/components/style/queries/feature_expression.rs @@ -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 pub fn parse_in_parenthesis_block<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, feature_type: FeatureType, ) -> Result> { + 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); }, }; diff --git a/components/style/queries/mod.rs b/components/style/queries/mod.rs index c68d0b30b39..ec11ab3721e 100644 --- a/components/style/queries/mod.rs +++ b/components/style/queries/mod.rs @@ -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; diff --git a/components/style/stylesheets/container_rule.rs b/components/style/stylesheets/container_rule.rs index 5df810b9be4..2d7356cf351 100644 --- a/components/style/stylesheets/container_rule.rs +++ b/components/style/stylesheets/container_rule.rs @@ -15,6 +15,7 @@ use crate::properties::ComputedValues; use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; use crate::queries::values::Orientation; use crate::queries::{FeatureType, QueryCondition}; +use crate::queries::condition::KleeneValue; use crate::shared_lock::{ DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard, }; @@ -226,7 +227,7 @@ impl ContainerCondition { device: &Device, element: E, invalidation_flags: &mut ComputedValueFlags, - ) -> bool + ) -> KleeneValue where E: TElement, { diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 321f879c83a..86a65ee24f7 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -19,6 +19,7 @@ use crate::invalidation::stylesheets::RuleChangeKind; use crate::media_queries::Device; use crate::properties::{self, CascadeMode, ComputedValues}; use crate::properties::{AnimationDeclarations, PropertyDeclarationBlock}; +use crate::queries::condition::KleeneValue; use crate::rule_cache::{RuleCache, RuleCacheConditions}; use crate::rule_collector::{containing_shadow_ignoring_svg_use, RuleCollector}; use crate::rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource}; @@ -2380,7 +2381,13 @@ impl CascadeData { None => return true, Some(ref c) => c, }; - if !condition.matches(stylist.device(), element, &mut context.extra_data.cascade_input_flags) { + let result = match !condition.matches(stylist.device(), element, &mut context.extra_data.cascade_input_flags) { + KleeneValue::True => true, + KleeneValue::False => false, + KleeneValue::Unknown => true, + }; + + if result { return false; } id = condition_ref.parent; diff --git a/components/style/values/specified/source_size_list.rs b/components/style/values/specified/source_size_list.rs index f7a39a89f5b..e3f1055cd0d 100644 --- a/components/style/values/specified/source_size_list.rs +++ b/components/style/values/specified/source_size_list.rs @@ -9,6 +9,7 @@ use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; use crate::media_queries::Device; use crate::parser::{Parse, ParserContext}; use crate::queries::{FeatureType, QueryCondition}; +use crate::queries::condition::KleeneValue; use crate::values::computed::{self, ToComputedValue}; use crate::values::specified::{Length, NoCalcLength, ViewportPercentageLength}; use app_units::Au; @@ -57,10 +58,15 @@ impl SourceSizeList { /// Evaluate this to get the final viewport length. pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> Au { computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { - let matching_source_size = self + let matching_source_size = self .source_sizes .iter() - .find(|source_size| source_size.condition.matches(context)); + .find(|source_size| match source_size.condition.matches(context) { + KleeneValue::Unknown => false, + KleeneValue::False => false, + KleeneValue::True => true, + } + ); match matching_source_size { Some(source_size) => source_size.value.to_computed_value(context),