tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }