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