/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Parsing for query feature expressions, like `(foo: bar)` or //! `(width >= 400px)`.
usesuper::feature::{Evaluator, QueryFeatureDescription}; usesuper::feature::{FeatureFlags, KeywordDiscriminant}; usecrate::parser::{Parse, ParserContext}; usecrate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; usecrate::values::computed::{self, Ratio, ToComputedValue}; usecrate::values::specified::{Integer, Length, Number, Resolution}; usecrate::values::{AtomString, CSSFloat}; usecrate::{Atom, Zero}; use cssparser::{Parser, Token}; use selectors::kleene_value::KleeneValue; use std::cmp::Ordering; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
/// Whether we're parsing a media or container query feature. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] pubenum FeatureType { /// We're parsing a media feature.
Media, /// We're parsing a container feature.
Container,
}
/// The kind of matching that should be performed on a feature value. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] enum LegacyRange { /// At least the specified value.
Min, /// At most the specified value.
Max,
}
/// The operator that was specified in this feature. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] enum Operator { /// =
Equal, /// >
GreaterThan, /// >=
GreaterThanEqual, /// <
LessThan, /// <=
LessThanEqual,
}
fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> { let location = input.current_source_location(); let operator = match *input.next()? {
Token::Delim('=') => return Ok(Operator::Equal),
Token::Delim('>') => Operator::GreaterThan,
Token::Delim('<') => Operator::LessThan, ref t => return Err(location.new_unexpected_token_error(t.clone())),
};
// https://drafts.csswg.org/mediaqueries-4/#mq-syntax: // // No whitespace is allowed between the “<” or “>” // <delim-token>s and the following “=” <delim-token>, if it’s // present. // // TODO(emilio): Maybe we should ignore comments as well? // https://github.com/w3c/csswg-drafts/issues/6248 let parsed_equal = input
.try_parse(|i| { let t = i.next_including_whitespace().map_err(|_| ())?; if !matches!(t, Token::Delim('=')) { return Err(());
}
Ok(())
})
.is_ok();
impl QueryFeatureExpressionKind { /// Evaluate a given range given an optional query value and a value from /// the browser. fn evaluate<T>(
&self,
context_value: T, mut compute: impl FnMut(&QueryExpressionValue) -> T,
) -> bool where
T: PartialOrd + Zero,
{ match *self { Self::Empty => return !context_value.is_zero(), Self::Single(ref value) => { let value = compute(value); let cmp = match context_value.partial_cmp(&value) {
Some(c) => c,
None => returnfalse,
};
cmp == Ordering::Equal
}, Self::LegacyRange(ref range, ref value) => { let value = compute(value); let cmp = match context_value.partial_cmp(&value) {
Some(c) => c,
None => returnfalse,
};
cmp == Ordering::Equal || match range {
LegacyRange::Min => cmp == Ordering::Greater,
LegacyRange::Max => cmp == Ordering::Less,
}
}, Self::Range { ref left, ref right,
} => {
debug_assert!(left.is_some() || right.is_some()); iflet Some((ref op, ref value)) = left { let value = compute(value); let cmp = match value.partial_cmp(&context_value) {
Some(c) => c,
None => returnfalse,
}; if !op.evaluate(cmp) { returnfalse;
}
} iflet Some((ref op, ref value)) = right { let value = compute(value); let cmp = match context_value.partial_cmp(&value) {
Some(c) => c,
None => returnfalse,
}; if !op.evaluate(cmp) { returnfalse;
}
} true
},
}
}
/// Non-ranged features only need to compare to one value at most. fn non_ranged_value(&self) -> Option<&QueryExpressionValue> { match *self { Self::Empty => None, Self::Single(ref v) => Some(v), Self::LegacyRange(..) | Self::Range { .. } => {
debug_assert!(false, "Unexpected ranged value in non-ranged feature!");
None
},
}
}
}
/// A feature expression contains a reference to the feature, the value the /// query contained, and the range to evaluate. #[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)] pubstruct QueryFeatureExpression {
feature_type: FeatureType,
feature_index: usize,
kind: QueryFeatureExpressionKind,
}
impl ToCss for QueryFeatureExpression { fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where
W: fmt::Write,
{
dest.write_char('(')?;
#[allow(unused_variables)] fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool { #[cfg(feature = "gecko")]
{ // prefers-reduced-transparency is always enabled in the ua and chrome. On // the web it is hidden behind a preference (see Bug 1822176). if *feature == atom!("prefers-reduced-transparency") { return !context.chrome_rules_enabled() &&
!static_prefs::pref!("layout.css.prefers-reduced-transparency.enabled");
}
// inverted-colors is always enabled in the ua and chrome. On // the web it is hidden behind a preference. if *feature == atom!("inverted-colors") { return !context.chrome_rules_enabled() &&
!static_prefs::pref!("layout.css.inverted-colors.enabled");
}
} false
}
/// Parses the following range syntax: /// /// (feature-value <operator> feature-name) /// (feature-value <operator> feature-name <operator> feature-value) fn parse_multi_range_syntax<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> { let start = input.state();
// To parse the values, we first need to find the feature name. We rely // on feature values for ranged features not being able to be top-level // <ident>s, which holds. let feature_index = loop { // NOTE: parse_feature_name advances the input. iflet Ok((index, range)) = Self::parse_feature_name(context, input, feature_type) { if range.is_some() { // Ranged names are not allowed here. return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
} break index;
} if input.is_exhausted() { return Err(start
.source_location()
.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
};
input.reset(&start);
let feature = &feature_type.features()[feature_index]; let left_val = QueryExpressionValue::parse(feature, context, input)?; let left_op = Operator::parse(input)?;
{ let (parsed_index, _) = Self::parse_feature_name(context, input, feature_type)?;
debug_assert_eq!(
parsed_index, feature_index, "How did we find a different feature?"
);
}
let right_op = input.try_parse(Operator::parse).ok(); let right = match right_op {
Some(op) => { if !left_op.is_compatible_with(op) { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Some((op, QueryExpressionValue::parse(feature, context, input)?))
},
None => None,
};
Ok(Self::new(
feature_type,
feature_index,
QueryFeatureExpressionKind::Range {
left: Some((left_op, left_val)),
right,
},
))
}
/// Parse a feature expression where we've already consumed the parenthesis. pubfn parse_in_parenthesis_block<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> { let (feature_index, range) = match input.try_parse(|input| Self::parse_feature_name(context, input, feature_type)) {
Ok(v) => v,
Err(e) => { iflet Ok(expr) = Self::parse_multi_range_syntax(context, input, feature_type) { return Ok(expr);
} return Err(e);
},
}; let operator = input.try_parse(consume_operation_or_colon); let operator = match operator {
Err(..) => { // If there's no colon, this is a query of the form // '(<feature>)', that is, there's no value specified. // // Gecko doesn't allow ranged expressions without a // value, so just reject them here too. if range.is_some() { return Err(
input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
);
}
let feature = &feature_type.features()[feature_index];
let value = QueryExpressionValue::parse(feature, context, input).map_err(|err| {
err.location
.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
})?;
let kind = match range {
Some(range) => { if operator.is_some() { return Err(
input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
);
}
QueryFeatureExpressionKind::LegacyRange(range, value)
},
None => match operator {
Some(operator) => { if !feature.allows_ranges() { return Err(input
.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
}
QueryFeatureExpressionKind::Range {
left: None,
right: Some((operator, value)),
}
},
None => QueryFeatureExpressionKind::Single(value),
},
};
/// Returns whether this query evaluates to true for the given device. pubfn matches(&self, context: &computed::Context) -> KleeneValue {
macro_rules! expect {
($variant:ident, $v:expr) => { match *$v {
QueryExpressionValue::$variant(ref v) => v,
_ => unreachable!("Unexpected QueryExpressionValue"),
}
};
}
KleeneValue::from(matchself.feature().evaluator {
Evaluator::Length(eval) => { let v = eval(context); self.kind
.evaluate(v, |v| expect!(Length, v).to_computed_value(context))
},
Evaluator::OptionalLength(eval) => { let v = match eval(context) {
Some(v) => v,
None => return KleeneValue::Unknown,
}; self.kind
.evaluate(v, |v| expect!(Length, v).to_computed_value(context))
},
Evaluator::Integer(eval) => { let v = eval(context); self.kind.evaluate(v, |v| *expect!(Integer, v))
},
Evaluator::Float(eval) => { let v = eval(context); self.kind.evaluate(v, |v| *expect!(Float, v))
},
Evaluator::NumberRatio(eval) => { let ratio = eval(context); // A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value() // to convert it if necessary. // FIXME: we may need to update here once // https://github.com/w3c/csswg-drafts/issues/4954 got resolved. self.kind
.evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
},
Evaluator::OptionalNumberRatio(eval) => { let ratio = match eval(context) {
Some(v) => v,
None => return KleeneValue::Unknown,
}; // See above for subtleties here. self.kind
.evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
},
Evaluator::Resolution(eval) => { let v = eval(context).dppx(); self.kind.evaluate(v, |v| {
expect!(Resolution, v).to_computed_value(context).dppx()
})
},
Evaluator::Enumerated { evaluator, .. } => { let computed = self
.kind
.non_ranged_value()
.map(|v| *expect!(Enumerated, v)); return evaluator(context, computed);
},
Evaluator::String(evaluator) => { let string = self.kind.non_ranged_value().map(|v| expect!(String, v)); return evaluator(context, string);
},
Evaluator::BoolInteger(eval) => { let computed = self
.kind
.non_ranged_value()
.map(|v| *expect!(BoolInteger, v)); let boolean = eval(context);
computed.map_or(boolean, |v| v == boolean)
},
})
}
}
/// A value found or expected in a expression. /// /// FIXME(emilio): How should calc() serialize in the Number / Integer / /// BoolInteger / NumberRatio case, as computed or as specified value? /// /// If the first, this would need to store the relevant values. /// /// See: https://github.com/w3c/csswg-drafts/issues/1968 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pubenum QueryExpressionValue { /// A length.
Length(Length), /// An integer.
Integer(i32), /// A floating point value.
Float(CSSFloat), /// A boolean value, specified as an integer (i.e., either 0 or 1).
BoolInteger(bool), /// A single non-negative number or two non-negative numbers separated by '/', /// with optional whitespace on either side of the '/'.
NumberRatio(Ratio), /// A resolution.
Resolution(Resolution), /// An enumerated value, defined by the variant keyword table in the /// feature's `mData` member.
Enumerated(KeywordDiscriminant), /// An arbitrary ident value.
String(AtomString),
}
fn parse<'i, 't>(
for_feature: &QueryFeatureDescription,
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<QueryExpressionValue, ParseError<'i>> {
Ok(match for_feature.evaluator {
Evaluator::OptionalLength(..) | Evaluator::Length(..) => { let length = Length::parse(context, input)?;
QueryExpressionValue::Length(length)
},
Evaluator::Integer(..) => { let integer = Integer::parse(context, input)?;
QueryExpressionValue::Integer(integer.value())
},
Evaluator::BoolInteger(..) => { let integer = Integer::parse_non_negative(context, input)?; let value = integer.value(); if value > 1 { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
QueryExpressionValue::BoolInteger(value == 1)
},
Evaluator::Float(..) => { let number = Number::parse(context, input)?;
QueryExpressionValue::Float(number.get())
},
Evaluator::OptionalNumberRatio(..) | Evaluator::NumberRatio(..) => { usecrate::values::specified::Ratio as SpecifiedRatio; let ratio = SpecifiedRatio::parse(context, input)?;
QueryExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
},
Evaluator::Resolution(..) => {
QueryExpressionValue::Resolution(Resolution::parse(context, input)?)
},
Evaluator::String(..) => {
QueryExpressionValue::String(input.expect_string()?.as_ref().into())
},
Evaluator::Enumerated { parser, .. } => {
QueryExpressionValue::Enumerated(parser(context, input)?)
},
})
}
}
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.13Bemerkung:
(vorverarbeitet am 2026-06-18)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.