Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
//! The [`@property`] at-rule.
//!
use super::{
registry::{PropertyRegistration, PropertyRegistrationData},
syntax::Descriptor,
value::{
AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
SpecifiedValue as SpecifiedRegisteredValue,
},
};
use crate::custom_properties::{Name as CustomPropertyName, SpecifiedValue};
use crate::error_reporting::ContextualParseError;
use crate::parser::{Parse, ParserContext};
use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
use crate::str::CssStringWriter;
use crate::values::{computed, serialize_atom_name};
use cssparser::{
AtRuleParser, BasicParseErrorKind, CowRcStr, DeclarationParser, ParseErrorKind, Parser,
ParserInput, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation,
};
#[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use selectors::parser::SelectorParseErrorKind;
use servo_arc::Arc;
use std::fmt::{self, Write};
use style_traits::{
CssWriter, ParseError, PropertyInheritsParseError, PropertySyntaxParseError,
StyleParseErrorKind, ToCss,
};
use to_shmem::{SharedMemoryBuilder, ToShmem};
/// Parse the block inside a `@property` rule.
///
/// Valid `@property` rules result in a registered custom property, as if `registerProperty()` had
/// been called with equivalent parameters.
pub fn parse_property_block<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
name: PropertyRuleName,
source_location: SourceLocation,
) -> Result<PropertyRegistration, ParseError<'i>> {
let mut descriptors = PropertyDescriptors::default();
let mut parser = PropertyRuleParser {
context,
descriptors: &mut descriptors,
};
let mut iter = RuleBodyParser::new(input, &mut parser);
let mut syntax_err = None;
let mut inherits_err = None;
while let Some(declaration) = iter.next() {
if !context.error_reporting_enabled() {
continue;
}
if let Err((error, slice)) = declaration {
let location = error.location;
let error = match error.kind {
// If the provided string is not a valid syntax string (if it
// returns failure when consume a syntax definition is called on
// it), the descriptor is invalid and must be ignored.
ParseErrorKind::Custom(StyleParseErrorKind::PropertySyntaxField(_)) => {
syntax_err = Some(error.clone());
ContextualParseError::UnsupportedValue(slice, error)
},
// If the provided string is not a valid inherits string,
// the descriptor is invalid and must be ignored.
ParseErrorKind::Custom(StyleParseErrorKind::PropertyInheritsField(_)) => {
inherits_err = Some(error.clone());
ContextualParseError::UnsupportedValue(slice, error)
},
// Unknown descriptors are invalid and ignored, but do not
// invalidate the @property rule.
_ => ContextualParseError::UnsupportedPropertyDescriptor(slice, error),
};
context.log_css_error(location, error);
}
}
//
// The syntax descriptor is required for the @property rule to be valid; if it’s
// missing, the @property rule is invalid.
let Some(syntax) = descriptors.syntax else {
return Err(if let Some(err) = syntax_err {
err
} else {
let err = input.new_custom_error(StyleParseErrorKind::PropertySyntaxField(
PropertySyntaxParseError::NoSyntax,
));
context.log_css_error(
source_location,
ContextualParseError::UnsupportedValue("", err.clone()),
);
err
});
};
//
// The inherits descriptor is required for the @property rule to be valid; if it’s
// missing, the @property rule is invalid.
let Some(inherits) = descriptors.inherits else {
return Err(if let Some(err) = inherits_err {
err
} else {
let err = input.new_custom_error(StyleParseErrorKind::PropertyInheritsField(
PropertyInheritsParseError::NoInherits,
));
context.log_css_error(
source_location,
ContextualParseError::UnsupportedValue("", err.clone()),
);
err
});
};
if PropertyRegistration::validate_initial_value(&syntax, descriptors.initial_value.as_deref())
.is_err()
{
return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid));
}
Ok(PropertyRegistration {
name,
data: PropertyRegistrationData {
syntax,
inherits,
initial_value: descriptors.initial_value,
},
url_data: context.url_data.clone(),
source_location,
})
}
struct PropertyRuleParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>,
descriptors: &'a mut PropertyDescriptors,
}
/// Default methods reject all at rules.
impl<'a, 'b, 'i> AtRuleParser<'i> for PropertyRuleParser<'a, 'b> {
type Prelude = ();
type AtRule = ();
type Error = StyleParseErrorKind<'i>;
}
impl<'a, 'b, 'i> QualifiedRuleParser<'i> for PropertyRuleParser<'a, 'b> {
type Prelude = ();
type QualifiedRule = ();
type Error = StyleParseErrorKind<'i>;
}
impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
for PropertyRuleParser<'a, 'b>
{
fn parse_qualified(&self) -> bool {
false
}
fn parse_declarations(&self) -> bool {
true
}
}
macro_rules! property_descriptors {
(
$( #[$doc: meta] $name: tt $ident: ident: $ty: ty, )*
) => {
/// Data inside a `@property` rule.
///
#[derive(Clone, Debug, Default, PartialEq)]
struct PropertyDescriptors {
$(
#[$doc]
$ident: Option<$ty>,
)*
}
impl PropertyRegistration {
fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
$(
let $ident = Option::<&$ty>::from(&self.data.$ident);
if let Some(ref value) = $ident {
dest.write_str(concat!($name, ": "))?;
value.to_css(&mut CssWriter::new(dest))?;
dest.write_str("; ")?;
}
)*
Ok(())
}
}
impl<'a, 'b, 'i> DeclarationParser<'i> for PropertyRuleParser<'a, 'b> {
type Declaration = ();
type Error = StyleParseErrorKind<'i>;
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<(), ParseError<'i>> {
match_ignore_ascii_case! { &*name,
$(
$name => {
// DeclarationParser also calls parse_entirely so we’d normally not need
// to, but in this case we do because we set the value as a side effect
// rather than returning it.
let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
self.descriptors.$ident = Some(value)
},
)*
_ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
}
Ok(())
}
}
}
}
property_descriptors! {
"syntax" syntax: Descriptor,
"inherits" inherits: Inherits,
"initial-value" initial_value: InitialValue,
}
/// Errors that can happen when registering a property.
#[allow(missing_docs)]
pub enum PropertyRegistrationError {
NoInitialValue,
InvalidInitialValue,
InitialValueNotComputationallyIndependent,
}
impl PropertyRegistration {
/// Measure heap usage.
#[cfg(feature = "gecko")]
pub fn size_of(&self, _: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
MallocSizeOf::size_of(self, ops)
}
/// Computes the value of the computationally independent initial value.
pub fn compute_initial_value(
&self,
computed_context: &computed::Context,
) -> Result<ComputedRegisteredValue, ()> {
let Some(ref initial) = self.data.initial_value else {
return Err(());
};
if self.data.syntax.is_universal() {
return Ok(ComputedRegisteredValue::universal(Arc::clone(initial)));
}
let mut input = ParserInput::new(initial.css_text());
let mut input = Parser::new(&mut input);
input.skip_whitespace();
match SpecifiedRegisteredValue::compute(
&mut input,
&self.data,
&self.url_data,
computed_context,
AllowComputationallyDependent::No,
) {
Ok(computed) => Ok(computed),
Err(_) => Err(()),
}
}
/// Performs syntax validation as per the initial value descriptor.
pub fn validate_initial_value(
syntax: &Descriptor,
initial_value: Option<&SpecifiedValue>,
) -> Result<(), PropertyRegistrationError> {
use crate::properties::CSSWideKeyword;
// If the value of the syntax descriptor is the universal syntax definition, then the
// initial-value descriptor is optional. If omitted, the initial value of the property is
// the guaranteed-invalid value.
if syntax.is_universal() && initial_value.is_none() {
return Ok(());
}
// Otherwise, if the value of the syntax descriptor is not the universal syntax definition,
// the following conditions must be met for the @property rule to be valid:
// The initial-value descriptor must be present.
let Some(initial) = initial_value else {
return Err(PropertyRegistrationError::NoInitialValue);
};
// A value that references the environment or other variables is not computationally
// independent.
if initial.has_references() {
return Err(PropertyRegistrationError::InitialValueNotComputationallyIndependent);
}
let mut input = ParserInput::new(initial.css_text());
let mut input = Parser::new(&mut input);
input.skip_whitespace();
// The initial-value cannot include CSS-wide keywords.
if input.try_parse(CSSWideKeyword::parse).is_ok() {
return Err(PropertyRegistrationError::InitialValueNotComputationallyIndependent);
}
match SpecifiedRegisteredValue::parse(
&mut input,
syntax,
&initial.url_data,
AllowComputationallyDependent::No,
) {
Ok(_) => {},
Err(_) => return Err(PropertyRegistrationError::InvalidInitialValue),
}
Ok(())
}
}
impl ToCssWithGuard for PropertyRegistration {
fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
dest.write_str("@property ")?;
self.name.to_css(&mut CssWriter::new(dest))?;
dest.write_str(" { ")?;
self.decl_to_css(dest)?;
dest.write_char('}')
}
}
impl ToShmem for PropertyRegistration {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
Err(String::from(
"ToShmem failed for PropertyRule: cannot handle @property rules",
))
}
}
/// A custom property name wrapper that includes the `--` prefix in its serialization
#[derive(Clone, Debug, PartialEq, MallocSizeOf)]
pub struct PropertyRuleName(pub CustomPropertyName);
impl ToCss for PropertyRuleName {
fn to_css<W: Write>(&self, dest: &mut CssWriter<W>) -> fmt::Result {
dest.write_str("--")?;
serialize_atom_name(&self.0, dest)
}
}
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
pub enum Inherits {
/// `true` value for the `inherits` descriptor
True,
/// `false` value for the `inherits` descriptor
False,
}
impl Parse for Inherits {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
// can be removed.
let result: Result<Inherits, ParseError> = (|| {
try_match_ident_ignore_ascii_case! { input,
"true" => Ok(Inherits::True),
"false" => Ok(Inherits::False),
}
})();
if let Err(err) = result {
Err(ParseError {
kind: ParseErrorKind::Custom(StyleParseErrorKind::PropertyInheritsField(
PropertyInheritsParseError::InvalidInherits,
)),
location: err.location,
})
} else {
result
}
}
}
/// Specifies the initial value of the custom property registration represented by the @property
/// rule, controlling the property’s initial value.
///
/// The SpecifiedValue is wrapped in an Arc to avoid copying when using it.
pub type InitialValue = Arc<SpecifiedValue>;
impl Parse for InitialValue {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
input.skip_whitespace();
Ok(Arc::new(SpecifiedValue::parse(input, &context.url_data)?))
}
}