2018-08-15 17:07:11 +03:00
|
|
|
/* 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 http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
//! Parsing for media feature expressions, like `(foo: bar)` or
|
|
|
|
//! `(width >= 400px)`.
|
|
|
|
|
|
|
|
use Atom;
|
|
|
|
use context::QuirksMode;
|
2018-08-18 19:41:40 +03:00
|
|
|
use cssparser::{Parser, Token};
|
|
|
|
#[cfg(feature = "gecko")]
|
|
|
|
use gecko_bindings::structs;
|
2018-08-15 17:07:11 +03:00
|
|
|
use num_traits::Zero;
|
|
|
|
use parser::{Parse, ParserContext};
|
2018-08-17 15:46:02 +03:00
|
|
|
use std::cmp::{PartialOrd, Ordering};
|
2018-08-15 17:07:11 +03:00
|
|
|
use std::fmt::{self, Write};
|
|
|
|
use str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
|
|
|
|
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
|
|
|
use stylesheets::Origin;
|
2018-08-18 19:41:40 +03:00
|
|
|
use super::Device;
|
|
|
|
use super::media_feature::{Evaluator, MediaFeatureDescription};
|
|
|
|
use super::media_feature::{ParsingRequirements, KeywordDiscriminant};
|
2018-08-15 17:07:11 +03:00
|
|
|
use values::{serialize_atom_identifier, CSSFloat};
|
|
|
|
use values::computed::{self, ToComputedValue};
|
2018-08-18 19:41:40 +03:00
|
|
|
use values::specified::{Integer, Length, Number, Resolution};
|
2018-08-15 17:07:11 +03:00
|
|
|
|
|
|
|
/// An aspect ratio, with a numerator and denominator.
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
|
|
|
|
pub struct AspectRatio(pub u32, pub u32);
|
|
|
|
|
|
|
|
impl ToCss for AspectRatio {
|
|
|
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
|
|
|
where
|
|
|
|
W: fmt::Write,
|
|
|
|
{
|
|
|
|
self.0.to_css(dest)?;
|
|
|
|
dest.write_char('/')?;
|
|
|
|
self.1.to_css(dest)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-17 15:46:02 +03:00
|
|
|
impl PartialOrd for AspectRatio {
|
|
|
|
fn partial_cmp(&self, other: &AspectRatio) -> Option<Ordering> {
|
|
|
|
u64::partial_cmp(
|
|
|
|
&(self.0 as u64 * other.1 as u64),
|
|
|
|
&(self.1 as u64 * other.0 as u64),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-15 17:07:11 +03:00
|
|
|
/// The kind of matching that should be performed on a media feature value.
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
|
|
|
|
pub enum Range {
|
|
|
|
/// At least the specified value.
|
|
|
|
Min,
|
|
|
|
/// At most the specified value.
|
|
|
|
Max,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The operator that was specified in this media feature.
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
|
|
|
|
pub enum Operator {
|
|
|
|
/// =
|
|
|
|
Equal,
|
|
|
|
/// >
|
|
|
|
GreaterThan,
|
|
|
|
/// >=
|
|
|
|
GreaterThanEqual,
|
|
|
|
/// <
|
|
|
|
LessThan,
|
|
|
|
/// <=
|
|
|
|
LessThanEqual,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ToCss for Operator {
|
|
|
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
|
|
|
where
|
|
|
|
W: fmt::Write,
|
|
|
|
{
|
|
|
|
dest.write_str(match *self {
|
|
|
|
Operator::Equal => "=",
|
|
|
|
Operator::LessThan => "<",
|
|
|
|
Operator::LessThanEqual => "<=",
|
|
|
|
Operator::GreaterThan => ">",
|
|
|
|
Operator::GreaterThanEqual => ">=",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Either a `Range` or an `Operator`.
|
|
|
|
///
|
|
|
|
/// Ranged media features are not allowed with operations (that'd make no
|
|
|
|
/// sense).
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
|
|
|
|
pub enum RangeOrOperator {
|
|
|
|
/// A `Range`.
|
|
|
|
Range(Range),
|
|
|
|
/// An `Operator`.
|
|
|
|
Operator(Operator),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RangeOrOperator {
|
2018-08-17 15:46:02 +03:00
|
|
|
/// Evaluate a given range given an optional query value and a value from
|
|
|
|
/// the browser.
|
2018-09-09 17:24:45 +03:00
|
|
|
pub fn evaluate<T>(range_or_op: Option<Self>, query_value: Option<T>, value: T) -> bool
|
2018-08-15 17:07:11 +03:00
|
|
|
where
|
2018-09-09 17:24:45 +03:00
|
|
|
T: PartialOrd + Zero,
|
2018-08-15 17:07:11 +03:00
|
|
|
{
|
2018-08-17 15:46:02 +03:00
|
|
|
match query_value {
|
|
|
|
Some(v) => Self::evaluate_with_query_value(range_or_op, v, value),
|
|
|
|
None => !value.is_zero(),
|
|
|
|
}
|
|
|
|
}
|
2018-08-15 17:07:11 +03:00
|
|
|
|
2018-08-17 15:46:02 +03:00
|
|
|
/// Evaluate a given range given a non-optional query value and a value from
|
|
|
|
/// the browser.
|
2018-09-09 17:24:45 +03:00
|
|
|
pub fn evaluate_with_query_value<T>(range_or_op: Option<Self>, query_value: T, value: T) -> bool
|
2018-08-17 15:46:02 +03:00
|
|
|
where
|
|
|
|
T: PartialOrd,
|
|
|
|
{
|
2018-08-15 17:07:11 +03:00
|
|
|
let cmp = match value.partial_cmp(&query_value) {
|
|
|
|
Some(c) => c,
|
|
|
|
None => return false,
|
|
|
|
};
|
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
|
|
|
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,
|
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A feature expression contains a reference to the media feature, the value
|
|
|
|
/// the media query contained, and the range to evaluate.
|
|
|
|
#[derive(Clone, Debug, MallocSizeOf)]
|
|
|
|
pub struct MediaFeatureExpression {
|
|
|
|
feature: &'static MediaFeatureDescription,
|
|
|
|
value: Option<MediaExpressionValue>,
|
|
|
|
range_or_operator: Option<RangeOrOperator>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for MediaFeatureExpression {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
self.feature as *const _ == other.feature as *const _ &&
|
2018-09-09 17:24:45 +03:00
|
|
|
self.value == other.value &&
|
|
|
|
self.range_or_operator == other.range_or_operator
|
2018-08-15 17:07:11 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ToCss for MediaFeatureExpression {
|
|
|
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
|
|
|
where
|
|
|
|
W: fmt::Write,
|
|
|
|
{
|
|
|
|
dest.write_str("(")?;
|
|
|
|
|
2018-09-09 17:24:45 +03:00
|
|
|
if self
|
|
|
|
.feature
|
|
|
|
.requirements
|
|
|
|
.contains(ParsingRequirements::WEBKIT_PREFIX)
|
|
|
|
{
|
2018-08-15 17:07:11 +03:00
|
|
|
dest.write_str("-webkit-")?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(RangeOrOperator::Range(range)) = self.range_or_operator {
|
|
|
|
match range {
|
|
|
|
Range::Min => dest.write_str("min-")?,
|
|
|
|
Range::Max => dest.write_str("max-")?,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NB: CssStringWriter not needed, feature names are under control.
|
|
|
|
write!(dest, "{}", self.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(")")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Consumes an operation or a colon, or returns an error.
|
2018-09-09 17:24:45 +03:00
|
|
|
fn consume_operation_or_colon(input: &mut Parser) -> Result<Option<Operator>, ()> {
|
2018-08-15 17:07:11 +03:00
|
|
|
let first_delim = {
|
|
|
|
let next_token = match input.next() {
|
|
|
|
Ok(t) => t,
|
|
|
|
Err(..) => return Err(()),
|
|
|
|
};
|
|
|
|
|
|
|
|
match *next_token {
|
|
|
|
Token::Colon => return Ok(None),
|
|
|
|
Token::Delim(oper) => oper,
|
|
|
|
_ => return Err(()),
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Ok(Some(match first_delim {
|
|
|
|
'=' => Operator::Equal,
|
|
|
|
'>' => {
|
|
|
|
if input.try(|i| i.expect_delim('=')).is_ok() {
|
|
|
|
Operator::GreaterThanEqual
|
|
|
|
} else {
|
|
|
|
Operator::GreaterThan
|
|
|
|
}
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
'<' => {
|
|
|
|
if input.try(|i| i.expect_delim('=')).is_ok() {
|
|
|
|
Operator::LessThanEqual
|
|
|
|
} else {
|
|
|
|
Operator::LessThan
|
|
|
|
}
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
_ => return Err(()),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MediaFeatureExpression {
|
|
|
|
fn new(
|
|
|
|
feature: &'static MediaFeatureDescription,
|
|
|
|
value: Option<MediaExpressionValue>,
|
|
|
|
range_or_operator: Option<RangeOrOperator>,
|
|
|
|
) -> Self {
|
2018-09-09 17:24:45 +03:00
|
|
|
Self {
|
|
|
|
feature,
|
|
|
|
value,
|
|
|
|
range_or_operator,
|
|
|
|
}
|
2018-08-15 17:07:11 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse a media expression of the form:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// (media-feature: media-value)
|
|
|
|
/// ```
|
|
|
|
pub fn parse<'i, 't>(
|
|
|
|
context: &ParserContext,
|
|
|
|
input: &mut Parser<'i, 't>,
|
|
|
|
) -> Result<Self, ParseError<'i>> {
|
|
|
|
input.expect_parenthesis_block()?;
|
2018-09-09 17:24:45 +03:00
|
|
|
input.parse_nested_block(|input| Self::parse_in_parenthesis_block(context, input))
|
2018-08-15 17:07:11 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse a media feature expression where we've already consumed the
|
|
|
|
/// parenthesis.
|
|
|
|
pub fn parse_in_parenthesis_block<'i, 't>(
|
|
|
|
context: &ParserContext,
|
|
|
|
input: &mut Parser<'i, 't>,
|
|
|
|
) -> Result<Self, ParseError<'i>> {
|
2018-08-18 19:41:40 +03:00
|
|
|
#[cfg(feature = "gecko")]
|
|
|
|
use gecko::media_features::MEDIA_FEATURES;
|
|
|
|
#[cfg(feature = "servo")]
|
|
|
|
use servo::media_queries::MEDIA_FEATURES;
|
|
|
|
|
2018-08-15 17:07:11 +03:00
|
|
|
// FIXME: remove extra indented block when lifetimes are non-lexical
|
|
|
|
let feature;
|
|
|
|
let range;
|
|
|
|
{
|
|
|
|
let location = input.current_source_location();
|
|
|
|
let ident = input.expect_ident()?;
|
|
|
|
|
|
|
|
let mut requirements = ParsingRequirements::empty();
|
|
|
|
|
2018-09-09 17:24:45 +03:00
|
|
|
if context.chrome_rules_enabled() || context.stylesheet_origin == Origin::UserAgent {
|
2018-08-15 17:07:11 +03:00
|
|
|
requirements.insert(ParsingRequirements::CHROME_AND_UA_ONLY);
|
|
|
|
}
|
|
|
|
|
|
|
|
let result = {
|
|
|
|
let mut feature_name = &**ident;
|
|
|
|
|
|
|
|
#[cfg(feature = "gecko")]
|
|
|
|
{
|
|
|
|
if unsafe { structs::StaticPrefs_sVarCache_layout_css_prefixes_webkit } &&
|
|
|
|
starts_with_ignore_ascii_case(feature_name, "-webkit-")
|
|
|
|
{
|
|
|
|
feature_name = &feature_name[8..];
|
|
|
|
requirements.insert(ParsingRequirements::WEBKIT_PREFIX);
|
|
|
|
if unsafe {
|
|
|
|
structs::StaticPrefs_sVarCache_layout_css_prefixes_device_pixel_ratio_webkit
|
|
|
|
} {
|
2018-09-09 17:24:45 +03:00
|
|
|
requirements.insert(
|
|
|
|
ParsingRequirements::WEBKIT_DEVICE_PIXEL_RATIO_PREF_ENABLED,
|
|
|
|
);
|
2018-08-15 17:07:11 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
|
|
|
|
feature_name = &feature_name[4..];
|
|
|
|
Some(Range::Min)
|
|
|
|
} else if starts_with_ignore_ascii_case(feature_name, "max-") {
|
|
|
|
feature_name = &feature_name[4..];
|
|
|
|
Some(Range::Max)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
let atom = Atom::from(string_as_ascii_lowercase(feature_name));
|
|
|
|
match MEDIA_FEATURES.iter().find(|f| f.name == atom) {
|
|
|
|
Some(f) => Ok((f, range)),
|
|
|
|
None => Err(()),
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Ok((f, r)) => {
|
|
|
|
feature = f;
|
|
|
|
range = r;
|
|
|
|
},
|
|
|
|
Err(()) => {
|
|
|
|
return Err(location.new_custom_error(
|
|
|
|
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
|
|
|
))
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
if !(feature.requirements & !requirements).is_empty() {
|
|
|
|
return Err(location.new_custom_error(
|
|
|
|
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if range.is_some() && !feature.allows_ranges() {
|
|
|
|
return Err(location.new_custom_error(
|
|
|
|
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let operator = input.try(consume_operation_or_colon);
|
|
|
|
let operator = match operator {
|
|
|
|
Err(..) => {
|
|
|
|
// If there's no colon, this is a media 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() {
|
2018-09-09 17:24:45 +03:00
|
|
|
return Err(
|
|
|
|
input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
|
|
|
|
);
|
2018-08-15 17:07:11 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(Self::new(feature, None, None));
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
Ok(operator) => operator,
|
|
|
|
};
|
|
|
|
|
|
|
|
let range_or_operator = match range {
|
|
|
|
Some(range) => {
|
|
|
|
if operator.is_some() {
|
2018-09-09 17:24:45 +03:00
|
|
|
return Err(
|
|
|
|
input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
|
|
|
|
);
|
2018-08-15 17:07:11 +03:00
|
|
|
}
|
|
|
|
Some(RangeOrOperator::Range(range))
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
|
|
|
None => match operator {
|
|
|
|
Some(operator) => {
|
|
|
|
if !feature.allows_ranges() {
|
|
|
|
return Err(input
|
|
|
|
.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
|
2018-08-15 17:07:11 +03:00
|
|
|
}
|
2018-09-09 17:24:45 +03:00
|
|
|
Some(RangeOrOperator::Operator(operator))
|
|
|
|
},
|
|
|
|
None => None,
|
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
};
|
|
|
|
|
2018-09-09 17:24:45 +03:00
|
|
|
let value = MediaExpressionValue::parse(feature, context, input).map_err(|err| {
|
|
|
|
err.location
|
|
|
|
.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
|
|
|
|
})?;
|
2018-08-15 17:07:11 +03:00
|
|
|
|
|
|
|
Ok(Self::new(feature, Some(value), range_or_operator))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns whether this media query evaluates to true for the given device.
|
|
|
|
pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
|
|
|
|
let value = self.value.as_ref();
|
|
|
|
|
|
|
|
macro_rules! expect {
|
|
|
|
($variant:ident) => {
|
2018-09-09 17:24:45 +03:00
|
|
|
value.map(|value| match *value {
|
|
|
|
MediaExpressionValue::$variant(ref v) => v,
|
|
|
|
_ => unreachable!("Unexpected MediaExpressionValue"),
|
2018-08-15 17:07:11 +03:00
|
|
|
})
|
2018-09-09 17:24:45 +03:00
|
|
|
};
|
2018-08-15 17:07:11 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
match self.feature.evaluator {
|
|
|
|
Evaluator::Length(eval) => {
|
|
|
|
let computed = expect!(Length).map(|specified| {
|
|
|
|
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
|
|
|
|
specified.to_computed_value(context)
|
|
|
|
})
|
|
|
|
});
|
|
|
|
eval(device, computed, self.range_or_operator)
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
Evaluator::Integer(eval) => {
|
|
|
|
eval(device, expect!(Integer).cloned(), self.range_or_operator)
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
|
|
|
Evaluator::Float(eval) => eval(device, expect!(Float).cloned(), self.range_or_operator),
|
2018-08-15 17:07:11 +03:00
|
|
|
Evaluator::IntRatio(eval) => {
|
|
|
|
eval(device, expect!(IntRatio).cloned(), self.range_or_operator)
|
|
|
|
},
|
|
|
|
Evaluator::Resolution(eval) => {
|
|
|
|
let computed = expect!(Resolution).map(|specified| {
|
|
|
|
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
|
|
|
|
specified.to_computed_value(context)
|
|
|
|
})
|
|
|
|
});
|
|
|
|
eval(device, computed, self.range_or_operator)
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
Evaluator::Enumerated { evaluator, .. } => {
|
2018-09-09 17:24:45 +03:00
|
|
|
evaluator(device, expect!(Enumerated).cloned(), self.range_or_operator)
|
|
|
|
},
|
|
|
|
Evaluator::Ident(eval) => eval(device, expect!(Ident).cloned(), self.range_or_operator),
|
|
|
|
Evaluator::BoolInteger(eval) => eval(
|
|
|
|
device,
|
|
|
|
expect!(BoolInteger).cloned(),
|
|
|
|
self.range_or_operator,
|
|
|
|
),
|
2018-08-15 17:07:11 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A value found or expected in a media expression.
|
|
|
|
///
|
|
|
|
/// FIXME(emilio): How should calc() serialize in the Number / Integer /
|
|
|
|
/// BoolInteger / IntRatio 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)]
|
|
|
|
pub enum MediaExpressionValue {
|
|
|
|
/// A length.
|
|
|
|
Length(Length),
|
|
|
|
/// A (non-negative) integer.
|
|
|
|
Integer(u32),
|
|
|
|
/// A floating point value.
|
|
|
|
Float(CSSFloat),
|
|
|
|
/// A boolean value, specified as an integer (i.e., either 0 or 1).
|
|
|
|
BoolInteger(bool),
|
|
|
|
/// Two integers separated by '/', with optional whitespace on either side
|
|
|
|
/// of the '/'.
|
|
|
|
IntRatio(AspectRatio),
|
|
|
|
/// A resolution.
|
|
|
|
Resolution(Resolution),
|
|
|
|
/// An enumerated value, defined by the variant keyword table in the
|
|
|
|
/// feature's `mData` member.
|
|
|
|
Enumerated(KeywordDiscriminant),
|
|
|
|
/// An identifier.
|
|
|
|
Ident(Atom),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MediaExpressionValue {
|
2018-09-09 17:24:45 +03:00
|
|
|
fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &MediaFeatureExpression) -> fmt::Result
|
2018-08-15 17:07:11 +03:00
|
|
|
where
|
|
|
|
W: fmt::Write,
|
|
|
|
{
|
|
|
|
match *self {
|
|
|
|
MediaExpressionValue::Length(ref l) => l.to_css(dest),
|
|
|
|
MediaExpressionValue::Integer(v) => v.to_css(dest),
|
|
|
|
MediaExpressionValue::Float(v) => v.to_css(dest),
|
|
|
|
MediaExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
|
2018-09-09 17:24:45 +03:00
|
|
|
MediaExpressionValue::IntRatio(ratio) => ratio.to_css(dest),
|
2018-08-15 17:07:11 +03:00
|
|
|
MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
|
|
|
|
MediaExpressionValue::Ident(ref ident) => serialize_atom_identifier(ident, dest),
|
2018-09-09 17:24:45 +03:00
|
|
|
MediaExpressionValue::Enumerated(value) => match for_expr.feature.evaluator {
|
|
|
|
Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
|
|
|
|
_ => unreachable!(),
|
2018-08-15 17:07:11 +03:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse<'i, 't>(
|
|
|
|
for_feature: &MediaFeatureDescription,
|
|
|
|
context: &ParserContext,
|
|
|
|
input: &mut Parser<'i, 't>,
|
|
|
|
) -> Result<MediaExpressionValue, ParseError<'i>> {
|
|
|
|
Ok(match for_feature.evaluator {
|
|
|
|
Evaluator::Length(..) => {
|
|
|
|
let length = Length::parse_non_negative(context, input)?;
|
|
|
|
MediaExpressionValue::Length(length)
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
Evaluator::Integer(..) => {
|
|
|
|
let integer = Integer::parse_non_negative(context, input)?;
|
|
|
|
MediaExpressionValue::Integer(integer.value() as u32)
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
MediaExpressionValue::BoolInteger(value == 1)
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
Evaluator::Float(..) => {
|
|
|
|
let number = Number::parse(context, input)?;
|
|
|
|
MediaExpressionValue::Float(number.get())
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
Evaluator::IntRatio(..) => {
|
|
|
|
let a = Integer::parse_positive(context, input)?;
|
|
|
|
input.expect_delim('/')?;
|
|
|
|
let b = Integer::parse_positive(context, input)?;
|
2018-09-09 17:24:45 +03:00
|
|
|
MediaExpressionValue::IntRatio(AspectRatio(a.value() as u32, b.value() as u32))
|
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
Evaluator::Resolution(..) => {
|
|
|
|
MediaExpressionValue::Resolution(Resolution::parse(context, input)?)
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
Evaluator::Enumerated { parser, .. } => {
|
|
|
|
MediaExpressionValue::Enumerated(parser(context, input)?)
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
Evaluator::Ident(..) => {
|
|
|
|
MediaExpressionValue::Ident(Atom::from(input.expect_ident()?.as_ref()))
|
2018-09-09 17:24:45 +03:00
|
|
|
},
|
2018-08-15 17:07:11 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|