summaryrefslogtreecommitdiffstats
path: root/servo/components/style/queries/condition.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/queries/condition.rs')
-rw-r--r--servo/components/style/queries/condition.rs365
1 files changed, 365 insertions, 0 deletions
diff --git a/servo/components/style/queries/condition.rs b/servo/components/style/queries/condition.rs
new file mode 100644
index 0000000000..56822c914a
--- /dev/null
+++ b/servo/components/style/queries/condition.rs
@@ -0,0 +1,365 @@
+/* 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 query condition:
+//!
+//! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition
+//! https://drafts.csswg.org/css-contain-3/#typedef-container-condition
+
+use super::{FeatureFlags, FeatureType, QueryFeatureExpression};
+use crate::values::computed;
+use crate::{error_reporting::ContextualParseError, 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,
+}
+
+/// https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics
+#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss)]
+pub enum KleeneValue {
+ /// False
+ False = 0,
+ /// True
+ True = 1,
+ /// Either true or false, but we’re not sure which yet.
+ Unknown,
+}
+
+impl From<bool> for KleeneValue {
+ fn from(b: bool) -> Self {
+ if b {
+ Self::True
+ } else {
+ Self::False
+ }
+ }
+}
+
+impl KleeneValue {
+ /// Turns this Kleene value to a bool, taking the unknown value as an
+ /// argument.
+ pub fn to_bool(self, unknown: bool) -> bool {
+ match self {
+ Self::True => true,
+ Self::False => false,
+ Self::Unknown => unknown,
+ }
+ }
+}
+
+impl std::ops::Not for KleeneValue {
+ type Output = Self;
+
+ fn not(self) -> Self {
+ match self {
+ Self::True => Self::False,
+ Self::False => Self::True,
+ Self::Unknown => Self::Unknown,
+ }
+ }
+}
+
+// Implements the logical and operation.
+impl std::ops::BitAnd for KleeneValue {
+ type Output = Self;
+
+ fn bitand(self, other: Self) -> Self {
+ if self == Self::False || other == Self::False {
+ return Self::False;
+ }
+ if self == Self::Unknown || other == Self::Unknown {
+ return Self::Unknown;
+ }
+ Self::True
+ }
+}
+
+// Implements the logical or operation.
+impl std::ops::BitOr for KleeneValue {
+ type Output = Self;
+
+ fn bitor(self, other: Self) -> Self {
+ if self == Self::True || other == Self::True {
+ return Self::True;
+ }
+ if self == Self::Unknown || other == Self::Unknown {
+ return Self::Unknown;
+ }
+ Self::False
+ }
+}
+
+impl std::ops::BitOrAssign for KleeneValue {
+ fn bitor_assign(&mut self, other: Self) {
+ *self = *self | other;
+ }
+}
+
+impl std::ops::BitAndAssign for KleeneValue {
+ fn bitand_assign(&mut self, other: Self) {
+ *self = *self & other;
+ }
+}
+
+/// Represents a condition.
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
+pub enum QueryCondition {
+ /// A simple feature expression, implicitly parenthesized.
+ Feature(QueryFeatureExpression),
+ /// A negation of a condition.
+ Not(Box<QueryCondition>),
+ /// A set of joint operations.
+ Operation(Box<[QueryCondition]>, Operator),
+ /// A condition wrapped in parenthesis.
+ InParens(Box<QueryCondition>),
+ /// [ <function-token> <any-value>? ) ] | [ ( <any-value>? ) ]
+ GeneralEnclosed(String),
+}
+
+impl ToCss for QueryCondition {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ match *self {
+ // NOTE(emilio): QueryFeatureExpression already includes the
+ // parenthesis.
+ QueryCondition::Feature(ref f) => f.to_css(dest),
+ QueryCondition::Not(ref c) => {
+ dest.write_str("not ")?;
+ c.to_css(dest)
+ },
+ QueryCondition::InParens(ref c) => {
+ dest.write_char('(')?;
+ c.to_css(dest)?;
+ dest.write_char(')')
+ },
+ QueryCondition::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(())
+ },
+ 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(Into::into)
+}
+
+impl QueryCondition {
+ /// Parse a single condition.
+ pub fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ feature_type: FeatureType,
+ ) -> Result<Self, ParseError<'i>> {
+ Self::parse_internal(context, input, feature_type, AllowOr::Yes)
+ }
+
+ fn visit<F>(&self, visitor: &mut F)
+ where
+ F: FnMut(&Self),
+ {
+ 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() {
+ cond.visit(visitor);
+ }
+ },
+ Self::InParens(ref cond) => cond.visit(visitor),
+ }
+ }
+
+ /// Returns the union of all flags in the expression. This is useful for
+ /// container queries.
+ pub fn cumulative_flags(&self) -> FeatureFlags {
+ let mut result = FeatureFlags::empty();
+ self.visit(&mut |condition| {
+ if let Self::Feature(ref f) = condition {
+ result.insert(f.feature_flags())
+ }
+ });
+ result
+ }
+
+ /// Parse a single condition, disallowing `or` expressions.
+ ///
+ /// To be used from the legacy query syntax.
+ pub fn parse_disallow_or<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ feature_type: FeatureType,
+ ) -> Result<Self, ParseError<'i>> {
+ Self::parse_internal(context, input, feature_type, AllowOr::No)
+ }
+
+ /// https://drafts.csswg.org/mediaqueries-5/#typedef-media-condition or
+ /// https://drafts.csswg.org/mediaqueries-5/#typedef-media-condition-without-or
+ /// (depending on `allow_or`).
+ fn parse_internal<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ feature_type: FeatureType,
+ allow_or: AllowOr,
+ ) -> Result<Self, ParseError<'i>> {
+ let location = input.current_source_location();
+ 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)));
+ }
+
+ 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),
+ };
+
+ 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, feature_type)?);
+
+ 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(QueryCondition::Operation(
+ conditions.into_boxed_slice(),
+ operator,
+ ));
+ }
+
+ conditions.push(Self::parse_in_parens(context, input, feature_type)?);
+ }
+ }
+
+ fn parse_in_parenthesis_block<'i>(
+ context: &ParserContext,
+ input: &mut Parser<'i, '_>,
+ feature_type: FeatureType,
+ ) -> Result<Self, ParseError<'i>> {
+ // Base case. Make sure to preserve this error as it's more generally
+ // relevant.
+ let feature_error = match input.try_parse(|input| {
+ QueryFeatureExpression::parse_in_parenthesis_block(context, input, feature_type)
+ }) {
+ Ok(expr) => return Ok(Self::Feature(expr)),
+ Err(e) => e,
+ };
+ if let Ok(inner) = Self::parse(context, input, feature_type) {
+ return Ok(Self::InParens(Box::new(inner)));
+ }
+ Err(feature_error)
+ }
+
+ /// Parse a condition in parentheses, or `<general-enclosed>`.
+ ///
+ /// https://drafts.csswg.org/mediaqueries/#typedef-media-in-parens
+ pub fn parse_in_parens<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ feature_type: FeatureType,
+ ) -> Result<Self, ParseError<'i>> {
+ input.skip_whitespace();
+ let start = input.position();
+ let start_location = input.current_source_location();
+ match *input.next()? {
+ Token::ParenthesisBlock => {
+ let nested = input.try_parse(|input| {
+ input.parse_nested_block(|input| {
+ Self::parse_in_parenthesis_block(context, input, feature_type)
+ })
+ });
+ match nested {
+ Ok(nested) => return Ok(nested),
+ Err(e) => {
+ // We're about to swallow the error in a `<general-enclosed>`
+ // condition, so report it while we can.
+ let loc = e.location;
+ let error = ContextualParseError::InvalidMediaRule(input.slice_from(start), e);
+ context.log_css_error(loc, error);
+ }
+ }
+ },
+ Token::Function(..) => {
+ // TODO: handle `style()` queries, etc.
+ },
+ ref t => return Err(start_location.new_unexpected_token_error(t.clone())),
+ }
+ input.parse_nested_block(consume_any_value)?;
+ Ok(Self::GeneralEnclosed(input.slice_from(start).to_owned()))
+ }
+
+ /// Whether this condition matches the device and quirks mode.
+ /// 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::GeneralEnclosed(_) => KleeneValue::Unknown,
+ QueryCondition::InParens(ref c) => c.matches(context),
+ QueryCondition::Not(ref c) => !c.matches(context),
+ QueryCondition::Operation(ref conditions, op) => {
+ debug_assert!(!conditions.is_empty(), "We never create an empty op");
+ match op {
+ Operator::And => {
+ let mut result = KleeneValue::True;
+ for c in conditions.iter() {
+ result &= c.matches(context);
+ if result == KleeneValue::False {
+ break;
+ }
+ }
+ result
+ },
+ Operator::Or => {
+ let mut result = KleeneValue::False;
+ for c in conditions.iter() {
+ result |= c.matches(context);
+ if result == KleeneValue::True {
+ break;
+ }
+ }
+ result
+ },
+ }
+ },
+ }
+ }
+}