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::{
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            /* attr_taint */ Default::default(),
176        ) {
177            Ok(computed) => Ok(computed),
178            Err(_) => Err(()),
179        }
180    }
181
182    /// Performs syntax validation as per the initial value descriptor.
183    /// https://drafts.css-houdini.org/css-properties-values-api-1/#initial-value-descriptor
184    pub fn validate_initial_value(
185        syntax: &SyntaxDescriptor,
186        initial_value: Option<&SpecifiedValue>,
187    ) -> Result<(), PropertyRegistrationError> {
188        use crate::properties::CSSWideKeyword;
189        // If the value of the syntax descriptor is the universal syntax definition, then the
190        // initial-value descriptor is optional. If omitted, the initial value of the property is
191        // the guaranteed-invalid value.
192        if syntax.is_universal() && initial_value.is_none() {
193            return Ok(());
194        }
195
196        // Otherwise, if the value of the syntax descriptor is not the universal syntax definition,
197        // the following conditions must be met for the @property rule to be valid:
198
199        // The initial-value descriptor must be present.
200        let Some(initial) = initial_value else {
201            return Err(PropertyRegistrationError::NoInitialValue);
202        };
203
204        // A value that references the environment or other variables is not computationally
205        // independent.
206        if initial.has_references() {
207            return Err(PropertyRegistrationError::InitialValueNotComputationallyIndependent);
208        }
209
210        let mut input = ParserInput::new(initial.css_text());
211        let mut input = Parser::new(&mut input);
212        input.skip_whitespace();
213
214        // The initial-value cannot include CSS-wide keywords.
215        if input.try_parse(CSSWideKeyword::parse).is_ok() {
216            return Err(PropertyRegistrationError::InitialValueNotComputationallyIndependent);
217        }
218
219        match SpecifiedRegisteredValue::parse(
220            &mut input,
221            syntax,
222            &initial.url_data,
223            None,
224            AllowComputationallyDependent::No,
225            /* attr_taint */ Default::default(),
226        ) {
227            Ok(_) => {},
228            Err(_) => return Err(PropertyRegistrationError::InvalidInitialValue),
229        }
230
231        Ok(())
232    }
233}
234
235impl ToCssWithGuard for PropertyRegistration {
236    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#serialize-a-csspropertyrule>
237    fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
238        dest.write_str("@property ")?;
239        self.name.to_css(&mut CssWriter::new(dest))?;
240        dest.write_str(" { ")?;
241        self.descriptors.to_css(&mut CssWriter::new(dest))?;
242        dest.write_char('}')
243    }
244}
245
246impl ToShmem for PropertyRegistration {
247    fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
248        Err(String::from(
249            "ToShmem failed for PropertyRule: cannot handle @property rules",
250        ))
251    }
252}
253
254/// A custom property name wrapper that includes the `--` prefix in its serialization
255#[derive(Clone, Debug, PartialEq, MallocSizeOf)]
256pub struct PropertyRuleName(pub CustomPropertyName);
257
258impl ToCss for PropertyRuleName {
259    fn to_css<W: Write>(&self, dest: &mut CssWriter<W>) -> fmt::Result {
260        dest.write_str("--")?;
261        serialize_atom_name(&self.0, dest)
262    }
263}
264
265/// <https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor>
266#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
267pub enum Inherits {
268    /// `true` value for the `inherits` descriptor
269    True,
270    /// `false` value for the `inherits` descriptor
271    False,
272}
273
274impl Parse for Inherits {
275    fn parse<'i, 't>(
276        _context: &ParserContext,
277        input: &mut Parser<'i, 't>,
278    ) -> Result<Self, ParseError<'i>> {
279        // FIXME(bug 1927012): Remove `return` from try_match_ident_ignore_ascii_case so the closure
280        // can be removed.
281        let result: Result<Inherits, ParseError> = (|| {
282            try_match_ident_ignore_ascii_case! { input,
283                "true" => Ok(Inherits::True),
284                "false" => Ok(Inherits::False),
285            }
286        })();
287        if let Err(err) = result {
288            Err(ParseError {
289                kind: ParseErrorKind::Custom(StyleParseErrorKind::PropertyInheritsField(
290                    PropertyInheritsParseError::InvalidInherits,
291                )),
292                location: err.location,
293            })
294        } else {
295            result
296        }
297    }
298}
299
300/// Specifies the initial value of the custom property registration represented by the @property
301/// rule, controlling the property’s initial value.
302///
303/// The SpecifiedValue is wrapped in an Arc to avoid copying when using it.
304pub type InitialValue = Arc<SpecifiedValue>;
305
306impl Parse for InitialValue {
307    fn parse<'i, 't>(
308        context: &ParserContext,
309        input: &mut Parser<'i, 't>,
310    ) -> Result<Self, ParseError<'i>> {
311        input.skip_whitespace();
312        Ok(Arc::new(SpecifiedValue::parse(
313            input,
314            Some(&context.namespaces.prefixes),
315            &context.url_data,
316        )?))
317    }
318}
319
320impl Descriptors {
321    /// Returns descriptors for an unregistered property.
322    #[inline]
323    pub fn unregistered() -> &'static Self {
324        static UNREGISTERED: Descriptors = Descriptors {
325            inherits: Some(Inherits::True),
326            syntax: Some(SyntaxDescriptor::universal()),
327            initial_value: None,
328        };
329        &UNREGISTERED
330    }
331
332    /// Whether this property inherits.
333    pub fn inherits(&self) -> bool {
334        self.inherits != Some(Inherits::False)
335    }
336
337    /// Whether this property uses universal syntax.
338    pub fn is_universal(&self) -> bool {
339        self.syntax.as_ref().map_or(true, |s| s.is_universal())
340    }
341}