style/queries/
feature.rs

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//! Query features.
6
7use crate::derives::*;
8use crate::parser::ParserContext;
9use crate::values::computed::{self, CSSPixelLength, Ratio, Resolution};
10use crate::Atom;
11use cssparser::Parser;
12use selectors::kleene_value::KleeneValue;
13use std::fmt;
14use style_traits::ParseError;
15
16/// A generic discriminant for an enum value.
17pub type KeywordDiscriminant = u8;
18
19type QueryFeatureGetter<T> = fn(device: &computed::Context) -> T;
20
21/// Serializes a given discriminant.
22///
23/// FIXME(emilio): we could prevent this allocation if the ToCss code would
24/// generate a method for keywords to get the static string or something.
25pub type KeywordSerializer = fn(KeywordDiscriminant) -> String;
26
27/// Parses a given identifier.
28pub type KeywordParser = for<'a, 'i, 't> fn(
29    context: &'a ParserContext,
30    input: &'a mut Parser<'i, 't>,
31) -> Result<KeywordDiscriminant, ParseError<'i>>;
32
33/// An evaluator for a given feature.
34///
35/// This determines the kind of values that get parsed, too.
36#[allow(missing_docs)]
37pub enum Evaluator {
38    Length(QueryFeatureGetter<CSSPixelLength>),
39    OptionalLength(QueryFeatureGetter<Option<CSSPixelLength>>),
40    Integer(QueryFeatureGetter<i32>),
41    Float(QueryFeatureGetter<f32>),
42    BoolInteger(QueryFeatureGetter<bool>),
43    /// A non-negative number ratio, such as the one from device-pixel-ratio.
44    NumberRatio(QueryFeatureGetter<Ratio>),
45    OptionalNumberRatio(QueryFeatureGetter<Option<Ratio>>),
46    /// A resolution.
47    Resolution(QueryFeatureGetter<Resolution>),
48    /// A keyword value.
49    Enumerated {
50        /// The parser to get a discriminant given a string.
51        parser: KeywordParser,
52        /// The serializer to get a string from a discriminant.
53        ///
54        /// This is guaranteed to be called with a keyword that `parser` has
55        /// produced.
56        serializer: KeywordSerializer,
57        /// The evaluator itself. This is guaranteed to be called with a
58        /// keyword that `parser` has produced.
59        evaluator: fn(&computed::Context, Option<KeywordDiscriminant>) -> KleeneValue,
60    },
61}
62
63/// A simple helper macro to create a keyword evaluator.
64///
65/// This assumes that keyword feature expressions don't accept ranges, and
66/// asserts if that's not true. As of today there's nothing like that (does that
67/// even make sense?).
68macro_rules! keyword_evaluator {
69    ($actual_evaluator:ident, $keyword_type:ty) => {{
70        fn __parse<'i, 't>(
71            context: &$crate::parser::ParserContext,
72            input: &mut $crate::cssparser::Parser<'i, 't>,
73        ) -> Result<$crate::queries::feature::KeywordDiscriminant, ::style_traits::ParseError<'i>>
74        {
75            let kw = <$keyword_type as $crate::parser::Parse>::parse(context, input)?;
76            Ok(kw as $crate::queries::feature::KeywordDiscriminant)
77        }
78
79        fn __serialize(kw: $crate::queries::feature::KeywordDiscriminant) -> String {
80            // This unwrap is ok because the only discriminants that get
81            // back to us is the ones that `parse` produces.
82            let value: $keyword_type = ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap();
83            <$keyword_type as ::style_traits::ToCss>::to_css_string(&value)
84        }
85
86        fn __evaluate(
87            context: &$crate::values::computed::Context,
88            value: Option<$crate::queries::feature::KeywordDiscriminant>,
89        ) -> selectors::kleene_value::KleeneValue {
90            // This unwrap is ok because the only discriminants that get
91            // back to us is the ones that `parse` produces.
92            let value: Option<$keyword_type> =
93                value.map(|kw| ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap());
94            selectors::kleene_value::KleeneValue::from($actual_evaluator(context, value))
95        }
96
97        $crate::queries::feature::Evaluator::Enumerated {
98            parser: __parse,
99            serializer: __serialize,
100            evaluator: __evaluate,
101        }
102    }};
103}
104
105/// Different flags or toggles that change how a expression is parsed or
106/// evaluated.
107#[derive(Clone, Copy, Debug, ToShmem)]
108pub struct FeatureFlags(u8);
109bitflags! {
110    impl FeatureFlags : u8 {
111        /// The feature should only be parsed in chrome and ua sheets.
112        const CHROME_AND_UA_ONLY = 1 << 0;
113        /// The feature requires a -webkit- prefix.
114        const WEBKIT_PREFIX = 1 << 1;
115        /// The feature requires the inline-axis containment.
116        const CONTAINER_REQUIRES_INLINE_AXIS = 1 << 2;
117        /// The feature requires the block-axis containment.
118        const CONTAINER_REQUIRES_BLOCK_AXIS = 1 << 3;
119        /// The feature requires containment in the physical width axis.
120        const CONTAINER_REQUIRES_WIDTH_AXIS = 1 << 4;
121        /// The feature requires containment in the physical height axis.
122        const CONTAINER_REQUIRES_HEIGHT_AXIS = 1 << 5;
123        /// The feature evaluation depends on the viewport size.
124        const VIEWPORT_DEPENDENT = 1 << 6;
125        /// The feature evaluation depends on style queries.
126        const STYLE = 1 << 7;
127    }
128}
129
130impl FeatureFlags {
131    /// Returns parsing requirement flags.
132    pub fn parsing_requirements(self) -> Self {
133        self.intersection(Self::CHROME_AND_UA_ONLY | Self::WEBKIT_PREFIX)
134    }
135
136    /// Returns all the container axis flags.
137    pub fn all_container_axes() -> Self {
138        Self::CONTAINER_REQUIRES_INLINE_AXIS
139            | Self::CONTAINER_REQUIRES_BLOCK_AXIS
140            | Self::CONTAINER_REQUIRES_WIDTH_AXIS
141            | Self::CONTAINER_REQUIRES_HEIGHT_AXIS
142    }
143
144    /// Returns our subset of container axis flags.
145    pub fn container_axes(self) -> Self {
146        self.intersection(Self::all_container_axes())
147    }
148}
149
150/// Whether a feature allows ranges or not.
151#[derive(Clone, Copy, Debug, Eq, PartialEq)]
152#[allow(missing_docs)]
153pub enum AllowsRanges {
154    Yes,
155    No,
156}
157
158/// A description of a feature.
159pub struct QueryFeatureDescription {
160    /// The feature name, in ascii lowercase.
161    pub name: Atom,
162    /// Whether min- / max- prefixes are allowed or not.
163    pub allows_ranges: AllowsRanges,
164    /// The evaluator, which we also use to determine which kind of value to
165    /// parse.
166    pub evaluator: Evaluator,
167    /// Different feature-specific flags.
168    pub flags: FeatureFlags,
169}
170
171impl QueryFeatureDescription {
172    /// Whether this feature allows ranges.
173    #[inline]
174    pub fn allows_ranges(&self) -> bool {
175        self.allows_ranges == AllowsRanges::Yes
176    }
177}
178
179/// A simple helper to construct a `QueryFeatureDescription`.
180macro_rules! feature {
181    ($name:expr, $allows_ranges:expr, $evaluator:expr, $flags:expr,) => {
182        $crate::queries::feature::QueryFeatureDescription {
183            name: $name,
184            allows_ranges: $allows_ranges,
185            evaluator: $evaluator,
186            flags: $flags,
187        }
188    };
189}
190
191impl fmt::Debug for QueryFeatureDescription {
192    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
193        f.debug_struct("QueryFeatureDescription")
194            .field("name", &self.name)
195            .field("allows_ranges", &self.allows_ranges)
196            .field("flags", &self.flags)
197            .finish()
198    }
199}