tor-browser

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

mod.rs (14496B)


      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 //! Used for parsing and serializing the [`@property`] syntax string.
      6 //!
      7 //! <https://drafts.css-houdini.org/css-properties-values-api-1/#parsing-syntax>
      8 
      9 use std::fmt::{self, Debug};
     10 use std::{borrow::Cow, fmt::Write};
     11 
     12 use crate::derives::*;
     13 use crate::parser::{Parse, ParserContext};
     14 use crate::values::CustomIdent;
     15 use cssparser::{Parser as CSSParser, ParserInput as CSSParserInput, Token};
     16 use style_traits::{
     17    CssWriter, ParseError as StyleParseError, PropertySyntaxParseError as ParseError,
     18    StyleParseErrorKind, ToCss,
     19 };
     20 
     21 use self::data_type::{DataType, DependentDataTypes};
     22 
     23 mod ascii;
     24 pub mod data_type;
     25 
     26 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#parsing-syntax>
     27 #[derive(Debug, Clone, Default, MallocSizeOf, PartialEq, ToShmem)]
     28 pub struct Descriptor {
     29    /// The parsed components, if any.
     30    /// TODO: Could be a Box<[]> if that supported const construction.
     31    pub components: Vec<Component>,
     32    /// The specified css syntax, if any.
     33    specified: Option<Box<str>>,
     34 }
     35 
     36 impl Descriptor {
     37    /// Returns the universal descriptor.
     38    pub const fn universal() -> Self {
     39        Self {
     40            components: Vec::new(),
     41            specified: None,
     42        }
     43    }
     44 
     45    /// Returns whether this is the universal syntax descriptor.
     46    #[inline]
     47    pub fn is_universal(&self) -> bool {
     48        self.components.is_empty()
     49    }
     50 
     51    /// Returns the specified string, if any.
     52    #[inline]
     53    pub fn specified_string(&self) -> Option<&str> {
     54        self.specified.as_deref()
     55    }
     56 
     57    /// Parse a syntax descriptor from a stream of tokens
     58    /// https://drafts.csswg.org/css-values-5/#typedef-syntax
     59    #[inline]
     60    pub fn from_css_parser<'i>(input: &mut CSSParser<'i, '_>) -> Result<Self, StyleParseError<'i>> {
     61        let mut components = vec![];
     62 
     63        if input.try_parse(|i| i.expect_delim('*')).is_ok() {
     64            return Ok(Self::universal());
     65        }
     66 
     67        // Parse <syntax-string> if given.
     68        if let Ok(syntax_string) = input.try_parse(|i| i.expect_string_cloned()) {
     69            return Self::from_str(syntax_string.as_ref(), /* save_specified = */ true).or_else(
     70                |err| Err(input.new_custom_error(StyleParseErrorKind::PropertySyntaxField(err))),
     71            );
     72        }
     73 
     74        loop {
     75            let name = Self::try_parse_component_name(input).map_err(|err| {
     76                input.new_custom_error(StyleParseErrorKind::PropertySyntaxField(err))
     77            })?;
     78 
     79            let multiplier = if name.is_pre_multiplied() {
     80                None
     81            } else {
     82                Self::try_parse_multiplier(input)
     83            };
     84 
     85            let component = Component { multiplier, name };
     86            components.push(component);
     87            let Ok(delim) = input.next() else { break };
     88 
     89            if delim != &Token::Delim('|') {
     90                return Err(
     91                    input.new_custom_error(StyleParseErrorKind::PropertySyntaxField(
     92                        ParseError::ExpectedPipeBetweenComponents,
     93                    )),
     94                );
     95            }
     96        }
     97 
     98        Ok(Self {
     99            components,
    100            specified: None,
    101        })
    102    }
    103 
    104    fn try_parse_multiplier<'i>(input: &mut CSSParser<'i, '_>) -> Option<Multiplier> {
    105        input
    106            .try_parse(|input| {
    107                let next = input.next().map_err(|_| ())?;
    108                match next {
    109                    Token::Delim('+') => Ok(Multiplier::Space),
    110                    Token::Delim('#') => Ok(Multiplier::Comma),
    111                    _ => Err(()),
    112                }
    113            })
    114            .ok()
    115    }
    116 
    117    fn try_parse_component_name<'i>(
    118        input: &mut CSSParser<'i, '_>,
    119    ) -> Result<ComponentName, ParseError> {
    120        if input.try_parse(|input| input.expect_delim('<')).is_ok() {
    121            let name = Self::parse_component_data_type_name(input)?;
    122            input
    123                .expect_delim('>')
    124                .map_err(|_| ParseError::UnclosedDataTypeName)?;
    125            Ok(ComponentName::DataType(name))
    126        } else {
    127            input.try_parse(|input| {
    128                let name = CustomIdent::parse(input, &[]).map_err(|_| ParseError::InvalidName)?;
    129                Ok(ComponentName::Ident(name))
    130            })
    131        }
    132    }
    133 
    134    fn parse_component_data_type_name<'i>(
    135        input: &mut CSSParser<'i, '_>,
    136    ) -> Result<DataType, ParseError> {
    137        input
    138            .expect_ident()
    139            .ok()
    140            .and_then(|n| DataType::from_str(n))
    141            .ok_or(ParseError::UnknownDataTypeName)
    142    }
    143 
    144    /// Parse a syntax descriptor.
    145    /// https://drafts.css-houdini.org/css-properties-values-api-1/#consume-a-syntax-definition
    146    pub fn from_str(css: &str, save_specified: bool) -> Result<Self, ParseError> {
    147        // 1. Strip leading and trailing ASCII whitespace from string.
    148        let input = ascii::trim_ascii_whitespace(css);
    149 
    150        // 2. If string's length is 0, return failure.
    151        if input.is_empty() {
    152            return Err(ParseError::EmptyInput);
    153        }
    154 
    155        let specified = if save_specified {
    156            Some(Box::from(css))
    157        } else {
    158            None
    159        };
    160 
    161        // 3. If string's length is 1, and the only code point in string is U+002A
    162        //    ASTERISK (*), return the universal syntax descriptor.
    163        if input.len() == 1 && input.as_bytes()[0] == b'*' {
    164            return Ok(Self {
    165                components: Default::default(),
    166                specified,
    167            });
    168        }
    169 
    170        // 4. Let stream be an input stream created from the code points of string,
    171        //    preprocessed as specified in [css-syntax-3]. Let descriptor be an
    172        //    initially empty list of syntax components.
    173        //
    174        // NOTE(emilio): Instead of preprocessing we cheat and treat new-lines and
    175        // nulls in the parser specially.
    176        let mut components = vec![];
    177        {
    178            let mut input = Parser::new(input, &mut components);
    179            // 5. Repeatedly consume the next input code point from stream.
    180            input.parse()?;
    181        }
    182        Ok(Self {
    183            components,
    184            specified,
    185        })
    186    }
    187 
    188    /// Returns the dependent types this syntax might contain.
    189    pub fn dependent_types(&self) -> DependentDataTypes {
    190        let mut types = DependentDataTypes::empty();
    191        for component in self.components.iter() {
    192            let t = match &component.name {
    193                ComponentName::DataType(ref t) => t,
    194                ComponentName::Ident(_) => continue,
    195            };
    196            types.insert(t.dependent_types());
    197        }
    198        types
    199    }
    200 }
    201 
    202 impl ToCss for Descriptor {
    203    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    204    where
    205        W: Write,
    206    {
    207        if let Some(ref specified) = self.specified {
    208            return specified.to_css(dest);
    209        }
    210 
    211        if self.is_universal() {
    212            return dest.write_char('*');
    213        }
    214 
    215        let mut first = true;
    216        for component in &*self.components {
    217            if !first {
    218                dest.write_str(" | ")?;
    219            }
    220            component.to_css(dest)?;
    221            first = false;
    222        }
    223 
    224        Ok(())
    225    }
    226 }
    227 
    228 impl Parse for Descriptor {
    229    /// Parse a syntax descriptor.
    230    fn parse<'i>(
    231        _: &ParserContext,
    232        parser: &mut CSSParser<'i, '_>,
    233    ) -> Result<Self, StyleParseError<'i>> {
    234        let input = parser.expect_string()?;
    235        Descriptor::from_str(input.as_ref(), /* save_specified = */ true)
    236            .map_err(|err| parser.new_custom_error(StyleParseErrorKind::PropertySyntaxField(err)))
    237    }
    238 }
    239 
    240 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#multipliers>
    241 #[derive(
    242    Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
    243 )]
    244 pub enum Multiplier {
    245    /// Indicates a space-separated list.
    246    Space,
    247    /// Indicates a comma-separated list.
    248    Comma,
    249 }
    250 
    251 impl ToCss for Multiplier {
    252    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    253    where
    254        W: Write,
    255    {
    256        dest.write_char(match *self {
    257            Multiplier::Space => '+',
    258            Multiplier::Comma => '#',
    259        })
    260    }
    261 }
    262 
    263 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-component>
    264 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
    265 pub struct Component {
    266    name: ComponentName,
    267    multiplier: Option<Multiplier>,
    268 }
    269 
    270 impl Component {
    271    /// Returns the component's name.
    272    #[inline]
    273    pub fn name(&self) -> &ComponentName {
    274        &self.name
    275    }
    276 
    277    /// Returns the component's multiplier, if one exists.
    278    #[inline]
    279    pub fn multiplier(&self) -> Option<Multiplier> {
    280        self.multiplier
    281    }
    282 
    283    /// If the component is premultiplied, return the un-premultiplied component.
    284    #[inline]
    285    pub fn unpremultiplied(&self) -> Cow<'_, Self> {
    286        match self.name.unpremultiply() {
    287            Some(component) => {
    288                debug_assert!(
    289                    self.multiplier.is_none(),
    290                    "Shouldn't have parsed a multiplier for a pre-multiplied data type name",
    291                );
    292                Cow::Owned(component)
    293            },
    294            None => Cow::Borrowed(self),
    295        }
    296    }
    297 }
    298 
    299 impl ToCss for Component {
    300    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    301    where
    302        W: Write,
    303    {
    304        self.name().to_css(dest)?;
    305        self.multiplier().to_css(dest)
    306    }
    307 }
    308 
    309 /// <https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-component-name>
    310 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
    311 pub enum ComponentName {
    312    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#data-type-name>
    313    DataType(DataType),
    314    /// <https://drafts.csswg.org/css-values-4/#custom-idents>
    315    Ident(CustomIdent),
    316 }
    317 
    318 impl ComponentName {
    319    fn unpremultiply(&self) -> Option<Component> {
    320        match *self {
    321            ComponentName::DataType(ref t) => t.unpremultiply(),
    322            ComponentName::Ident(..) => None,
    323        }
    324    }
    325 
    326    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#pre-multiplied-data-type-name>
    327    fn is_pre_multiplied(&self) -> bool {
    328        self.unpremultiply().is_some()
    329    }
    330 }
    331 
    332 struct Parser<'a> {
    333    input: &'a str,
    334    position: usize,
    335    output: &'a mut Vec<Component>,
    336 }
    337 
    338 /// <https://drafts.csswg.org/css-syntax-3/#letter>
    339 fn is_letter(byte: u8) -> bool {
    340    match byte {
    341        b'A'..=b'Z' | b'a'..=b'z' => true,
    342        _ => false,
    343    }
    344 }
    345 
    346 /// <https://drafts.csswg.org/css-syntax-3/#non-ascii-code-point>
    347 fn is_non_ascii(byte: u8) -> bool {
    348    byte >= 0x80
    349 }
    350 
    351 /// <https://drafts.csswg.org/css-syntax-3/#name-start-code-point>
    352 fn is_name_start(byte: u8) -> bool {
    353    is_letter(byte) || is_non_ascii(byte) || byte == b'_'
    354 }
    355 
    356 impl<'a> Parser<'a> {
    357    fn new(input: &'a str, output: &'a mut Vec<Component>) -> Self {
    358        Self {
    359            input,
    360            position: 0,
    361            output,
    362        }
    363    }
    364 
    365    fn peek(&self) -> Option<u8> {
    366        self.input.as_bytes().get(self.position).cloned()
    367    }
    368 
    369    fn parse(&mut self) -> Result<(), ParseError> {
    370        // 5. Repeatedly consume the next input code point from stream:
    371        loop {
    372            let component = self.parse_component()?;
    373            self.output.push(component);
    374            self.skip_whitespace();
    375 
    376            let byte = match self.peek() {
    377                None => return Ok(()),
    378                Some(b) => b,
    379            };
    380 
    381            if byte != b'|' {
    382                return Err(ParseError::ExpectedPipeBetweenComponents);
    383            }
    384 
    385            self.position += 1;
    386        }
    387    }
    388 
    389    fn skip_whitespace(&mut self) {
    390        loop {
    391            match self.peek() {
    392                Some(c) if c.is_ascii_whitespace() => self.position += 1,
    393                _ => return,
    394            }
    395        }
    396    }
    397 
    398    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-data-type-name>
    399    fn parse_data_type_name(&mut self) -> Result<DataType, ParseError> {
    400        let start = self.position;
    401        loop {
    402            let byte = match self.peek() {
    403                Some(b) => b,
    404                None => return Err(ParseError::UnclosedDataTypeName),
    405            };
    406            if byte != b'>' {
    407                self.position += 1;
    408                continue;
    409            }
    410            let ty = match DataType::from_str(&self.input[start..self.position]) {
    411                Some(ty) => ty,
    412                None => return Err(ParseError::UnknownDataTypeName),
    413            };
    414            self.position += 1;
    415            return Ok(ty);
    416        }
    417    }
    418 
    419    fn parse_name(&mut self) -> Result<ComponentName, ParseError> {
    420        let b = match self.peek() {
    421            Some(b) => b,
    422            None => return Err(ParseError::UnexpectedEOF),
    423        };
    424 
    425        if b == b'<' {
    426            self.position += 1;
    427            return Ok(ComponentName::DataType(self.parse_data_type_name()?));
    428        }
    429 
    430        if b != b'\\' && !is_name_start(b) {
    431            return Err(ParseError::InvalidNameStart);
    432        }
    433 
    434        let input = &self.input[self.position..];
    435        let mut input = CSSParserInput::new(input);
    436        let mut input = CSSParser::new(&mut input);
    437        let name = match CustomIdent::parse(&mut input, &[]) {
    438            Ok(name) => name,
    439            Err(_) => return Err(ParseError::InvalidName),
    440        };
    441        self.position += input.position().byte_index();
    442        return Ok(ComponentName::Ident(name));
    443    }
    444 
    445    fn parse_multiplier(&mut self) -> Option<Multiplier> {
    446        let multiplier = match self.peek()? {
    447            b'+' => Multiplier::Space,
    448            b'#' => Multiplier::Comma,
    449            _ => return None,
    450        };
    451        self.position += 1;
    452        Some(multiplier)
    453    }
    454 
    455    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-a-syntax-component>
    456    fn parse_component(&mut self) -> Result<Component, ParseError> {
    457        // Consume as much whitespace as possible from stream.
    458        self.skip_whitespace();
    459        let name = self.parse_name()?;
    460        let multiplier = if name.is_pre_multiplied() {
    461            None
    462        } else {
    463            self.parse_multiplier()
    464        };
    465        Ok(Component { name, multiplier })
    466    }
    467 }