Skip to main content

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