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