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, StyleParseErrorKind, ToCss};
use to_shmem::{SharedMemoryBuilder, ToShmem};
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);
while let Some(declaration) = iter.next() {
if !context.error_reporting_enabled() {
continue;
}
if let Err((error, slice)) = declaration {
let location = error.location;
let error = if matches!(
error.kind,
ParseErrorKind::Custom(StyleParseErrorKind::PropertySyntaxField(_))
) {
ContextualParseError::UnsupportedValue(slice, error)
} else {
ContextualParseError::UnsupportedPropertyDescriptor(slice, error)
};
context.log_css_error(location, error);
}
}
let Some(syntax) = descriptors.syntax else {
return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid));
};
let Some(inherits) = descriptors.inherits else {
return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid));
};
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,
}
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, )*
) => {
#[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 => {
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,
}
#[allow(missing_docs)]
pub enum PropertyRegistrationError {
NoInitialValue,
InvalidInitialValue,
InitialValueNotComputationallyIndependent,
}
impl PropertyRegistration {
#[cfg(feature = "gecko")]
pub fn size_of(&self, _: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
MallocSizeOf::size_of(self, ops)
}
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(()),
}
}
pub fn validate_initial_value(
syntax: &Descriptor,
initial_value: Option<&SpecifiedValue>,
) -> Result<(), PropertyRegistrationError> {
use crate::properties::CSSWideKeyword;
if syntax.is_universal() && initial_value.is_none() {
return Ok(());
}
let Some(initial) = initial_value else {
return Err(PropertyRegistrationError::NoInitialValue);
};
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();
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",
))
}
}
#[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, Parse, PartialEq, ToCss)]
pub enum Inherits {
True,
False,
}
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)?))
}
}