rule.rs (14995B)
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 9 use super::{ 10 registry::{PropertyRegistration, PropertyRegistrationData}, 11 syntax::Descriptor, 12 value::{ 13 AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue, 14 SpecifiedValue as SpecifiedRegisteredValue, 15 }, 16 }; 17 use crate::custom_properties::{Name as CustomPropertyName, SpecifiedValue}; 18 use crate::derives::*; 19 use crate::error_reporting::ContextualParseError; 20 use crate::parser::{Parse, ParserContext}; 21 use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; 22 use crate::values::{computed, serialize_atom_name}; 23 use cssparser::{ 24 match_ignore_ascii_case, AtRuleParser, BasicParseErrorKind, CowRcStr, DeclarationParser, 25 ParseErrorKind, Parser, ParserInput, ParserState, QualifiedRuleParser, RuleBodyItemParser, 26 RuleBodyParser, SourceLocation, 27 }; 28 #[cfg(feature = "gecko")] 29 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; 30 use selectors::parser::SelectorParseErrorKind; 31 use servo_arc::Arc; 32 use std::fmt::{self, Write}; 33 use style_traits::{ 34 CssStringWriter, CssWriter, ParseError, PropertyInheritsParseError, PropertySyntaxParseError, 35 StyleParseErrorKind, ToCss, 36 }; 37 use to_shmem::{SharedMemoryBuilder, ToShmem}; 38 39 /// Parse the block inside a `@property` rule. 40 /// 41 /// Valid `@property` rules result in a registered custom property, as if `registerProperty()` had 42 /// been called with equivalent parameters. 43 pub fn parse_property_block<'i, 't>( 44 context: &ParserContext, 45 input: &mut Parser<'i, 't>, 46 name: PropertyRuleName, 47 source_location: SourceLocation, 48 ) -> Result<PropertyRegistration, ParseError<'i>> { 49 let mut descriptors = PropertyDescriptors::default(); 50 let mut parser = PropertyRuleParser { 51 context, 52 descriptors: &mut descriptors, 53 }; 54 let mut iter = RuleBodyParser::new(input, &mut parser); 55 let mut syntax_err = None; 56 let mut inherits_err = None; 57 while let Some(declaration) = iter.next() { 58 if !context.error_reporting_enabled() { 59 continue; 60 } 61 if let Err((error, slice)) = declaration { 62 let location = error.location; 63 let error = match error.kind { 64 // If the provided string is not a valid syntax string (if it 65 // returns failure when consume a syntax definition is called on 66 // it), the descriptor is invalid and must be ignored. 67 ParseErrorKind::Custom(StyleParseErrorKind::PropertySyntaxField(_)) => { 68 syntax_err = Some(error.clone()); 69 ContextualParseError::UnsupportedValue(slice, error) 70 }, 71 72 // If the provided string is not a valid inherits string, 73 // the descriptor is invalid and must be ignored. 74 ParseErrorKind::Custom(StyleParseErrorKind::PropertyInheritsField(_)) => { 75 inherits_err = Some(error.clone()); 76 ContextualParseError::UnsupportedValue(slice, error) 77 }, 78 79 // Unknown descriptors are invalid and ignored, but do not 80 // invalidate the @property rule. 81 _ => ContextualParseError::UnsupportedPropertyDescriptor(slice, error), 82 }; 83 context.log_css_error(location, error); 84 } 85 } 86 87 // https://drafts.css-houdini.org/css-properties-values-api-1/#the-syntax-descriptor: 88 // 89 // The syntax descriptor is required for the @property rule to be valid; if it’s 90 // missing, the @property rule is invalid. 91 let Some(syntax) = descriptors.syntax else { 92 return Err(if let Some(err) = syntax_err { 93 err 94 } else { 95 let err = input.new_custom_error(StyleParseErrorKind::PropertySyntaxField( 96 PropertySyntaxParseError::NoSyntax, 97 )); 98 context.log_css_error( 99 source_location, 100 ContextualParseError::UnsupportedValue("", err.clone()), 101 ); 102 err 103 }); 104 }; 105 106 // https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor: 107 // 108 // The inherits descriptor is required for the @property rule to be valid; if it’s 109 // missing, the @property rule is invalid. 110 let Some(inherits) = descriptors.inherits else { 111 return Err(if let Some(err) = inherits_err { 112 err 113 } else { 114 let err = input.new_custom_error(StyleParseErrorKind::PropertyInheritsField( 115 PropertyInheritsParseError::NoInherits, 116 )); 117 context.log_css_error( 118 source_location, 119 ContextualParseError::UnsupportedValue("", err.clone()), 120 ); 121 err 122 }); 123 }; 124 125 if PropertyRegistration::validate_initial_value(&syntax, descriptors.initial_value.as_deref()) 126 .is_err() 127 { 128 return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)); 129 } 130 131 Ok(PropertyRegistration { 132 name, 133 data: PropertyRegistrationData { 134 syntax, 135 inherits, 136 initial_value: descriptors.initial_value, 137 }, 138 url_data: context.url_data.clone(), 139 source_location, 140 }) 141 } 142 143 struct PropertyRuleParser<'a, 'b: 'a> { 144 context: &'a ParserContext<'b>, 145 descriptors: &'a mut PropertyDescriptors, 146 } 147 148 /// Default methods reject all at rules. 149 impl<'a, 'b, 'i> AtRuleParser<'i> for PropertyRuleParser<'a, 'b> { 150 type Prelude = (); 151 type AtRule = (); 152 type Error = StyleParseErrorKind<'i>; 153 } 154 155 impl<'a, 'b, 'i> QualifiedRuleParser<'i> for PropertyRuleParser<'a, 'b> { 156 type Prelude = (); 157 type QualifiedRule = (); 158 type Error = StyleParseErrorKind<'i>; 159 } 160 161 impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> 162 for PropertyRuleParser<'a, 'b> 163 { 164 fn parse_qualified(&self) -> bool { 165 false 166 } 167 fn parse_declarations(&self) -> bool { 168 true 169 } 170 } 171 172 macro_rules! property_descriptors { 173 ( 174 $( #[$doc: meta] $name: tt $ident: ident: $ty: ty, )* 175 ) => { 176 /// Data inside a `@property` rule. 177 /// 178 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule> 179 #[derive(Clone, Debug, Default, PartialEq)] 180 struct PropertyDescriptors { 181 $( 182 #[$doc] 183 $ident: Option<$ty>, 184 )* 185 } 186 187 impl PropertyRegistration { 188 fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result { 189 $( 190 let $ident = Option::<&$ty>::from(&self.data.$ident); 191 if let Some(ref value) = $ident { 192 dest.write_str(concat!($name, ": "))?; 193 value.to_css(&mut CssWriter::new(dest))?; 194 dest.write_str("; ")?; 195 } 196 )* 197 Ok(()) 198 } 199 } 200 201 impl<'a, 'b, 'i> DeclarationParser<'i> for PropertyRuleParser<'a, 'b> { 202 type Declaration = (); 203 type Error = StyleParseErrorKind<'i>; 204 205 fn parse_value<'t>( 206 &mut self, 207 name: CowRcStr<'i>, 208 input: &mut Parser<'i, 't>, 209 _declaration_start: &ParserState, 210 ) -> Result<(), ParseError<'i>> { 211 match_ignore_ascii_case! { &*name, 212 $( 213 $name => { 214 // DeclarationParser also calls parse_entirely so we’d normally not need 215 // to, but in this case we do because we set the value as a side effect 216 // rather than returning it. 217 let value = input.parse_entirely(|i| Parse::parse(self.context, i))?; 218 self.descriptors.$ident = Some(value) 219 }, 220 )* 221 _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), 222 } 223 Ok(()) 224 } 225 } 226 } 227 } 228 229 property_descriptors! { 230 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#the-syntax-descriptor> 231 "syntax" syntax: Descriptor, 232 233 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor> 234 "inherits" inherits: Inherits, 235 236 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#initial-value-descriptor> 237 "initial-value" initial_value: InitialValue, 238 } 239 240 /// Errors that can happen when registering a property. 241 #[allow(missing_docs)] 242 pub enum PropertyRegistrationError { 243 NoInitialValue, 244 InvalidInitialValue, 245 InitialValueNotComputationallyIndependent, 246 } 247 248 impl PropertyRegistration { 249 /// Measure heap usage. 250 #[cfg(feature = "gecko")] 251 pub fn size_of(&self, _: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { 252 MallocSizeOf::size_of(self, ops) 253 } 254 255 /// Computes the value of the computationally independent initial value. 256 pub fn compute_initial_value( 257 &self, 258 computed_context: &computed::Context, 259 ) -> Result<ComputedRegisteredValue, ()> { 260 let Some(ref initial) = self.data.initial_value else { 261 return Err(()); 262 }; 263 264 if self.data.syntax.is_universal() { 265 return Ok(ComputedRegisteredValue::universal(Arc::clone(initial))); 266 } 267 268 let mut input = ParserInput::new(initial.css_text()); 269 let mut input = Parser::new(&mut input); 270 input.skip_whitespace(); 271 272 match SpecifiedRegisteredValue::compute( 273 &mut input, 274 &self.data, 275 &self.url_data, 276 computed_context, 277 AllowComputationallyDependent::No, 278 ) { 279 Ok(computed) => Ok(computed), 280 Err(_) => Err(()), 281 } 282 } 283 284 /// Performs syntax validation as per the initial value descriptor. 285 /// https://drafts.css-houdini.org/css-properties-values-api-1/#initial-value-descriptor 286 pub fn validate_initial_value( 287 syntax: &Descriptor, 288 initial_value: Option<&SpecifiedValue>, 289 ) -> Result<(), PropertyRegistrationError> { 290 use crate::properties::CSSWideKeyword; 291 // If the value of the syntax descriptor is the universal syntax definition, then the 292 // initial-value descriptor is optional. If omitted, the initial value of the property is 293 // the guaranteed-invalid value. 294 if syntax.is_universal() && initial_value.is_none() { 295 return Ok(()); 296 } 297 298 // Otherwise, if the value of the syntax descriptor is not the universal syntax definition, 299 // the following conditions must be met for the @property rule to be valid: 300 301 // The initial-value descriptor must be present. 302 let Some(initial) = initial_value else { 303 return Err(PropertyRegistrationError::NoInitialValue); 304 }; 305 306 // A value that references the environment or other variables is not computationally 307 // independent. 308 if initial.has_references() { 309 return Err(PropertyRegistrationError::InitialValueNotComputationallyIndependent); 310 } 311 312 let mut input = ParserInput::new(initial.css_text()); 313 let mut input = Parser::new(&mut input); 314 input.skip_whitespace(); 315 316 // The initial-value cannot include CSS-wide keywords. 317 if input.try_parse(CSSWideKeyword::parse).is_ok() { 318 return Err(PropertyRegistrationError::InitialValueNotComputationallyIndependent); 319 } 320 321 match SpecifiedRegisteredValue::parse( 322 &mut input, 323 syntax, 324 &initial.url_data, 325 AllowComputationallyDependent::No, 326 ) { 327 Ok(_) => {}, 328 Err(_) => return Err(PropertyRegistrationError::InvalidInitialValue), 329 } 330 331 Ok(()) 332 } 333 } 334 335 impl ToCssWithGuard for PropertyRegistration { 336 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#serialize-a-csspropertyrule> 337 fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { 338 dest.write_str("@property ")?; 339 self.name.to_css(&mut CssWriter::new(dest))?; 340 dest.write_str(" { ")?; 341 self.decl_to_css(dest)?; 342 dest.write_char('}') 343 } 344 } 345 346 impl ToShmem for PropertyRegistration { 347 fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> { 348 Err(String::from( 349 "ToShmem failed for PropertyRule: cannot handle @property rules", 350 )) 351 } 352 } 353 354 /// A custom property name wrapper that includes the `--` prefix in its serialization 355 #[derive(Clone, Debug, PartialEq, MallocSizeOf)] 356 pub struct PropertyRuleName(pub CustomPropertyName); 357 358 impl ToCss for PropertyRuleName { 359 fn to_css<W: Write>(&self, dest: &mut CssWriter<W>) -> fmt::Result { 360 dest.write_str("--")?; 361 serialize_atom_name(&self.0, dest) 362 } 363 } 364 365 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor> 366 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)] 367 pub enum Inherits { 368 /// `true` value for the `inherits` descriptor 369 True, 370 /// `false` value for the `inherits` descriptor 371 False, 372 } 373 374 impl Parse for Inherits { 375 fn parse<'i, 't>( 376 _context: &ParserContext, 377 input: &mut Parser<'i, 't>, 378 ) -> Result<Self, ParseError<'i>> { 379 // FIXME(bug 1927012): Remove `return` from try_match_ident_ignore_ascii_case so the closure 380 // can be removed. 381 let result: Result<Inherits, ParseError> = (|| { 382 try_match_ident_ignore_ascii_case! { input, 383 "true" => Ok(Inherits::True), 384 "false" => Ok(Inherits::False), 385 } 386 })(); 387 if let Err(err) = result { 388 Err(ParseError { 389 kind: ParseErrorKind::Custom(StyleParseErrorKind::PropertyInheritsField( 390 PropertyInheritsParseError::InvalidInherits, 391 )), 392 location: err.location, 393 }) 394 } else { 395 result 396 } 397 } 398 } 399 400 /// Specifies the initial value of the custom property registration represented by the @property 401 /// rule, controlling the property’s initial value. 402 /// 403 /// The SpecifiedValue is wrapped in an Arc to avoid copying when using it. 404 pub type InitialValue = Arc<SpecifiedValue>; 405 406 impl Parse for InitialValue { 407 fn parse<'i, 't>( 408 context: &ParserContext, 409 input: &mut Parser<'i, 't>, 410 ) -> Result<Self, ParseError<'i>> { 411 input.skip_whitespace(); 412 Ok(Arc::new(SpecifiedValue::parse(input, &context.url_data)?)) 413 } 414 }