tor-browser

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

mod.rs (28597B)


      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 [`@counter-style`][counter-style] at-rule.
      6 //!
      7 //! [counter-style]: https://drafts.csswg.org/css-counter-styles/
      8 
      9 use crate::derives::*;
     10 use crate::error_reporting::ContextualParseError;
     11 use crate::parser::{Parse, ParserContext};
     12 use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
     13 use crate::values::specified::Integer;
     14 use crate::values::{AtomString, CustomIdent};
     15 use crate::Atom;
     16 use cssparser::{
     17    ascii_case_insensitive_phf_map, match_ignore_ascii_case, CowRcStr, Parser, ParserState,
     18    SourceLocation, Token,
     19 };
     20 use cssparser::{
     21    AtRuleParser, DeclarationParser, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser,
     22 };
     23 use selectors::parser::SelectorParseErrorKind;
     24 use std::fmt::{self, Write};
     25 use std::mem;
     26 use std::num::Wrapping;
     27 use style_traits::{
     28    Comma, CssStringWriter, CssWriter, KeywordsCollectFn, OneOrMoreSeparated, ParseError,
     29    SpecifiedValueInfo, StyleParseErrorKind, ToCss,
     30 };
     31 
     32 /// https://drafts.csswg.org/css-counter-styles/#typedef-symbols-type
     33 #[allow(missing_docs)]
     34 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
     35 #[derive(
     36    Clone,
     37    Copy,
     38    Debug,
     39    Eq,
     40    MallocSizeOf,
     41    Parse,
     42    PartialEq,
     43    ToComputedValue,
     44    ToCss,
     45    ToResolvedValue,
     46    ToShmem,
     47 )]
     48 #[repr(u8)]
     49 pub enum SymbolsType {
     50    Cyclic,
     51    Numeric,
     52    Alphabetic,
     53    Symbolic,
     54    Fixed,
     55 }
     56 
     57 /// <https://drafts.csswg.org/css-counter-styles/#typedef-counter-style>
     58 ///
     59 /// Note that 'none' is not a valid name, but we include this (along with String) for space
     60 /// efficiency when storing list-style-type.
     61 #[derive(
     62    Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
     63 )]
     64 #[repr(u8)]
     65 pub enum CounterStyle {
     66    /// The 'none' value.
     67    None,
     68    /// `<counter-style-name>`
     69    Name(CustomIdent),
     70    /// `symbols()`
     71    #[css(function)]
     72    Symbols {
     73        /// The <symbols-type>, or symbolic if not specified.
     74        #[css(skip_if = "is_symbolic")]
     75        ty: SymbolsType,
     76        /// The actual symbols.
     77        symbols: Symbols,
     78    },
     79    /// A single string value, useful for `<list-style-type>`.
     80    String(AtomString),
     81 }
     82 
     83 #[inline]
     84 fn is_symbolic(symbols_type: &SymbolsType) -> bool {
     85    *symbols_type == SymbolsType::Symbolic
     86 }
     87 
     88 impl CounterStyle {
     89    /// disc value
     90    pub fn disc() -> Self {
     91        CounterStyle::Name(CustomIdent(atom!("disc")))
     92    }
     93 
     94    /// decimal value
     95    pub fn decimal() -> Self {
     96        CounterStyle::Name(CustomIdent(atom!("decimal")))
     97    }
     98 
     99    /// Is this a bullet? (i.e. `list-style-type: disc|circle|square|disclosure-closed|disclosure-open`)
    100    #[inline]
    101    pub fn is_bullet(&self) -> bool {
    102        match self {
    103            CounterStyle::Name(CustomIdent(ref name)) => {
    104                name == &atom!("disc")
    105                    || name == &atom!("circle")
    106                    || name == &atom!("square")
    107                    || name == &atom!("disclosure-closed")
    108                    || name == &atom!("disclosure-open")
    109            },
    110            _ => false,
    111        }
    112    }
    113 }
    114 
    115 bitflags! {
    116    #[derive(Clone, Copy)]
    117    /// Flags to control parsing of counter styles.
    118    pub struct CounterStyleParsingFlags: u8 {
    119        /// Whether `none` is allowed.
    120        const ALLOW_NONE = 1 << 0;
    121        /// Whether a bare string is allowed.
    122        const ALLOW_STRING = 1 << 1;
    123    }
    124 }
    125 
    126 impl CounterStyle {
    127    /// Parse a counter style, and optionally none|string (for list-style-type).
    128    pub fn parse<'i, 't>(
    129        context: &ParserContext,
    130        input: &mut Parser<'i, 't>,
    131        flags: CounterStyleParsingFlags,
    132    ) -> Result<Self, ParseError<'i>> {
    133        use self::CounterStyleParsingFlags as Flags;
    134        let location = input.current_source_location();
    135        match input.next()? {
    136            Token::QuotedString(ref string) if flags.intersects(Flags::ALLOW_STRING) => {
    137                Ok(Self::String(AtomString::from(string.as_ref())))
    138            },
    139            Token::Ident(ref ident) => {
    140                if flags.intersects(Flags::ALLOW_NONE) && ident.eq_ignore_ascii_case("none") {
    141                    return Ok(Self::None);
    142                }
    143                Ok(Self::Name(counter_style_name_from_ident(ident, location)?))
    144            },
    145            Token::Function(ref name) if name.eq_ignore_ascii_case("symbols") => {
    146                input.parse_nested_block(|input| {
    147                    let symbols_type = input
    148                        .try_parse(SymbolsType::parse)
    149                        .unwrap_or(SymbolsType::Symbolic);
    150                    let symbols = Symbols::parse(context, input)?;
    151                    // There must be at least two symbols for alphabetic or
    152                    // numeric system.
    153                    if (symbols_type == SymbolsType::Alphabetic
    154                        || symbols_type == SymbolsType::Numeric)
    155                        && symbols.0.len() < 2
    156                    {
    157                        return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    158                    }
    159                    // Identifier is not allowed in symbols() function.
    160                    if symbols.0.iter().any(|sym| !sym.is_allowed_in_symbols()) {
    161                        return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    162                    }
    163                    Ok(Self::Symbols {
    164                        ty: symbols_type,
    165                        symbols,
    166                    })
    167                })
    168            },
    169            t => Err(location.new_unexpected_token_error(t.clone())),
    170        }
    171    }
    172 }
    173 
    174 impl SpecifiedValueInfo for CounterStyle {
    175    fn collect_completion_keywords(f: KeywordsCollectFn) {
    176        // XXX The best approach for implementing this is probably
    177        // having a CounterStyleName type wrapping CustomIdent, and
    178        // put the predefined list for that type in counter_style mod.
    179        // But that's a non-trivial change itself, so we use a simpler
    180        // approach here.
    181        macro_rules! predefined {
    182            ($($name:expr,)+) => {
    183                f(&["symbols", "none", $($name,)+])
    184            }
    185        }
    186        include!("predefined.rs");
    187    }
    188 }
    189 
    190 fn parse_counter_style_name<'i>(input: &mut Parser<'i, '_>) -> Result<CustomIdent, ParseError<'i>> {
    191    let location = input.current_source_location();
    192    let ident = input.expect_ident()?;
    193    counter_style_name_from_ident(ident, location)
    194 }
    195 
    196 /// This allows the reserved counter style names "decimal" and "disc".
    197 fn counter_style_name_from_ident<'i>(
    198    ident: &CowRcStr<'i>,
    199    location: SourceLocation,
    200 ) -> Result<CustomIdent, ParseError<'i>> {
    201    macro_rules! predefined {
    202        ($($name: tt,)+) => {{
    203            ascii_case_insensitive_phf_map! {
    204                predefined -> Atom = {
    205                    $(
    206                        $name => atom!($name),
    207                    )+
    208                }
    209            }
    210 
    211            // This effectively performs case normalization only on predefined names.
    212            if let Some(lower_case) = predefined::get(&ident) {
    213                Ok(CustomIdent(lower_case.clone()))
    214            } else {
    215                // none is always an invalid <counter-style> value.
    216                CustomIdent::from_ident(location, ident, &["none"])
    217            }
    218        }}
    219    }
    220    include!("predefined.rs")
    221 }
    222 
    223 fn is_valid_name_definition(ident: &CustomIdent) -> bool {
    224    ident.0 != atom!("decimal")
    225        && ident.0 != atom!("disc")
    226        && ident.0 != atom!("circle")
    227        && ident.0 != atom!("square")
    228        && ident.0 != atom!("disclosure-closed")
    229        && ident.0 != atom!("disclosure-open")
    230 }
    231 
    232 /// Parse the prelude of an @counter-style rule
    233 pub fn parse_counter_style_name_definition<'i, 't>(
    234    input: &mut Parser<'i, 't>,
    235 ) -> Result<CustomIdent, ParseError<'i>> {
    236    parse_counter_style_name(input).and_then(|ident| {
    237        if !is_valid_name_definition(&ident) {
    238            Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
    239        } else {
    240            Ok(ident)
    241        }
    242    })
    243 }
    244 
    245 /// Parse the body (inside `{}`) of an @counter-style rule
    246 pub fn parse_counter_style_body<'i, 't>(
    247    name: CustomIdent,
    248    context: &ParserContext,
    249    input: &mut Parser<'i, 't>,
    250    location: SourceLocation,
    251 ) -> Result<CounterStyleRuleData, ParseError<'i>> {
    252    let start = input.current_source_location();
    253    let mut rule = CounterStyleRuleData::empty(name, location);
    254    {
    255        let mut parser = CounterStyleRuleParser {
    256            context,
    257            rule: &mut rule,
    258        };
    259        let mut iter = RuleBodyParser::new(input, &mut parser);
    260        while let Some(declaration) = iter.next() {
    261            if let Err((error, slice)) = declaration {
    262                let location = error.location;
    263                let error = ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(
    264                    slice, error,
    265                );
    266                context.log_css_error(location, error)
    267            }
    268        }
    269    }
    270    let error = match *rule.resolved_system() {
    271        ref system @ System::Cyclic
    272        | ref system @ System::Fixed { .. }
    273        | ref system @ System::Symbolic
    274        | ref system @ System::Alphabetic
    275        | ref system @ System::Numeric
    276            if rule.symbols.is_none() =>
    277        {
    278            let system = system.to_css_string();
    279            Some(ContextualParseError::InvalidCounterStyleWithoutSymbols(
    280                system,
    281            ))
    282        },
    283        ref system @ System::Alphabetic | ref system @ System::Numeric
    284            if rule.symbols().unwrap().0.len() < 2 =>
    285        {
    286            let system = system.to_css_string();
    287            Some(ContextualParseError::InvalidCounterStyleNotEnoughSymbols(
    288                system,
    289            ))
    290        },
    291        System::Additive if rule.additive_symbols.is_none() => {
    292            Some(ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols)
    293        },
    294        System::Extends(_) if rule.symbols.is_some() => {
    295            Some(ContextualParseError::InvalidCounterStyleExtendsWithSymbols)
    296        },
    297        System::Extends(_) if rule.additive_symbols.is_some() => {
    298            Some(ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols)
    299        },
    300        _ => None,
    301    };
    302    if let Some(error) = error {
    303        context.log_css_error(start, error);
    304        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
    305    } else {
    306        Ok(rule)
    307    }
    308 }
    309 
    310 struct CounterStyleRuleParser<'a, 'b: 'a> {
    311    context: &'a ParserContext<'b>,
    312    rule: &'a mut CounterStyleRuleData,
    313 }
    314 
    315 /// Default methods reject all at rules.
    316 impl<'a, 'b, 'i> AtRuleParser<'i> for CounterStyleRuleParser<'a, 'b> {
    317    type Prelude = ();
    318    type AtRule = ();
    319    type Error = StyleParseErrorKind<'i>;
    320 }
    321 
    322 impl<'a, 'b, 'i> QualifiedRuleParser<'i> for CounterStyleRuleParser<'a, 'b> {
    323    type Prelude = ();
    324    type QualifiedRule = ();
    325    type Error = StyleParseErrorKind<'i>;
    326 }
    327 
    328 impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
    329    for CounterStyleRuleParser<'a, 'b>
    330 {
    331    fn parse_qualified(&self) -> bool {
    332        false
    333    }
    334    fn parse_declarations(&self) -> bool {
    335        true
    336    }
    337 }
    338 
    339 macro_rules! checker {
    340    ($self:ident._($value:ident)) => {};
    341    ($self:ident. $checker:ident($value:ident)) => {
    342        if !$self.$checker(&$value) {
    343            return false;
    344        }
    345    };
    346 }
    347 
    348 macro_rules! counter_style_descriptors {
    349    (
    350        $( #[$doc: meta] $name: tt $ident: ident / $setter: ident [$checker: tt]: $ty: ty, )+
    351    ) => {
    352        /// An @counter-style rule
    353        #[derive(Clone, Debug, ToShmem)]
    354        pub struct CounterStyleRuleData {
    355            name: CustomIdent,
    356            generation: Wrapping<u32>,
    357            $(
    358                #[$doc]
    359                $ident: Option<$ty>,
    360            )+
    361            /// Line and column of the @counter-style rule source code.
    362            pub source_location: SourceLocation,
    363        }
    364 
    365        impl CounterStyleRuleData {
    366            fn empty(name: CustomIdent, source_location: SourceLocation) -> Self {
    367                CounterStyleRuleData {
    368                    name: name,
    369                    generation: Wrapping(0),
    370                    $(
    371                        $ident: None,
    372                    )+
    373                    source_location,
    374                }
    375            }
    376 
    377            $(
    378                #[$doc]
    379                pub fn $ident(&self) -> Option<&$ty> {
    380                    self.$ident.as_ref()
    381                }
    382            )+
    383 
    384            $(
    385                #[$doc]
    386                pub fn $setter(&mut self, value: $ty) -> bool {
    387                    checker!(self.$checker(value));
    388                    self.$ident = Some(value);
    389                    self.generation += Wrapping(1);
    390                    true
    391                }
    392            )+
    393        }
    394 
    395        impl<'a, 'b, 'i> DeclarationParser<'i> for CounterStyleRuleParser<'a, 'b> {
    396            type Declaration = ();
    397            type Error = StyleParseErrorKind<'i>;
    398 
    399            fn parse_value<'t>(
    400                &mut self,
    401                name: CowRcStr<'i>,
    402                input: &mut Parser<'i, 't>,
    403                _declaration_start: &ParserState,
    404            ) -> Result<(), ParseError<'i>> {
    405                match_ignore_ascii_case! { &*name,
    406                    $(
    407                        $name => {
    408                            // DeclarationParser also calls parse_entirely so we’d normally not
    409                            // need to, but in this case we do because we set the value as a side
    410                            // effect rather than returning it.
    411                            let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
    412                            self.rule.$ident = Some(value)
    413                        },
    414                    )*
    415                    _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
    416                }
    417                Ok(())
    418            }
    419        }
    420 
    421        impl ToCssWithGuard for CounterStyleRuleData {
    422            fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
    423                dest.write_str("@counter-style ")?;
    424                self.name.to_css(&mut CssWriter::new(dest))?;
    425                dest.write_str(" { ")?;
    426                $(
    427                    if let Some(ref value) = self.$ident {
    428                        dest.write_str(concat!($name, ": "))?;
    429                        ToCss::to_css(value, &mut CssWriter::new(dest))?;
    430                        dest.write_str("; ")?;
    431                    }
    432                )+
    433                dest.write_char('}')
    434            }
    435        }
    436    }
    437 }
    438 
    439 counter_style_descriptors! {
    440    /// <https://drafts.csswg.org/css-counter-styles/#counter-style-system>
    441    "system" system / set_system [check_system]: System,
    442 
    443    /// <https://drafts.csswg.org/css-counter-styles/#counter-style-negative>
    444    "negative" negative / set_negative [_]: Negative,
    445 
    446    /// <https://drafts.csswg.org/css-counter-styles/#counter-style-prefix>
    447    "prefix" prefix / set_prefix [_]: Symbol,
    448 
    449    /// <https://drafts.csswg.org/css-counter-styles/#counter-style-suffix>
    450    "suffix" suffix / set_suffix [_]: Symbol,
    451 
    452    /// <https://drafts.csswg.org/css-counter-styles/#counter-style-range>
    453    "range" range / set_range [_]: CounterRanges,
    454 
    455    /// <https://drafts.csswg.org/css-counter-styles/#counter-style-pad>
    456    "pad" pad / set_pad [_]: Pad,
    457 
    458    /// <https://drafts.csswg.org/css-counter-styles/#counter-style-fallback>
    459    "fallback" fallback / set_fallback [_]: Fallback,
    460 
    461    /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols>
    462    "symbols" symbols / set_symbols [check_symbols]: Symbols,
    463 
    464    /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-additive-symbols>
    465    "additive-symbols" additive_symbols /
    466        set_additive_symbols [check_additive_symbols]: AdditiveSymbols,
    467 
    468    /// <https://drafts.csswg.org/css-counter-styles/#counter-style-speak-as>
    469    "speak-as" speak_as / set_speak_as [_]: SpeakAs,
    470 }
    471 
    472 // Implements the special checkers for some setters.
    473 // See <https://drafts.csswg.org/css-counter-styles/#the-csscounterstylerule-interface>
    474 impl CounterStyleRuleData {
    475    /// Check that the system is effectively not changed. Only params
    476    /// of system descriptor is changeable.
    477    fn check_system(&self, value: &System) -> bool {
    478        mem::discriminant(self.resolved_system()) == mem::discriminant(value)
    479    }
    480 
    481    fn check_symbols(&self, value: &Symbols) -> bool {
    482        match *self.resolved_system() {
    483            // These two systems require at least two symbols.
    484            System::Numeric | System::Alphabetic => value.0.len() >= 2,
    485            // No symbols should be set for extends system.
    486            System::Extends(_) => false,
    487            _ => true,
    488        }
    489    }
    490 
    491    fn check_additive_symbols(&self, _value: &AdditiveSymbols) -> bool {
    492        match *self.resolved_system() {
    493            // No additive symbols should be set for extends system.
    494            System::Extends(_) => false,
    495            _ => true,
    496        }
    497    }
    498 }
    499 
    500 impl CounterStyleRuleData {
    501    /// Get the name of the counter style rule.
    502    pub fn name(&self) -> &CustomIdent {
    503        &self.name
    504    }
    505 
    506    /// Set the name of the counter style rule. Caller must ensure that
    507    /// the name is valid.
    508    pub fn set_name(&mut self, name: CustomIdent) {
    509        debug_assert!(is_valid_name_definition(&name));
    510        self.name = name;
    511    }
    512 
    513    /// Get the current generation of the counter style rule.
    514    pub fn generation(&self) -> u32 {
    515        self.generation.0
    516    }
    517 
    518    /// Get the system of this counter style rule, default to
    519    /// `symbolic` if not specified.
    520    pub fn resolved_system(&self) -> &System {
    521        match self.system {
    522            Some(ref system) => system,
    523            None => &System::Symbolic,
    524        }
    525    }
    526 }
    527 
    528 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-system>
    529 #[derive(Clone, Debug, ToShmem)]
    530 pub enum System {
    531    /// 'cyclic'
    532    Cyclic,
    533    /// 'numeric'
    534    Numeric,
    535    /// 'alphabetic'
    536    Alphabetic,
    537    /// 'symbolic'
    538    Symbolic,
    539    /// 'additive'
    540    Additive,
    541    /// 'fixed <integer>?'
    542    Fixed {
    543        /// '<integer>?'
    544        first_symbol_value: Option<Integer>,
    545    },
    546    /// 'extends <counter-style-name>'
    547    Extends(CustomIdent),
    548 }
    549 
    550 impl Parse for System {
    551    fn parse<'i, 't>(
    552        context: &ParserContext,
    553        input: &mut Parser<'i, 't>,
    554    ) -> Result<Self, ParseError<'i>> {
    555        try_match_ident_ignore_ascii_case! { input,
    556            "cyclic" => Ok(System::Cyclic),
    557            "numeric" => Ok(System::Numeric),
    558            "alphabetic" => Ok(System::Alphabetic),
    559            "symbolic" => Ok(System::Symbolic),
    560            "additive" => Ok(System::Additive),
    561            "fixed" => {
    562                let first_symbol_value = input.try_parse(|i| Integer::parse(context, i)).ok();
    563                Ok(System::Fixed { first_symbol_value })
    564            },
    565            "extends" => {
    566                let other = parse_counter_style_name(input)?;
    567                Ok(System::Extends(other))
    568            },
    569        }
    570    }
    571 }
    572 
    573 impl ToCss for System {
    574    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    575    where
    576        W: Write,
    577    {
    578        match *self {
    579            System::Cyclic => dest.write_str("cyclic"),
    580            System::Numeric => dest.write_str("numeric"),
    581            System::Alphabetic => dest.write_str("alphabetic"),
    582            System::Symbolic => dest.write_str("symbolic"),
    583            System::Additive => dest.write_str("additive"),
    584            System::Fixed { first_symbol_value } => {
    585                if let Some(value) = first_symbol_value {
    586                    dest.write_str("fixed ")?;
    587                    value.to_css(dest)
    588                } else {
    589                    dest.write_str("fixed")
    590                }
    591            },
    592            System::Extends(ref other) => {
    593                dest.write_str("extends ")?;
    594                other.to_css(dest)
    595            },
    596        }
    597    }
    598 }
    599 
    600 /// <https://drafts.csswg.org/css-counter-styles/#typedef-symbol>
    601 #[derive(
    602    Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem,
    603 )]
    604 #[repr(u8)]
    605 pub enum Symbol {
    606    /// <string>
    607    String(crate::OwnedStr),
    608    /// <custom-ident>
    609    Ident(CustomIdent),
    610    // Not implemented:
    611    // /// <image>
    612    // Image(Image),
    613 }
    614 
    615 impl Parse for Symbol {
    616    fn parse<'i, 't>(
    617        _context: &ParserContext,
    618        input: &mut Parser<'i, 't>,
    619    ) -> Result<Self, ParseError<'i>> {
    620        let location = input.current_source_location();
    621        match *input.next()? {
    622            Token::QuotedString(ref s) => Ok(Symbol::String(s.as_ref().to_owned().into())),
    623            Token::Ident(ref s) => Ok(Symbol::Ident(CustomIdent::from_ident(location, s, &[])?)),
    624            ref t => Err(location.new_unexpected_token_error(t.clone())),
    625        }
    626    }
    627 }
    628 
    629 impl Symbol {
    630    /// Returns whether this symbol is allowed in symbols() function.
    631    pub fn is_allowed_in_symbols(&self) -> bool {
    632        match self {
    633            // Identifier is not allowed.
    634            &Symbol::Ident(_) => false,
    635            _ => true,
    636        }
    637    }
    638 }
    639 
    640 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-negative>
    641 #[derive(Clone, Debug, ToCss, ToShmem)]
    642 pub struct Negative(pub Symbol, pub Option<Symbol>);
    643 
    644 impl Parse for Negative {
    645    fn parse<'i, 't>(
    646        context: &ParserContext,
    647        input: &mut Parser<'i, 't>,
    648    ) -> Result<Self, ParseError<'i>> {
    649        Ok(Negative(
    650            Symbol::parse(context, input)?,
    651            input.try_parse(|input| Symbol::parse(context, input)).ok(),
    652        ))
    653    }
    654 }
    655 
    656 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-range>
    657 #[derive(Clone, Debug, ToCss, ToShmem)]
    658 pub struct CounterRange {
    659    /// The start of the range.
    660    pub start: CounterBound,
    661    /// The end of the range.
    662    pub end: CounterBound,
    663 }
    664 
    665 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-range>
    666 ///
    667 /// Empty represents 'auto'
    668 #[derive(Clone, Debug, ToCss, ToShmem)]
    669 #[css(comma)]
    670 pub struct CounterRanges(#[css(iterable, if_empty = "auto")] pub crate::OwnedSlice<CounterRange>);
    671 
    672 /// A bound found in `CounterRanges`.
    673 #[derive(Clone, Copy, Debug, ToCss, ToShmem)]
    674 pub enum CounterBound {
    675    /// An integer bound.
    676    Integer(Integer),
    677    /// The infinite bound.
    678    Infinite,
    679 }
    680 
    681 impl Parse for CounterRanges {
    682    fn parse<'i, 't>(
    683        context: &ParserContext,
    684        input: &mut Parser<'i, 't>,
    685    ) -> Result<Self, ParseError<'i>> {
    686        if input
    687            .try_parse(|input| input.expect_ident_matching("auto"))
    688            .is_ok()
    689        {
    690            return Ok(CounterRanges(Default::default()));
    691        }
    692 
    693        let ranges = input.parse_comma_separated(|input| {
    694            let start = parse_bound(context, input)?;
    695            let end = parse_bound(context, input)?;
    696            if let (CounterBound::Integer(start), CounterBound::Integer(end)) = (start, end) {
    697                if start > end {
    698                    return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    699                }
    700            }
    701            Ok(CounterRange { start, end })
    702        })?;
    703 
    704        Ok(CounterRanges(ranges.into()))
    705    }
    706 }
    707 
    708 fn parse_bound<'i, 't>(
    709    context: &ParserContext,
    710    input: &mut Parser<'i, 't>,
    711 ) -> Result<CounterBound, ParseError<'i>> {
    712    if let Ok(integer) = input.try_parse(|input| Integer::parse(context, input)) {
    713        return Ok(CounterBound::Integer(integer));
    714    }
    715    input.expect_ident_matching("infinite")?;
    716    Ok(CounterBound::Infinite)
    717 }
    718 
    719 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-pad>
    720 #[derive(Clone, Debug, ToCss, ToShmem)]
    721 pub struct Pad(pub Integer, pub Symbol);
    722 
    723 impl Parse for Pad {
    724    fn parse<'i, 't>(
    725        context: &ParserContext,
    726        input: &mut Parser<'i, 't>,
    727    ) -> Result<Self, ParseError<'i>> {
    728        let pad_with = input.try_parse(|input| Symbol::parse(context, input));
    729        let min_length = Integer::parse_non_negative(context, input)?;
    730        let pad_with = pad_with.or_else(|_| Symbol::parse(context, input))?;
    731        Ok(Pad(min_length, pad_with))
    732    }
    733 }
    734 
    735 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-fallback>
    736 #[derive(Clone, Debug, ToCss, ToShmem)]
    737 pub struct Fallback(pub CustomIdent);
    738 
    739 impl Parse for Fallback {
    740    fn parse<'i, 't>(
    741        _context: &ParserContext,
    742        input: &mut Parser<'i, 't>,
    743    ) -> Result<Self, ParseError<'i>> {
    744        Ok(Fallback(parse_counter_style_name(input)?))
    745    }
    746 }
    747 
    748 /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols>
    749 #[derive(
    750    Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem,
    751 )]
    752 #[repr(C)]
    753 pub struct Symbols(
    754    #[css(iterable)]
    755    #[ignore_malloc_size_of = "Arc"]
    756    pub crate::ArcSlice<Symbol>,
    757 );
    758 
    759 impl Parse for Symbols {
    760    fn parse<'i, 't>(
    761        context: &ParserContext,
    762        input: &mut Parser<'i, 't>,
    763    ) -> Result<Self, ParseError<'i>> {
    764        let mut symbols = smallvec::SmallVec::<[_; 5]>::new();
    765        while let Ok(s) = input.try_parse(|input| Symbol::parse(context, input)) {
    766            symbols.push(s);
    767        }
    768        if symbols.is_empty() {
    769            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    770        }
    771        Ok(Symbols(crate::ArcSlice::from_iter(symbols.drain(..))))
    772    }
    773 }
    774 
    775 /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-additive-symbols>
    776 #[derive(Clone, Debug, ToCss, ToShmem)]
    777 #[css(comma)]
    778 pub struct AdditiveSymbols(#[css(iterable)] pub crate::OwnedSlice<AdditiveTuple>);
    779 
    780 impl Parse for AdditiveSymbols {
    781    fn parse<'i, 't>(
    782        context: &ParserContext,
    783        input: &mut Parser<'i, 't>,
    784    ) -> Result<Self, ParseError<'i>> {
    785        let tuples = Vec::<AdditiveTuple>::parse(context, input)?;
    786        // FIXME maybe? https://github.com/w3c/csswg-drafts/issues/1220
    787        if tuples
    788            .windows(2)
    789            .any(|window| window[0].weight <= window[1].weight)
    790        {
    791            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    792        }
    793        Ok(AdditiveSymbols(tuples.into()))
    794    }
    795 }
    796 
    797 /// <integer> && <symbol>
    798 #[derive(Clone, Debug, ToCss, ToShmem)]
    799 pub struct AdditiveTuple {
    800    /// <integer>
    801    pub weight: Integer,
    802    /// <symbol>
    803    pub symbol: Symbol,
    804 }
    805 
    806 impl OneOrMoreSeparated for AdditiveTuple {
    807    type S = Comma;
    808 }
    809 
    810 impl Parse for AdditiveTuple {
    811    fn parse<'i, 't>(
    812        context: &ParserContext,
    813        input: &mut Parser<'i, 't>,
    814    ) -> Result<Self, ParseError<'i>> {
    815        let symbol = input.try_parse(|input| Symbol::parse(context, input));
    816        let weight = Integer::parse_non_negative(context, input)?;
    817        let symbol = symbol.or_else(|_| Symbol::parse(context, input))?;
    818        Ok(Self { weight, symbol })
    819    }
    820 }
    821 
    822 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-speak-as>
    823 #[derive(Clone, Debug, ToCss, ToShmem)]
    824 pub enum SpeakAs {
    825    /// auto
    826    Auto,
    827    /// bullets
    828    Bullets,
    829    /// numbers
    830    Numbers,
    831    /// words
    832    Words,
    833    // /// spell-out, not supported, see bug 1024178
    834    // SpellOut,
    835    /// <counter-style-name>
    836    Other(CustomIdent),
    837 }
    838 
    839 impl Parse for SpeakAs {
    840    fn parse<'i, 't>(
    841        _context: &ParserContext,
    842        input: &mut Parser<'i, 't>,
    843    ) -> Result<Self, ParseError<'i>> {
    844        let mut is_spell_out = false;
    845        let result = input.try_parse(|input| {
    846            let ident = input.expect_ident().map_err(|_| ())?;
    847            match_ignore_ascii_case! { &*ident,
    848                "auto" => Ok(SpeakAs::Auto),
    849                "bullets" => Ok(SpeakAs::Bullets),
    850                "numbers" => Ok(SpeakAs::Numbers),
    851                "words" => Ok(SpeakAs::Words),
    852                "spell-out" => {
    853                    is_spell_out = true;
    854                    Err(())
    855                },
    856                _ => Err(()),
    857            }
    858        });
    859        if is_spell_out {
    860            // spell-out is not supported, but don’t parse it as a <counter-style-name>.
    861            // See bug 1024178.
    862            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    863        }
    864        result.or_else(|_| Ok(SpeakAs::Other(parse_counter_style_name(input)?)))
    865    }
    866 }