tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

feature_expression.rs (26130B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      4 
      5 //! Parsing for query feature expressions, like `(foo: bar)` or
      6 //! `(width >= 400px)`.
      7 
      8 use super::feature::{Evaluator, QueryFeatureDescription};
      9 use super::feature::{FeatureFlags, KeywordDiscriminant};
     10 use crate::derives::*;
     11 use crate::parser::{Parse, ParserContext};
     12 use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
     13 use crate::values::computed::{self, Ratio, ToComputedValue};
     14 use crate::values::specified::{Integer, Length, Number, Resolution};
     15 use crate::values::CSSFloat;
     16 use crate::{Atom, Zero};
     17 use cssparser::{Parser, Token};
     18 use selectors::kleene_value::KleeneValue;
     19 use std::cmp::Ordering;
     20 use std::fmt::{self, Write};
     21 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
     22 
     23 /// Whether we're parsing a media or container query feature.
     24 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
     25 pub enum FeatureType {
     26    /// We're parsing a media feature.
     27    Media,
     28    /// We're parsing a container feature.
     29    Container,
     30 }
     31 
     32 impl FeatureType {
     33    fn features(&self) -> &'static [QueryFeatureDescription] {
     34        #[cfg(feature = "gecko")]
     35        use crate::gecko::media_features::MEDIA_FEATURES;
     36        #[cfg(feature = "servo")]
     37        use crate::servo::media_queries::MEDIA_FEATURES;
     38 
     39        use crate::stylesheets::container_rule::CONTAINER_FEATURES;
     40 
     41        match *self {
     42            FeatureType::Media => &MEDIA_FEATURES,
     43            FeatureType::Container => &CONTAINER_FEATURES,
     44        }
     45    }
     46 
     47    fn find_feature(&self, name: &Atom) -> Option<(usize, &'static QueryFeatureDescription)> {
     48        self.features()
     49            .iter()
     50            .enumerate()
     51            .find(|(_, f)| f.name == *name)
     52    }
     53 }
     54 
     55 /// The kind of matching that should be performed on a feature value.
     56 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
     57 enum LegacyRange {
     58    /// At least the specified value.
     59    Min,
     60    /// At most the specified value.
     61    Max,
     62 }
     63 
     64 /// The operator that was specified in this feature.
     65 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
     66 enum Operator {
     67    /// =
     68    Equal,
     69    /// >
     70    GreaterThan,
     71    /// >=
     72    GreaterThanEqual,
     73    /// <
     74    LessThan,
     75    /// <=
     76    LessThanEqual,
     77 }
     78 
     79 impl ToCss for Operator {
     80    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
     81    where
     82        W: fmt::Write,
     83    {
     84        dest.write_str(match *self {
     85            Self::Equal => "=",
     86            Self::LessThan => "<",
     87            Self::LessThanEqual => "<=",
     88            Self::GreaterThan => ">",
     89            Self::GreaterThanEqual => ">=",
     90        })
     91    }
     92 }
     93 
     94 impl Operator {
     95    fn is_compatible_with(self, right_op: Self) -> bool {
     96        // Some operators are not compatible with each other in multi-range
     97        // context.
     98        match self {
     99            Self::Equal => false,
    100            Self::GreaterThan | Self::GreaterThanEqual => {
    101                matches!(right_op, Self::GreaterThan | Self::GreaterThanEqual)
    102            },
    103            Self::LessThan | Self::LessThanEqual => {
    104                matches!(right_op, Self::LessThan | Self::LessThanEqual)
    105            },
    106        }
    107    }
    108 
    109    fn evaluate(&self, cmp: Ordering) -> bool {
    110        match *self {
    111            Self::Equal => cmp == Ordering::Equal,
    112            Self::GreaterThan => cmp == Ordering::Greater,
    113            Self::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
    114            Self::LessThan => cmp == Ordering::Less,
    115            Self::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
    116        }
    117    }
    118 
    119    fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
    120        let location = input.current_source_location();
    121        let operator = match *input.next()? {
    122            Token::Delim('=') => return Ok(Operator::Equal),
    123            Token::Delim('>') => Operator::GreaterThan,
    124            Token::Delim('<') => Operator::LessThan,
    125            ref t => return Err(location.new_unexpected_token_error(t.clone())),
    126        };
    127 
    128        // https://drafts.csswg.org/mediaqueries-4/#mq-syntax:
    129        //
    130        //     No whitespace is allowed between the “<” or “>”
    131        //     <delim-token>s and the following “=” <delim-token>, if it’s
    132        //     present.
    133        //
    134        // TODO(emilio): Maybe we should ignore comments as well?
    135        // https://github.com/w3c/csswg-drafts/issues/6248
    136        let parsed_equal = input
    137            .try_parse(|i| {
    138                let t = i.next_including_whitespace().map_err(|_| ())?;
    139                if !matches!(t, Token::Delim('=')) {
    140                    return Err(());
    141                }
    142                Ok(())
    143            })
    144            .is_ok();
    145 
    146        if !parsed_equal {
    147            return Ok(operator);
    148        }
    149 
    150        Ok(match operator {
    151            Operator::GreaterThan => Operator::GreaterThanEqual,
    152            Operator::LessThan => Operator::LessThanEqual,
    153            _ => unreachable!(),
    154        })
    155    }
    156 }
    157 
    158 #[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
    159 enum QueryFeatureExpressionKind {
    160    /// Just the media feature name.
    161    Empty,
    162 
    163    /// A single value.
    164    Single(QueryExpressionValue),
    165 
    166    /// Legacy range syntax (min-*: value) or so.
    167    LegacyRange(LegacyRange, QueryExpressionValue),
    168 
    169    /// Modern range context syntax:
    170    /// https://drafts.csswg.org/mediaqueries-5/#mq-range-context
    171    Range {
    172        left: Option<(Operator, QueryExpressionValue)>,
    173        right: Option<(Operator, QueryExpressionValue)>,
    174    },
    175 }
    176 
    177 impl QueryFeatureExpressionKind {
    178    /// Evaluate a given range given an optional query value and a value from
    179    /// the browser.
    180    fn evaluate<T>(
    181        &self,
    182        context_value: T,
    183        mut compute: impl FnMut(&QueryExpressionValue) -> T,
    184    ) -> bool
    185    where
    186        T: PartialOrd + Zero,
    187    {
    188        match *self {
    189            Self::Empty => return !context_value.is_zero(),
    190            Self::Single(ref value) => {
    191                let value = compute(value);
    192                let cmp = match context_value.partial_cmp(&value) {
    193                    Some(c) => c,
    194                    None => return false,
    195                };
    196                cmp == Ordering::Equal
    197            },
    198            Self::LegacyRange(ref range, ref value) => {
    199                let value = compute(value);
    200                let cmp = match context_value.partial_cmp(&value) {
    201                    Some(c) => c,
    202                    None => return false,
    203                };
    204                cmp == Ordering::Equal
    205                    || match range {
    206                        LegacyRange::Min => cmp == Ordering::Greater,
    207                        LegacyRange::Max => cmp == Ordering::Less,
    208                    }
    209            },
    210            Self::Range {
    211                ref left,
    212                ref right,
    213            } => {
    214                debug_assert!(left.is_some() || right.is_some());
    215                if let Some((ref op, ref value)) = left {
    216                    let value = compute(value);
    217                    let cmp = match value.partial_cmp(&context_value) {
    218                        Some(c) => c,
    219                        None => return false,
    220                    };
    221                    if !op.evaluate(cmp) {
    222                        return false;
    223                    }
    224                }
    225                if let Some((ref op, ref value)) = right {
    226                    let value = compute(value);
    227                    let cmp = match context_value.partial_cmp(&value) {
    228                        Some(c) => c,
    229                        None => return false,
    230                    };
    231                    if !op.evaluate(cmp) {
    232                        return false;
    233                    }
    234                }
    235                true
    236            },
    237        }
    238    }
    239 
    240    /// Non-ranged features only need to compare to one value at most.
    241    fn non_ranged_value(&self) -> Option<&QueryExpressionValue> {
    242        match *self {
    243            Self::Empty => None,
    244            Self::Single(ref v) => Some(v),
    245            Self::LegacyRange(..) | Self::Range { .. } => {
    246                debug_assert!(false, "Unexpected ranged value in non-ranged feature!");
    247                None
    248            },
    249        }
    250    }
    251 }
    252 
    253 /// A feature expression contains a reference to the feature, the value the
    254 /// query contained, and the range to evaluate.
    255 #[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
    256 pub struct QueryFeatureExpression {
    257    feature_type: FeatureType,
    258    feature_index: usize,
    259    kind: QueryFeatureExpressionKind,
    260 }
    261 
    262 impl ToCss for QueryFeatureExpression {
    263    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    264    where
    265        W: fmt::Write,
    266    {
    267        dest.write_char('(')?;
    268 
    269        match self.kind {
    270            QueryFeatureExpressionKind::Empty => self.write_name(dest)?,
    271            QueryFeatureExpressionKind::Single(ref v)
    272            | QueryFeatureExpressionKind::LegacyRange(_, ref v) => {
    273                self.write_name(dest)?;
    274                dest.write_str(": ")?;
    275                v.to_css(dest, self)?;
    276            },
    277            QueryFeatureExpressionKind::Range {
    278                ref left,
    279                ref right,
    280            } => {
    281                if let Some((ref op, ref val)) = left {
    282                    val.to_css(dest, self)?;
    283                    dest.write_char(' ')?;
    284                    op.to_css(dest)?;
    285                    dest.write_char(' ')?;
    286                }
    287                self.write_name(dest)?;
    288                if let Some((ref op, ref val)) = right {
    289                    dest.write_char(' ')?;
    290                    op.to_css(dest)?;
    291                    dest.write_char(' ')?;
    292                    val.to_css(dest, self)?;
    293                }
    294            },
    295        }
    296        dest.write_char(')')
    297    }
    298 }
    299 
    300 fn consume_operation_or_colon<'i>(
    301    input: &mut Parser<'i, '_>,
    302 ) -> Result<Option<Operator>, ParseError<'i>> {
    303    if input.try_parse(|input| input.expect_colon()).is_ok() {
    304        return Ok(None);
    305    }
    306    Operator::parse(input).map(|op| Some(op))
    307 }
    308 
    309 #[allow(unused_variables)]
    310 fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool {
    311    #[cfg(feature = "gecko")]
    312    {
    313        // prefers-reduced-transparency is always enabled in the ua and chrome. On
    314        // the web it is hidden behind a preference (see Bug 1822176).
    315        if *feature == atom!("prefers-reduced-transparency") {
    316            return !context.chrome_rules_enabled()
    317                && !static_prefs::pref!("layout.css.prefers-reduced-transparency.enabled");
    318        }
    319 
    320        // inverted-colors is always enabled in the ua and chrome. On
    321        // the web it is hidden behind a preference.
    322        if *feature == atom!("inverted-colors") {
    323            return !context.chrome_rules_enabled()
    324                && !static_prefs::pref!("layout.css.inverted-colors.enabled");
    325        }
    326    }
    327    false
    328 }
    329 
    330 impl QueryFeatureExpression {
    331    fn new(
    332        feature_type: FeatureType,
    333        feature_index: usize,
    334        kind: QueryFeatureExpressionKind,
    335    ) -> Self {
    336        debug_assert!(feature_index < feature_type.features().len());
    337        Self {
    338            feature_type,
    339            feature_index,
    340            kind,
    341        }
    342    }
    343 
    344    fn write_name<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    345    where
    346        W: fmt::Write,
    347    {
    348        let feature = self.feature();
    349        if feature.flags.contains(FeatureFlags::WEBKIT_PREFIX) {
    350            dest.write_str("-webkit-")?;
    351        }
    352 
    353        if let QueryFeatureExpressionKind::LegacyRange(range, _) = self.kind {
    354            match range {
    355                LegacyRange::Min => dest.write_str("min-")?,
    356                LegacyRange::Max => dest.write_str("max-")?,
    357            }
    358        }
    359 
    360        // NB: CssStringWriter not needed, feature names are under control.
    361        write!(dest, "{}", feature.name)?;
    362 
    363        Ok(())
    364    }
    365 
    366    fn feature(&self) -> &'static QueryFeatureDescription {
    367        &self.feature_type.features()[self.feature_index]
    368    }
    369 
    370    /// Returns the feature flags for our feature.
    371    pub fn feature_flags(&self) -> FeatureFlags {
    372        self.feature().flags
    373    }
    374 
    375    fn parse_feature_name<'i, 't>(
    376        context: &ParserContext,
    377        input: &mut Parser<'i, 't>,
    378        feature_type: FeatureType,
    379    ) -> Result<(usize, Option<LegacyRange>), ParseError<'i>> {
    380        let mut flags = FeatureFlags::empty();
    381        let location = input.current_source_location();
    382        let ident = input.expect_ident()?;
    383 
    384        if context.chrome_rules_enabled() {
    385            flags.insert(FeatureFlags::CHROME_AND_UA_ONLY);
    386        }
    387 
    388        let mut feature_name = &**ident;
    389        if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
    390            feature_name = &feature_name[8..];
    391            flags.insert(FeatureFlags::WEBKIT_PREFIX);
    392        }
    393 
    394        let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
    395            feature_name = &feature_name[4..];
    396            Some(LegacyRange::Min)
    397        } else if starts_with_ignore_ascii_case(feature_name, "max-") {
    398            feature_name = &feature_name[4..];
    399            Some(LegacyRange::Max)
    400        } else {
    401            None
    402        };
    403 
    404        let atom = Atom::from(string_as_ascii_lowercase(feature_name));
    405        let (feature_index, feature) = match feature_type.find_feature(&atom) {
    406            Some((i, f)) => (i, f),
    407            None => {
    408                return Err(location.new_custom_error(
    409                    StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
    410                ))
    411            },
    412        };
    413 
    414        if disabled_by_pref(&feature.name, context)
    415            || !flags.contains(feature.flags.parsing_requirements())
    416            || (range.is_some() && !feature.allows_ranges())
    417        {
    418            return Err(location.new_custom_error(
    419                StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
    420            ));
    421        }
    422 
    423        Ok((feature_index, range))
    424    }
    425 
    426    /// Parses the following range syntax:
    427    ///
    428    ///   (feature-value <operator> feature-name)
    429    ///   (feature-value <operator> feature-name <operator> feature-value)
    430    fn parse_multi_range_syntax<'i, 't>(
    431        context: &ParserContext,
    432        input: &mut Parser<'i, 't>,
    433        feature_type: FeatureType,
    434    ) -> Result<Self, ParseError<'i>> {
    435        let start = input.state();
    436 
    437        // To parse the values, we first need to find the feature name. We rely
    438        // on feature values for ranged features not being able to be top-level
    439        // <ident>s, which holds.
    440        let feature_index = loop {
    441            // NOTE: parse_feature_name advances the input.
    442            if let Ok((index, range)) = Self::parse_feature_name(context, input, feature_type) {
    443                if range.is_some() {
    444                    // Ranged names are not allowed here.
    445                    return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    446                }
    447                break index;
    448            }
    449            if input.is_exhausted() {
    450                return Err(start
    451                    .source_location()
    452                    .new_custom_error(StyleParseErrorKind::UnspecifiedError));
    453            }
    454        };
    455 
    456        input.reset(&start);
    457 
    458        let feature = &feature_type.features()[feature_index];
    459        let left_val = QueryExpressionValue::parse(feature, context, input)?;
    460        let left_op = Operator::parse(input)?;
    461 
    462        {
    463            let (parsed_index, _) = Self::parse_feature_name(context, input, feature_type)?;
    464            debug_assert_eq!(
    465                parsed_index, feature_index,
    466                "How did we find a different feature?"
    467            );
    468        }
    469 
    470        let right_op = input.try_parse(Operator::parse).ok();
    471        let right = match right_op {
    472            Some(op) => {
    473                if !left_op.is_compatible_with(op) {
    474                    return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    475                }
    476                Some((op, QueryExpressionValue::parse(feature, context, input)?))
    477            },
    478            None => None,
    479        };
    480        Ok(Self::new(
    481            feature_type,
    482            feature_index,
    483            QueryFeatureExpressionKind::Range {
    484                left: Some((left_op, left_val)),
    485                right,
    486            },
    487        ))
    488    }
    489 
    490    /// Parse a feature expression where we've already consumed the parenthesis.
    491    pub fn parse_in_parenthesis_block<'i, 't>(
    492        context: &ParserContext,
    493        input: &mut Parser<'i, 't>,
    494        feature_type: FeatureType,
    495    ) -> Result<Self, ParseError<'i>> {
    496        let (feature_index, range) =
    497            match input.try_parse(|input| Self::parse_feature_name(context, input, feature_type)) {
    498                Ok(v) => v,
    499                Err(e) => {
    500                    if let Ok(expr) = Self::parse_multi_range_syntax(context, input, feature_type) {
    501                        return Ok(expr);
    502                    }
    503                    return Err(e);
    504                },
    505            };
    506        let operator = input.try_parse(consume_operation_or_colon);
    507        let operator = match operator {
    508            Err(..) => {
    509                // If there's no colon, this is a query of the form
    510                // '(<feature>)', that is, there's no value specified.
    511                //
    512                // Gecko doesn't allow ranged expressions without a
    513                // value, so just reject them here too.
    514                if range.is_some() {
    515                    return Err(
    516                        input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
    517                    );
    518                }
    519 
    520                return Ok(Self::new(
    521                    feature_type,
    522                    feature_index,
    523                    QueryFeatureExpressionKind::Empty,
    524                ));
    525            },
    526            Ok(operator) => operator,
    527        };
    528 
    529        let feature = &feature_type.features()[feature_index];
    530 
    531        let value = QueryExpressionValue::parse(feature, context, input).map_err(|err| {
    532            err.location
    533                .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
    534        })?;
    535 
    536        let kind = match range {
    537            Some(range) => {
    538                if operator.is_some() {
    539                    return Err(
    540                        input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
    541                    );
    542                }
    543                QueryFeatureExpressionKind::LegacyRange(range, value)
    544            },
    545            None => match operator {
    546                Some(operator) => {
    547                    if !feature.allows_ranges() {
    548                        return Err(input
    549                            .new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
    550                    }
    551                    QueryFeatureExpressionKind::Range {
    552                        left: None,
    553                        right: Some((operator, value)),
    554                    }
    555                },
    556                None => QueryFeatureExpressionKind::Single(value),
    557            },
    558        };
    559 
    560        Ok(Self::new(feature_type, feature_index, kind))
    561    }
    562 
    563    /// Returns whether this query evaluates to true for the given device.
    564    pub fn matches(&self, context: &computed::Context) -> KleeneValue {
    565        macro_rules! expect {
    566            ($variant:ident, $v:expr) => {
    567                match *$v {
    568                    QueryExpressionValue::$variant(ref v) => v,
    569                    _ => unreachable!("Unexpected QueryExpressionValue"),
    570                }
    571            };
    572        }
    573 
    574        KleeneValue::from(match self.feature().evaluator {
    575            Evaluator::Length(eval) => {
    576                let v = eval(context);
    577                self.kind
    578                    .evaluate(v, |v| expect!(Length, v).to_computed_value(context))
    579            },
    580            Evaluator::OptionalLength(eval) => {
    581                let v = match eval(context) {
    582                    Some(v) => v,
    583                    None => return KleeneValue::Unknown,
    584                };
    585                self.kind
    586                    .evaluate(v, |v| expect!(Length, v).to_computed_value(context))
    587            },
    588            Evaluator::Integer(eval) => {
    589                let v = eval(context);
    590                self.kind.evaluate(v, |v| *expect!(Integer, v))
    591            },
    592            Evaluator::Float(eval) => {
    593                let v = eval(context);
    594                self.kind.evaluate(v, |v| *expect!(Float, v))
    595            },
    596            Evaluator::NumberRatio(eval) => {
    597                let ratio = eval(context);
    598                // A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value()
    599                // to convert it if necessary.
    600                // FIXME: we may need to update here once
    601                // https://github.com/w3c/csswg-drafts/issues/4954 got resolved.
    602                self.kind
    603                    .evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
    604            },
    605            Evaluator::OptionalNumberRatio(eval) => {
    606                let ratio = match eval(context) {
    607                    Some(v) => v,
    608                    None => return KleeneValue::Unknown,
    609                };
    610                // See above for subtleties here.
    611                self.kind
    612                    .evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
    613            },
    614            Evaluator::Resolution(eval) => {
    615                let v = eval(context).dppx();
    616                self.kind.evaluate(v, |v| {
    617                    expect!(Resolution, v).to_computed_value(context).dppx()
    618                })
    619            },
    620            Evaluator::Enumerated { evaluator, .. } => {
    621                let computed = self
    622                    .kind
    623                    .non_ranged_value()
    624                    .map(|v| *expect!(Enumerated, v));
    625                return evaluator(context, computed);
    626            },
    627            Evaluator::BoolInteger(eval) => {
    628                let computed = self
    629                    .kind
    630                    .non_ranged_value()
    631                    .map(|v| *expect!(BoolInteger, v));
    632                let boolean = eval(context);
    633                computed.map_or(boolean, |v| v == boolean)
    634            },
    635        })
    636    }
    637 }
    638 
    639 /// A value found or expected in a expression.
    640 ///
    641 /// FIXME(emilio): How should calc() serialize in the Number / Integer /
    642 /// BoolInteger / NumberRatio case, as computed or as specified value?
    643 ///
    644 /// If the first, this would need to store the relevant values.
    645 ///
    646 /// See: https://github.com/w3c/csswg-drafts/issues/1968
    647 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
    648 pub enum QueryExpressionValue {
    649    /// A length.
    650    Length(Length),
    651    /// An integer.
    652    Integer(i32),
    653    /// A floating point value.
    654    Float(CSSFloat),
    655    /// A boolean value, specified as an integer (i.e., either 0 or 1).
    656    BoolInteger(bool),
    657    /// A single non-negative number or two non-negative numbers separated by '/',
    658    /// with optional whitespace on either side of the '/'.
    659    NumberRatio(Ratio),
    660    /// A resolution.
    661    Resolution(Resolution),
    662    /// An enumerated value, defined by the variant keyword table in the
    663    /// feature's `mData` member.
    664    Enumerated(KeywordDiscriminant),
    665 }
    666 
    667 impl QueryExpressionValue {
    668    fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &QueryFeatureExpression) -> fmt::Result
    669    where
    670        W: fmt::Write,
    671    {
    672        match *self {
    673            QueryExpressionValue::Length(ref l) => l.to_css(dest),
    674            QueryExpressionValue::Integer(v) => v.to_css(dest),
    675            QueryExpressionValue::Float(v) => v.to_css(dest),
    676            QueryExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
    677            QueryExpressionValue::NumberRatio(ratio) => ratio.to_css(dest),
    678            QueryExpressionValue::Resolution(ref r) => r.to_css(dest),
    679            QueryExpressionValue::Enumerated(value) => match for_expr.feature().evaluator {
    680                Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
    681                _ => unreachable!(),
    682            },
    683        }
    684    }
    685 
    686    fn parse<'i, 't>(
    687        for_feature: &QueryFeatureDescription,
    688        context: &ParserContext,
    689        input: &mut Parser<'i, 't>,
    690    ) -> Result<QueryExpressionValue, ParseError<'i>> {
    691        Ok(match for_feature.evaluator {
    692            Evaluator::OptionalLength(..) | Evaluator::Length(..) => {
    693                let length = Length::parse(context, input)?;
    694                QueryExpressionValue::Length(length)
    695            },
    696            Evaluator::Integer(..) => {
    697                let integer = Integer::parse(context, input)?;
    698                QueryExpressionValue::Integer(integer.value())
    699            },
    700            Evaluator::BoolInteger(..) => {
    701                let integer = Integer::parse_non_negative(context, input)?;
    702                let value = integer.value();
    703                if value > 1 {
    704                    return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    705                }
    706                QueryExpressionValue::BoolInteger(value == 1)
    707            },
    708            Evaluator::Float(..) => {
    709                let number = Number::parse(context, input)?;
    710                QueryExpressionValue::Float(number.get())
    711            },
    712            Evaluator::OptionalNumberRatio(..) | Evaluator::NumberRatio(..) => {
    713                use crate::values::specified::Ratio as SpecifiedRatio;
    714                let ratio = SpecifiedRatio::parse(context, input)?;
    715                QueryExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
    716            },
    717            Evaluator::Resolution(..) => {
    718                QueryExpressionValue::Resolution(Resolution::parse(context, input)?)
    719            },
    720            Evaluator::Enumerated { parser, .. } => {
    721                QueryExpressionValue::Enumerated(parser(context, input)?)
    722            },
    723        })
    724    }
    725 }