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.
7 //! https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule
10 registry::{PropertyRegistration, PropertyRegistrationData},
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};
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;
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.
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 {
44 descriptors: &mut descriptors,
46 let mut iter = RuleBodyParser::new(input, &mut parser);
47 while let Some(declaration) = iter.next() {
48 if !context.error_reporting_enabled() {
51 if let Err((error, slice)) = declaration {
52 let location = error.location;
53 let error = if matches!(
55 ParseErrorKind::Custom(StyleParseErrorKind::PropertySyntaxField(_))
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)
62 // Unknown descriptors are invalid and ignored, but do not
63 // invalidate the @property rule.
64 ContextualParseError::UnsupportedPropertyDescriptor(slice, error)
66 context.log_css_error(location, error);
70 // https://drafts.css-houdini.org/css-properties-values-api-1/#the-syntax-descriptor:
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));
78 // https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor:
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));
86 if PropertyRegistration::validate_initial_value(&syntax, descriptors.initial_value.as_deref())
89 return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid));
92 Ok(PropertyRegistration {
94 data: PropertyRegistrationData {
97 initial_value: descriptors.initial_value,
99 url_data: context.url_data.clone(),
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> {
113 type Error = StyleParseErrorKind<'i>;
116 impl<'a, 'b, 'i> QualifiedRuleParser<'i> for PropertyRuleParser<'a, 'b> {
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 {
128 fn parse_declarations(&self) -> bool {
133 macro_rules! property_descriptors {
135 $( #[$doc: meta] $name: tt $ident: ident: $ty: ty, )*
137 /// Data inside a `@property` rule.
139 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule>
140 #[derive(Clone, Debug, Default, PartialEq)]
141 struct PropertyDescriptors {
148 impl PropertyRegistration {
149 fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
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("; ")?;
162 impl<'a, 'b, 'i> DeclarationParser<'i> for PropertyRuleParser<'a, 'b> {
163 type Declaration = ();
164 type Error = StyleParseErrorKind<'i>;
169 input: &mut Parser<'i, 't>,
170 ) -> Result<(), ParseError<'i>> {
171 match_ignore_ascii_case! { &*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)
181 _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
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 {
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)
215 /// Computes the value of the computationally independent initial value.
216 pub fn compute_initial_value(
218 computed_context: &computed::Context,
219 ) -> Result<InitialValue, ()> {
220 let Some(ref initial) = self.data.initial_value else {
224 if self.data.syntax.is_universal() {
225 return Ok(Arc::clone(initial));
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(
237 AllowComputationallyDependent::No,
239 Ok(computed) => Ok(Arc::new(computed)),
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(
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() {
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);
266 // A value that references the environment or other variables is not computationally
268 if initial.has_references() {
269 return Err(PropertyRegistrationError::InitialValueNotComputationallyIndependent);
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);
281 match SpecifiedRegisteredValue::parse(
285 AllowComputationallyDependent::No,
288 Err(_) => return Err(PropertyRegistrationError::InvalidInitialValue),
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)?;
306 impl ToShmem for PropertyRegistration {
307 fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
309 "ToShmem failed for PropertyRule: cannot handle @property rules",
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)
325 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor>
326 #[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, ToCss)]
328 /// `true` value for the `inherits` descriptor
330 /// `false` value for the `inherits` descriptor
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 {
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)?))