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