stylo/properties_and_values/
rule.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//! The [`@property`] at-rule.
6//!
7//! https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule
8
9use super::{
10    registry::{PropertyRegistration, PropertyRegistrationData},
11    syntax::Descriptor,
12    value::{
13        AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
14        SpecifiedValue as SpecifiedRegisteredValue,
15    },
16};
17use crate::custom_properties::{Name as CustomPropertyName, SpecifiedValue};
18use crate::error_reporting::ContextualParseError;
19use crate::parser::{Parse, ParserContext};
20use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
21use crate::str::CssStringWriter;
22use crate::values::{computed, serialize_atom_name};
23use cssparser::{
24    AtRuleParser, BasicParseErrorKind, CowRcStr, DeclarationParser, ParseErrorKind, Parser,
25    ParserInput, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation,
26};
27#[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
28use selectors::parser::SelectorParseErrorKind;
29use servo_arc::Arc;
30use std::fmt::{self, Write};
31use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
32use to_shmem::{SharedMemoryBuilder, ToShmem};
33
34/// Parse the block inside a `@property` rule.
35///
36/// Valid `@property` rules result in a registered custom property, as if `registerProperty()` had
37/// been called with equivalent parameters.
38pub fn parse_property_block<'i, 't>(
39    context: &ParserContext,
40    input: &mut Parser<'i, 't>,
41    name: PropertyRuleName,
42    source_location: SourceLocation,
43) -> Result<PropertyRegistration, ParseError<'i>> {
44    let mut descriptors = PropertyDescriptors::default();
45    let mut parser = PropertyRuleParser {
46        context,
47        descriptors: &mut descriptors,
48    };
49    let mut iter = RuleBodyParser::new(input, &mut parser);
50    while let Some(declaration) = iter.next() {
51        if !context.error_reporting_enabled() {
52            continue;
53        }
54        if let Err((error, slice)) = declaration {
55            let location = error.location;
56            let error = if matches!(
57                error.kind,
58                ParseErrorKind::Custom(StyleParseErrorKind::PropertySyntaxField(_))
59            ) {
60                // If the provided string is not a valid syntax string (if it
61                // returns failure when consume a syntax definition is called on
62                // it), the descriptor is invalid and must be ignored.
63                ContextualParseError::UnsupportedValue(slice, error)
64            } else {
65                // Unknown descriptors are invalid and ignored, but do not
66                // invalidate the @property rule.
67                ContextualParseError::UnsupportedPropertyDescriptor(slice, error)
68            };
69            context.log_css_error(location, error);
70        }
71    }
72
73    // https://drafts.css-houdini.org/css-properties-values-api-1/#the-syntax-descriptor:
74    //
75    //     The syntax descriptor is required for the @property rule to be valid; if it’s
76    //     missing, the @property rule is invalid.
77    let Some(syntax) = descriptors.syntax else {
78        return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid));
79    };
80
81    // https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor:
82    //
83    //     The inherits descriptor is required for the @property rule to be valid; if it’s
84    //     missing, the @property rule is invalid.
85    let Some(inherits) = descriptors.inherits else {
86        return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid));
87    };
88
89    if PropertyRegistration::validate_initial_value(&syntax, descriptors.initial_value.as_deref())
90        .is_err()
91    {
92        return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid));
93    }
94
95    Ok(PropertyRegistration {
96        name,
97        data: PropertyRegistrationData {
98            syntax,
99            inherits,
100            initial_value: descriptors.initial_value,
101        },
102        url_data: context.url_data.clone(),
103        source_location,
104    })
105}
106
107struct PropertyRuleParser<'a, 'b: 'a> {
108    context: &'a ParserContext<'b>,
109    descriptors: &'a mut PropertyDescriptors,
110}
111
112/// Default methods reject all at rules.
113impl<'a, 'b, 'i> AtRuleParser<'i> for PropertyRuleParser<'a, 'b> {
114    type Prelude = ();
115    type AtRule = ();
116    type Error = StyleParseErrorKind<'i>;
117}
118
119impl<'a, 'b, 'i> QualifiedRuleParser<'i> for PropertyRuleParser<'a, 'b> {
120    type Prelude = ();
121    type QualifiedRule = ();
122    type Error = StyleParseErrorKind<'i>;
123}
124
125impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
126    for PropertyRuleParser<'a, 'b>
127{
128    fn parse_qualified(&self) -> bool {
129        false
130    }
131    fn parse_declarations(&self) -> bool {
132        true
133    }
134}
135
136macro_rules! property_descriptors {
137    (
138        $( #[$doc: meta] $name: tt $ident: ident: $ty: ty, )*
139    ) => {
140        /// Data inside a `@property` rule.
141        ///
142        /// <https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule>
143        #[derive(Clone, Debug, Default, PartialEq)]
144        struct PropertyDescriptors {
145            $(
146                #[$doc]
147                $ident: Option<$ty>,
148            )*
149        }
150
151        impl PropertyRegistration {
152            fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
153                $(
154                    let $ident = Option::<&$ty>::from(&self.data.$ident);
155                    if let Some(ref value) = $ident {
156                        dest.write_str(concat!($name, ": "))?;
157                        value.to_css(&mut CssWriter::new(dest))?;
158                        dest.write_str("; ")?;
159                    }
160                )*
161                Ok(())
162            }
163        }
164
165        impl<'a, 'b, 'i> DeclarationParser<'i> for PropertyRuleParser<'a, 'b> {
166            type Declaration = ();
167            type Error = StyleParseErrorKind<'i>;
168
169            fn parse_value<'t>(
170                &mut self,
171                name: CowRcStr<'i>,
172                input: &mut Parser<'i, 't>,
173            ) -> Result<(), ParseError<'i>> {
174                match_ignore_ascii_case! { &*name,
175                    $(
176                        $name => {
177                            // DeclarationParser also calls parse_entirely so we’d normally not need
178                            // to, but in this case we do because we set the value as a side effect
179                            // rather than returning it.
180                            let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
181                            self.descriptors.$ident = Some(value)
182                        },
183                    )*
184                    _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
185                }
186                Ok(())
187            }
188        }
189    }
190}
191
192property_descriptors! {
193    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#the-syntax-descriptor>
194    "syntax" syntax: Descriptor,
195
196    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor>
197    "inherits" inherits: Inherits,
198
199    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#initial-value-descriptor>
200    "initial-value" initial_value: InitialValue,
201}
202
203/// Errors that can happen when registering a property.
204#[allow(missing_docs)]
205pub enum PropertyRegistrationError {
206    NoInitialValue,
207    InvalidInitialValue,
208    InitialValueNotComputationallyIndependent,
209}
210
211impl PropertyRegistration {
212    /// Measure heap usage.
213    #[cfg(feature = "gecko")]
214    pub fn size_of(&self, _: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
215        MallocSizeOf::size_of(self, ops)
216    }
217
218    /// Computes the value of the computationally independent initial value.
219    pub fn compute_initial_value(
220        &self,
221        computed_context: &computed::Context,
222    ) -> Result<ComputedRegisteredValue, ()> {
223        let Some(ref initial) = self.data.initial_value else {
224            return Err(());
225        };
226
227        if self.data.syntax.is_universal() {
228            return Ok(ComputedRegisteredValue::universal(Arc::clone(initial)));
229        }
230
231        let mut input = ParserInput::new(initial.css_text());
232        let mut input = Parser::new(&mut input);
233        input.skip_whitespace();
234
235        match SpecifiedRegisteredValue::compute(
236            &mut input,
237            &self.data,
238            &self.url_data,
239            computed_context,
240            AllowComputationallyDependent::No,
241        ) {
242            Ok(computed) => Ok(computed),
243            Err(_) => Err(()),
244        }
245    }
246
247    /// Performs syntax validation as per the initial value descriptor.
248    /// https://drafts.css-houdini.org/css-properties-values-api-1/#initial-value-descriptor
249    pub fn validate_initial_value(
250        syntax: &Descriptor,
251        initial_value: Option<&SpecifiedValue>,
252    ) -> Result<(), PropertyRegistrationError> {
253        use crate::properties::CSSWideKeyword;
254        // If the value of the syntax descriptor is the universal syntax definition, then the
255        // initial-value descriptor is optional. If omitted, the initial value of the property is
256        // the guaranteed-invalid value.
257        if syntax.is_universal() && initial_value.is_none() {
258            return Ok(());
259        }
260
261        // Otherwise, if the value of the syntax descriptor is not the universal syntax definition,
262        // the following conditions must be met for the @property rule to be valid:
263
264        // The initial-value descriptor must be present.
265        let Some(initial) = initial_value else {
266            return Err(PropertyRegistrationError::NoInitialValue);
267        };
268
269        // A value that references the environment or other variables is not computationally
270        // independent.
271        if initial.has_references() {
272            return Err(PropertyRegistrationError::InitialValueNotComputationallyIndependent);
273        }
274
275        let mut input = ParserInput::new(initial.css_text());
276        let mut input = Parser::new(&mut input);
277        input.skip_whitespace();
278
279        // The initial-value cannot include CSS-wide keywords.
280        if input.try_parse(CSSWideKeyword::parse).is_ok() {
281            return Err(PropertyRegistrationError::InitialValueNotComputationallyIndependent);
282        }
283
284        match SpecifiedRegisteredValue::parse(
285            &mut input,
286            syntax,
287            &initial.url_data,
288            AllowComputationallyDependent::No,
289        ) {
290            Ok(_) => {},
291            Err(_) => return Err(PropertyRegistrationError::InvalidInitialValue),
292        }
293
294        Ok(())
295    }
296}
297
298impl ToCssWithGuard for PropertyRegistration {
299    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#serialize-a-csspropertyrule>
300    fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
301        dest.write_str("@property ")?;
302        self.name.to_css(&mut CssWriter::new(dest))?;
303        dest.write_str(" { ")?;
304        self.decl_to_css(dest)?;
305        dest.write_char('}')
306    }
307}
308
309impl ToShmem for PropertyRegistration {
310    fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
311        Err(String::from(
312            "ToShmem failed for PropertyRule: cannot handle @property rules",
313        ))
314    }
315}
316
317/// A custom property name wrapper that includes the `--` prefix in its serialization
318#[derive(Clone, Debug, PartialEq, MallocSizeOf)]
319pub struct PropertyRuleName(pub CustomPropertyName);
320
321impl ToCss for PropertyRuleName {
322    fn to_css<W: Write>(&self, dest: &mut CssWriter<W>) -> fmt::Result {
323        dest.write_str("--")?;
324        serialize_atom_name(&self.0, dest)
325    }
326}
327
328/// <https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor>
329#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, ToCss)]
330pub enum Inherits {
331    /// `true` value for the `inherits` descriptor
332    True,
333    /// `false` value for the `inherits` descriptor
334    False,
335}
336
337/// Specifies the initial value of the custom property registration represented by the @property
338/// rule, controlling the property’s initial value.
339///
340/// The SpecifiedValue is wrapped in an Arc to avoid copying when using it.
341pub type InitialValue = Arc<SpecifiedValue>;
342
343impl Parse for InitialValue {
344    fn parse<'i, 't>(
345        context: &ParserContext,
346        input: &mut Parser<'i, 't>,
347    ) -> Result<Self, ParseError<'i>> {
348        input.skip_whitespace();
349        Ok(Arc::new(SpecifiedValue::parse(input, &context.url_data)?))
350    }
351}