1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/* 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 https://mozilla.org/MPL/2.0/. */

//! Query features.

use crate::parser::ParserContext;
use crate::values::computed::{self, CSSPixelLength, Ratio, Resolution};
use crate::values::AtomString;
use crate::Atom;
use cssparser::Parser;
use std::fmt;
use style_traits::ParseError;
use selectors::kleene_value::KleeneValue;

/// A generic discriminant for an enum value.
pub type KeywordDiscriminant = u8;

type QueryFeatureGetter<T> = fn(device: &computed::Context) -> T;

/// Serializes a given discriminant.
///
/// FIXME(emilio): we could prevent this allocation if the ToCss code would
/// generate a method for keywords to get the static string or something.
pub type KeywordSerializer = fn(KeywordDiscriminant) -> String;

/// Parses a given identifier.
pub type KeywordParser = for<'a, 'i, 't> fn(
    context: &'a ParserContext,
    input: &'a mut Parser<'i, 't>,
) -> Result<KeywordDiscriminant, ParseError<'i>>;

/// An evaluator for a given feature.
///
/// This determines the kind of values that get parsed, too.
#[allow(missing_docs)]
pub enum Evaluator {
    Length(QueryFeatureGetter<CSSPixelLength>),
    OptionalLength(QueryFeatureGetter<Option<CSSPixelLength>>),
    Integer(QueryFeatureGetter<i32>),
    Float(QueryFeatureGetter<f32>),
    BoolInteger(QueryFeatureGetter<bool>),
    /// A non-negative number ratio, such as the one from device-pixel-ratio.
    NumberRatio(QueryFeatureGetter<Ratio>),
    OptionalNumberRatio(QueryFeatureGetter<Option<Ratio>>),
    /// A resolution.
    Resolution(QueryFeatureGetter<Resolution>),
    String(fn(&computed::Context, Option<&AtomString>) -> KleeneValue),
    /// A keyword value.
    Enumerated {
        /// The parser to get a discriminant given a string.
        parser: KeywordParser,
        /// The serializer to get a string from a discriminant.
        ///
        /// This is guaranteed to be called with a keyword that `parser` has
        /// produced.
        serializer: KeywordSerializer,
        /// The evaluator itself. This is guaranteed to be called with a
        /// keyword that `parser` has produced.
        evaluator: fn(&computed::Context, Option<KeywordDiscriminant>) -> KleeneValue,
    },
}

/// A simple helper macro to create a keyword evaluator.
///
/// This assumes that keyword feature expressions don't accept ranges, and
/// asserts if that's not true. As of today there's nothing like that (does that
/// even make sense?).
macro_rules! keyword_evaluator {
    ($actual_evaluator:ident, $keyword_type:ty) => {{
        fn __parse<'i, 't>(
            context: &$crate::parser::ParserContext,
            input: &mut $crate::cssparser::Parser<'i, 't>,
        ) -> Result<$crate::queries::feature::KeywordDiscriminant, ::style_traits::ParseError<'i>>
        {
            let kw = <$keyword_type as $crate::parser::Parse>::parse(context, input)?;
            Ok(kw as $crate::queries::feature::KeywordDiscriminant)
        }

        fn __serialize(kw: $crate::queries::feature::KeywordDiscriminant) -> String {
            // This unwrap is ok because the only discriminants that get
            // back to us is the ones that `parse` produces.
            let value: $keyword_type = ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap();
            <$keyword_type as ::style_traits::ToCss>::to_css_string(&value)
        }

        fn __evaluate(
            context: &$crate::values::computed::Context,
            value: Option<$crate::queries::feature::KeywordDiscriminant>,
        ) -> selectors::kleene_value::KleeneValue {
            // This unwrap is ok because the only discriminants that get
            // back to us is the ones that `parse` produces.
            let value: Option<$keyword_type> =
                value.map(|kw| ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap());
                selectors::kleene_value::KleeneValue::from($actual_evaluator(context, value))
        }

        $crate::queries::feature::Evaluator::Enumerated {
            parser: __parse,
            serializer: __serialize,
            evaluator: __evaluate,
        }
    }};
}

/// Different flags or toggles that change how a expression is parsed or
/// evaluated.
#[derive(Clone, Copy, Debug, ToShmem)]
pub struct FeatureFlags(u8);
bitflags! {
    impl FeatureFlags : u8 {
        /// The feature should only be parsed in chrome and ua sheets.
        const CHROME_AND_UA_ONLY = 1 << 0;
        /// The feature requires a -webkit- prefix.
        const WEBKIT_PREFIX = 1 << 1;
        /// The feature requires the inline-axis containment.
        const CONTAINER_REQUIRES_INLINE_AXIS = 1 << 2;
        /// The feature requires the block-axis containment.
        const CONTAINER_REQUIRES_BLOCK_AXIS = 1 << 3;
        /// The feature requires containment in the physical width axis.
        const CONTAINER_REQUIRES_WIDTH_AXIS = 1 << 4;
        /// The feature requires containment in the physical height axis.
        const CONTAINER_REQUIRES_HEIGHT_AXIS = 1 << 5;
        /// The feature evaluation depends on the viewport size.
        const VIEWPORT_DEPENDENT = 1 << 6;
    }
}

impl FeatureFlags {
    /// Returns parsing requirement flags.
    pub fn parsing_requirements(self) -> Self {
        self.intersection(Self::CHROME_AND_UA_ONLY | Self::WEBKIT_PREFIX)
    }

    /// Returns all the container axis flags.
    pub fn all_container_axes() -> Self {
        Self::CONTAINER_REQUIRES_INLINE_AXIS |
            Self::CONTAINER_REQUIRES_BLOCK_AXIS |
            Self::CONTAINER_REQUIRES_WIDTH_AXIS |
            Self::CONTAINER_REQUIRES_HEIGHT_AXIS
    }

    /// Returns our subset of container axis flags.
    pub fn container_axes(self) -> Self {
        self.intersection(Self::all_container_axes())
    }
}

/// Whether a feature allows ranges or not.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum AllowsRanges {
    Yes,
    No,
}

/// A description of a feature.
pub struct QueryFeatureDescription {
    /// The feature name, in ascii lowercase.
    pub name: Atom,
    /// Whether min- / max- prefixes are allowed or not.
    pub allows_ranges: AllowsRanges,
    /// The evaluator, which we also use to determine which kind of value to
    /// parse.
    pub evaluator: Evaluator,
    /// Different feature-specific flags.
    pub flags: FeatureFlags,
}

impl QueryFeatureDescription {
    /// Whether this feature allows ranges.
    #[inline]
    pub fn allows_ranges(&self) -> bool {
        self.allows_ranges == AllowsRanges::Yes
    }
}

/// A simple helper to construct a `QueryFeatureDescription`.
macro_rules! feature {
    ($name:expr, $allows_ranges:expr, $evaluator:expr, $flags:expr,) => {
        $crate::queries::feature::QueryFeatureDescription {
            name: $name,
            allows_ranges: $allows_ranges,
            evaluator: $evaluator,
            flags: $flags,
        }
    };
}

impl fmt::Debug for QueryFeatureDescription {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("QueryFeatureDescription")
            .field("name", &self.name)
            .field("allows_ranges", &self.allows_ranges)
            .field("flags", &self.flags)
            .finish()
    }
}