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