mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
style: Refactor media feature expression representation in preparation to support multi-range syntax
No behavior change. Depends on D145229 Differential Revision: https://phabricator.services.mozilla.com/D145230
This commit is contained in:
parent
990de9ceaa
commit
f545a473ff
2 changed files with 159 additions and 127 deletions
|
@ -50,7 +50,7 @@ impl FeatureType {
|
||||||
|
|
||||||
/// The kind of matching that should be performed on a feature value.
|
/// The kind of matching that should be performed on a feature value.
|
||||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
||||||
pub enum Range {
|
enum LegacyRange {
|
||||||
/// At least the specified value.
|
/// At least the specified value.
|
||||||
Min,
|
Min,
|
||||||
/// At most the specified value.
|
/// At most the specified value.
|
||||||
|
@ -59,7 +59,7 @@ pub enum Range {
|
||||||
|
|
||||||
/// The operator that was specified in this feature.
|
/// The operator that was specified in this feature.
|
||||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
||||||
pub enum Operator {
|
enum Operator {
|
||||||
/// =
|
/// =
|
||||||
Equal,
|
Equal,
|
||||||
/// >
|
/// >
|
||||||
|
@ -87,61 +87,86 @@ impl ToCss for Operator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Either a `Range` or an `Operator`.
|
impl Operator {
|
||||||
///
|
fn evaluate(&self, cmp: Ordering) -> bool {
|
||||||
/// Ranged features are not allowed with operations (that'd make no sense).
|
match *self {
|
||||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
Operator::Equal => cmp == Ordering::Equal,
|
||||||
pub enum RangeOrOperator {
|
Operator::GreaterThan => cmp == Ordering::Greater,
|
||||||
/// A `Range`.
|
Operator::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
|
||||||
Range(Range),
|
Operator::LessThan => cmp == Ordering::Less,
|
||||||
/// An `Operator`.
|
Operator::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
|
||||||
Operator(Operator),
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RangeOrOperator {
|
#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
|
||||||
|
enum QueryFeatureExpressionKind {
|
||||||
|
/// Just the media feature name.
|
||||||
|
Empty,
|
||||||
|
|
||||||
|
/// A single value.
|
||||||
|
Single(QueryExpressionValue),
|
||||||
|
|
||||||
|
/// Legacy range syntax (min-*: value) or so.
|
||||||
|
LegacyRange(LegacyRange, QueryExpressionValue),
|
||||||
|
|
||||||
|
/// Modern range context syntax:
|
||||||
|
/// https://drafts.csswg.org/mediaqueries-5/#mq-range-context
|
||||||
|
///
|
||||||
|
/// TODO: Extend to support <value> <op> <name> <op> <value> as needed.
|
||||||
|
Range { operator: Operator, value: QueryExpressionValue },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryFeatureExpressionKind {
|
||||||
/// Evaluate a given range given an optional query value and a value from
|
/// Evaluate a given range given an optional query value and a value from
|
||||||
/// the browser.
|
/// the browser.
|
||||||
fn evaluate<T>(range_or_op: Option<Self>, query_value: Option<T>, value: T) -> bool
|
fn evaluate<T>(&self, context_value: T, mut compute: impl FnMut(&QueryExpressionValue) -> T) -> bool
|
||||||
where
|
where
|
||||||
T: PartialOrd + Zero,
|
T: PartialOrd + Zero,
|
||||||
{
|
{
|
||||||
match query_value {
|
match *self {
|
||||||
Some(v) => Self::evaluate_with_query_value(range_or_op, v, value),
|
Self::Empty => return !context_value.is_zero(),
|
||||||
None => !value.is_zero(),
|
Self::Single(ref value) => {
|
||||||
|
let value = compute(value);
|
||||||
|
let cmp = match context_value.partial_cmp(&value) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
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 => return false,
|
||||||
|
};
|
||||||
|
cmp == Ordering::Equal ||
|
||||||
|
match range {
|
||||||
|
LegacyRange::Min => cmp == Ordering::Greater,
|
||||||
|
LegacyRange::Max => cmp == Ordering::Less,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Range { ref operator, ref value } => {
|
||||||
|
let value = compute(value);
|
||||||
|
let cmp = match context_value.partial_cmp(&value) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
operator.evaluate(cmp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a given range given a non-optional query value and a value from
|
/// Non-ranged features only need to compare to one value at most.
|
||||||
/// the browser.
|
fn non_ranged_value(&self) -> Option<&QueryExpressionValue> {
|
||||||
fn evaluate_with_query_value<T>(range_or_op: Option<Self>, query_value: T, value: T) -> bool
|
match *self {
|
||||||
where
|
Self::Empty => None,
|
||||||
T: PartialOrd,
|
Self::Single(ref v) => Some(v),
|
||||||
{
|
Self::LegacyRange(..) |
|
||||||
let cmp = match value.partial_cmp(&query_value) {
|
Self::Range { .. } => {
|
||||||
Some(c) => c,
|
debug_assert!(false, "Unexpected ranged value in non-ranged feature!");
|
||||||
None => return false,
|
None
|
||||||
};
|
}
|
||||||
|
|
||||||
let range_or_op = match range_or_op {
|
|
||||||
Some(r) => r,
|
|
||||||
None => return cmp == Ordering::Equal,
|
|
||||||
};
|
|
||||||
|
|
||||||
match range_or_op {
|
|
||||||
RangeOrOperator::Range(range) => {
|
|
||||||
cmp == Ordering::Equal ||
|
|
||||||
match range {
|
|
||||||
Range::Min => cmp == Ordering::Greater,
|
|
||||||
Range::Max => cmp == Ordering::Less,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RangeOrOperator::Operator(op) => match op {
|
|
||||||
Operator::Equal => cmp == Ordering::Equal,
|
|
||||||
Operator::GreaterThan => cmp == Ordering::Greater,
|
|
||||||
Operator::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
|
|
||||||
Operator::LessThan => cmp == Ordering::Less,
|
|
||||||
Operator::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,8 +177,7 @@ impl RangeOrOperator {
|
||||||
pub struct QueryFeatureExpression {
|
pub struct QueryFeatureExpression {
|
||||||
feature_type: FeatureType,
|
feature_type: FeatureType,
|
||||||
feature_index: usize,
|
feature_index: usize,
|
||||||
value: Option<QueryExpressionValue>,
|
kind: QueryFeatureExpressionKind,
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToCss for QueryFeatureExpression {
|
impl ToCss for QueryFeatureExpression {
|
||||||
|
@ -163,38 +187,23 @@ impl ToCss for QueryFeatureExpression {
|
||||||
{
|
{
|
||||||
dest.write_str("(")?;
|
dest.write_str("(")?;
|
||||||
|
|
||||||
let feature = self.feature();
|
self.write_name(dest)?;
|
||||||
|
|
||||||
if feature
|
match self.kind {
|
||||||
.requirements
|
QueryFeatureExpressionKind::Empty => {},
|
||||||
.contains(ParsingRequirements::WEBKIT_PREFIX)
|
QueryFeatureExpressionKind::Single(ref v) |
|
||||||
{
|
QueryFeatureExpressionKind::LegacyRange(_, ref v) => {
|
||||||
dest.write_str("-webkit-")?;
|
dest.write_str(": ")?;
|
||||||
}
|
v.to_css(dest, self)?;
|
||||||
|
}
|
||||||
if let Some(RangeOrOperator::Range(range)) = self.range_or_operator {
|
QueryFeatureExpressionKind::Range { ref operator, ref value } => {
|
||||||
match range {
|
dest.write_char(' ')?;
|
||||||
Range::Min => dest.write_str("min-")?,
|
operator.to_css(dest)?;
|
||||||
Range::Max => dest.write_str("max-")?,
|
dest.write_char(' ')?;
|
||||||
|
value.to_css(dest, self)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dest.write_char(')')
|
||||||
// NB: CssStringWriter not needed, feature names are under control.
|
|
||||||
write!(dest, "{}", feature.name)?;
|
|
||||||
|
|
||||||
if let Some(RangeOrOperator::Operator(op)) = self.range_or_operator {
|
|
||||||
dest.write_char(' ')?;
|
|
||||||
op.to_css(dest)?;
|
|
||||||
dest.write_char(' ')?;
|
|
||||||
} else if self.value.is_some() {
|
|
||||||
dest.write_str(": ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref val) = self.value {
|
|
||||||
val.to_css(dest, self)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
dest.write_str(")")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,18 +282,41 @@ impl QueryFeatureExpression {
|
||||||
fn new(
|
fn new(
|
||||||
feature_type: FeatureType,
|
feature_type: FeatureType,
|
||||||
feature_index: usize,
|
feature_index: usize,
|
||||||
value: Option<QueryExpressionValue>,
|
kind: QueryFeatureExpressionKind,
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
debug_assert!(feature_index < feature_type.features().len());
|
debug_assert!(feature_index < feature_type.features().len());
|
||||||
Self {
|
Self {
|
||||||
feature_type,
|
feature_type,
|
||||||
feature_index,
|
feature_index,
|
||||||
value,
|
kind,
|
||||||
range_or_operator,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_name<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||||
|
where
|
||||||
|
W: fmt::Write,
|
||||||
|
{
|
||||||
|
let feature = self.feature();
|
||||||
|
if feature
|
||||||
|
.requirements
|
||||||
|
.contains(ParsingRequirements::WEBKIT_PREFIX)
|
||||||
|
{
|
||||||
|
dest.write_str("-webkit-")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let QueryFeatureExpressionKind::LegacyRange(range, _) = self.kind {
|
||||||
|
match range {
|
||||||
|
LegacyRange::Min => dest.write_str("min-")?,
|
||||||
|
LegacyRange::Max => dest.write_str("max-")?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: CssStringWriter not needed, feature names are under control.
|
||||||
|
write!(dest, "{}", feature.name)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn feature(&self) -> &'static QueryFeatureDescription {
|
fn feature(&self) -> &'static QueryFeatureDescription {
|
||||||
&self.feature_type.features()[self.feature_index]
|
&self.feature_type.features()[self.feature_index]
|
||||||
}
|
}
|
||||||
|
@ -307,7 +339,7 @@ impl QueryFeatureExpression {
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
feature_type: FeatureType,
|
feature_type: FeatureType,
|
||||||
) -> Result<(usize, Option<Range>), ParseError<'i>> {
|
) -> Result<(usize, Option<LegacyRange>), ParseError<'i>> {
|
||||||
let mut requirements = ParsingRequirements::empty();
|
let mut requirements = ParsingRequirements::empty();
|
||||||
let location = input.current_source_location();
|
let location = input.current_source_location();
|
||||||
let ident = input.expect_ident()?;
|
let ident = input.expect_ident()?;
|
||||||
|
@ -324,10 +356,10 @@ impl QueryFeatureExpression {
|
||||||
|
|
||||||
let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
|
let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
|
||||||
feature_name = &feature_name[4..];
|
feature_name = &feature_name[4..];
|
||||||
Some(Range::Min)
|
Some(LegacyRange::Min)
|
||||||
} else if starts_with_ignore_ascii_case(feature_name, "max-") {
|
} else if starts_with_ignore_ascii_case(feature_name, "max-") {
|
||||||
feature_name = &feature_name[4..];
|
feature_name = &feature_name[4..];
|
||||||
Some(Range::Max)
|
Some(LegacyRange::Max)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -375,20 +407,26 @@ impl QueryFeatureExpression {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(Self::new(feature_type, feature_index, None, None));
|
return Ok(Self::new(feature_type, feature_index, QueryFeatureExpressionKind::Empty));
|
||||||
},
|
},
|
||||||
Ok(operator) => operator,
|
Ok(operator) => operator,
|
||||||
};
|
};
|
||||||
|
|
||||||
let feature = &feature_type.features()[feature_index];
|
let feature = &feature_type.features()[feature_index];
|
||||||
let range_or_operator = match range {
|
|
||||||
|
let value = QueryExpressionValue::parse(feature, context, input).map_err(|err| {
|
||||||
|
err.location
|
||||||
|
.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let kind = match range {
|
||||||
Some(range) => {
|
Some(range) => {
|
||||||
if operator.is_some() {
|
if operator.is_some() {
|
||||||
return Err(
|
return Err(
|
||||||
input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
|
input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Some(RangeOrOperator::Range(range))
|
QueryFeatureExpressionKind::LegacyRange(range, value)
|
||||||
},
|
},
|
||||||
None => match operator {
|
None => match operator {
|
||||||
Some(operator) => {
|
Some(operator) => {
|
||||||
|
@ -396,77 +434,61 @@ impl QueryFeatureExpression {
|
||||||
return Err(input
|
return Err(input
|
||||||
.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
|
.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
|
||||||
}
|
}
|
||||||
Some(RangeOrOperator::Operator(operator))
|
QueryFeatureExpressionKind::Range { operator, value }
|
||||||
},
|
},
|
||||||
None => None,
|
None => QueryFeatureExpressionKind::Single(value),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = QueryExpressionValue::parse(feature, context, input).map_err(|err| {
|
Ok(Self::new(feature_type, feature_index, kind))
|
||||||
err.location
|
|
||||||
.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Self::new(feature_type, feature_index, Some(value), range_or_operator))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether this query evaluates to true for the given device.
|
/// Returns whether this query evaluates to true for the given device.
|
||||||
pub fn matches(&self, context: &computed::Context) -> bool {
|
pub fn matches(&self, context: &computed::Context) -> bool {
|
||||||
let value = self.value.as_ref();
|
|
||||||
|
|
||||||
macro_rules! expect {
|
macro_rules! expect {
|
||||||
($variant:ident) => {
|
($variant:ident, $v:expr) => {
|
||||||
value.map(|value| match *value {
|
match *$v {
|
||||||
QueryExpressionValue::$variant(ref v) => v,
|
QueryExpressionValue::$variant(ref v) => v,
|
||||||
_ => unreachable!("Unexpected QueryExpressionValue"),
|
_ => unreachable!("Unexpected QueryExpressionValue"),
|
||||||
})
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.feature().evaluator {
|
match self.feature().evaluator {
|
||||||
Evaluator::Length(eval) => {
|
Evaluator::Length(eval) => {
|
||||||
let computed = expect!(Length).map(|specified| {
|
let v = eval(context);
|
||||||
specified.to_computed_value(context)
|
self.kind.evaluate(v, |v| {
|
||||||
});
|
expect!(Length, v).to_computed_value(context)
|
||||||
let length = eval(context);
|
})
|
||||||
RangeOrOperator::evaluate(self.range_or_operator, computed, length)
|
|
||||||
},
|
},
|
||||||
Evaluator::Integer(eval) => {
|
Evaluator::Integer(eval) => {
|
||||||
let computed = expect!(Integer).cloned();
|
let v = eval(context);
|
||||||
let integer = eval(context);
|
self.kind.evaluate(v, |v| *expect!(Integer, v))
|
||||||
RangeOrOperator::evaluate(self.range_or_operator, computed, integer)
|
|
||||||
},
|
},
|
||||||
Evaluator::Float(eval) => {
|
Evaluator::Float(eval) => {
|
||||||
let computed = expect!(Float).cloned();
|
let v = eval(context);
|
||||||
let float = eval(context);
|
self.kind.evaluate(v, |v| *expect!(Float, v))
|
||||||
RangeOrOperator::evaluate(self.range_or_operator, computed, float)
|
|
||||||
}
|
}
|
||||||
Evaluator::NumberRatio(eval) => {
|
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()
|
// A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value()
|
||||||
// to convert it if necessary.
|
// to convert it if necessary.
|
||||||
// FIXME: we may need to update here once
|
// FIXME: we may need to update here once
|
||||||
// https://github.com/w3c/csswg-drafts/issues/4954 got resolved.
|
// https://github.com/w3c/csswg-drafts/issues/4954 got resolved.
|
||||||
let computed = match expect!(NumberRatio).cloned() {
|
self.kind.evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
|
||||||
Some(ratio) => ratio.used_value(),
|
|
||||||
None => return true,
|
|
||||||
};
|
|
||||||
let ratio = eval(context);
|
|
||||||
RangeOrOperator::evaluate_with_query_value(self.range_or_operator, computed, ratio)
|
|
||||||
},
|
},
|
||||||
Evaluator::Resolution(eval) => {
|
Evaluator::Resolution(eval) => {
|
||||||
let computed = expect!(Resolution).map(|specified| {
|
let v = eval(context).dppx();
|
||||||
specified.to_computed_value(context).dppx()
|
self.kind.evaluate(v, |v| {
|
||||||
});
|
expect!(Resolution, v).to_computed_value(context).dppx()
|
||||||
let resolution = eval(context).dppx();
|
})
|
||||||
RangeOrOperator::evaluate(self.range_or_operator, computed, resolution)
|
|
||||||
},
|
},
|
||||||
Evaluator::Enumerated { evaluator, .. } => {
|
Evaluator::Enumerated { evaluator, .. } => {
|
||||||
debug_assert!(self.range_or_operator.is_none(), "Ranges with keywords?");
|
let computed = self.kind.non_ranged_value().map(|v| *expect!(Enumerated, v));
|
||||||
evaluator(context, expect!(Enumerated).cloned())
|
evaluator(context, computed)
|
||||||
},
|
},
|
||||||
Evaluator::BoolInteger(eval) => {
|
Evaluator::BoolInteger(eval) => {
|
||||||
debug_assert!(self.range_or_operator.is_none(), "Ranges with bools?");
|
let computed = self.kind.non_ranged_value().map(|v| *expect!(BoolInteger, v));
|
||||||
let computed = expect!(BoolInteger).cloned();
|
|
||||||
let boolean = eval(context);
|
let boolean = eval(context);
|
||||||
computed.map_or(boolean, |v| v == boolean)
|
computed.map_or(boolean, |v| v == boolean)
|
||||||
},
|
},
|
||||||
|
|
|
@ -72,6 +72,16 @@ impl ComputeSquaredDistance for Ratio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Zero for Ratio {
|
||||||
|
fn zero() -> Self {
|
||||||
|
Self::new(Zero::zero(), One::one())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
self.0.is_zero()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Ratio {
|
impl Ratio {
|
||||||
/// Returns a new Ratio.
|
/// Returns a new Ratio.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue