tor-browser

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

counters.rs (10689B)


      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 //! Specified types for counter properties.
      6 
      7 #[cfg(feature = "servo")]
      8 use crate::computed_values::list_style_type::T as ListStyleType;
      9 #[cfg(feature = "gecko")]
     10 use crate::counter_style::CounterStyle;
     11 use crate::parser::{Parse, ParserContext};
     12 use crate::values::generics::counters as generics;
     13 use crate::values::generics::counters::CounterPair;
     14 use crate::values::specified::image::Image;
     15 use crate::values::specified::Attr;
     16 use crate::values::specified::Integer;
     17 use crate::values::CustomIdent;
     18 use cssparser::{match_ignore_ascii_case, Parser, Token};
     19 use selectors::parser::SelectorParseErrorKind;
     20 use style_traits::{ParseError, StyleParseErrorKind};
     21 
     22 #[derive(PartialEq)]
     23 enum CounterType {
     24    Increment,
     25    Set,
     26    Reset,
     27 }
     28 
     29 impl CounterType {
     30    fn default_value(&self) -> i32 {
     31        match *self {
     32            Self::Increment => 1,
     33            Self::Reset | Self::Set => 0,
     34        }
     35    }
     36 }
     37 
     38 /// A specified value for the `counter-increment` property.
     39 pub type CounterIncrement = generics::GenericCounterIncrement<Integer>;
     40 
     41 impl Parse for CounterIncrement {
     42    fn parse<'i, 't>(
     43        context: &ParserContext,
     44        input: &mut Parser<'i, 't>,
     45    ) -> Result<Self, ParseError<'i>> {
     46        Ok(Self::new(parse_counters(
     47            context,
     48            input,
     49            CounterType::Increment,
     50        )?))
     51    }
     52 }
     53 
     54 /// A specified value for the `counter-set` property.
     55 pub type CounterSet = generics::GenericCounterSet<Integer>;
     56 
     57 impl Parse for CounterSet {
     58    fn parse<'i, 't>(
     59        context: &ParserContext,
     60        input: &mut Parser<'i, 't>,
     61    ) -> Result<Self, ParseError<'i>> {
     62        Ok(Self::new(parse_counters(context, input, CounterType::Set)?))
     63    }
     64 }
     65 
     66 /// A specified value for the `counter-reset` property.
     67 pub type CounterReset = generics::GenericCounterReset<Integer>;
     68 
     69 impl Parse for CounterReset {
     70    fn parse<'i, 't>(
     71        context: &ParserContext,
     72        input: &mut Parser<'i, 't>,
     73    ) -> Result<Self, ParseError<'i>> {
     74        Ok(Self::new(parse_counters(
     75            context,
     76            input,
     77            CounterType::Reset,
     78        )?))
     79    }
     80 }
     81 
     82 fn parse_counters<'i, 't>(
     83    context: &ParserContext,
     84    input: &mut Parser<'i, 't>,
     85    counter_type: CounterType,
     86 ) -> Result<Vec<CounterPair<Integer>>, ParseError<'i>> {
     87    if input
     88        .try_parse(|input| input.expect_ident_matching("none"))
     89        .is_ok()
     90    {
     91        return Ok(vec![]);
     92    }
     93 
     94    let mut counters = Vec::new();
     95    loop {
     96        let location = input.current_source_location();
     97        let (name, is_reversed) = match input.next() {
     98            Ok(&Token::Ident(ref ident)) => {
     99                (CustomIdent::from_ident(location, ident, &["none"])?, false)
    100            },
    101            Ok(&Token::Function(ref name))
    102                if counter_type == CounterType::Reset && name.eq_ignore_ascii_case("reversed") =>
    103            {
    104                input
    105                    .parse_nested_block(|input| Ok((CustomIdent::parse(input, &["none"])?, true)))?
    106            },
    107            Ok(t) => {
    108                let t = t.clone();
    109                return Err(location.new_unexpected_token_error(t));
    110            },
    111            Err(_) => break,
    112        };
    113 
    114        let value = match input.try_parse(|input| Integer::parse(context, input)) {
    115            Ok(start) => {
    116                if start.value() == i32::min_value() {
    117                    // The spec says that values must be clamped to the valid range,
    118                    // and we reserve i32::min_value() as an internal magic value.
    119                    // https://drafts.csswg.org/css-lists/#auto-numbering
    120                    Integer::new(i32::min_value() + 1)
    121                } else {
    122                    start
    123                }
    124            },
    125            _ => Integer::new(if is_reversed {
    126                i32::min_value()
    127            } else {
    128                counter_type.default_value()
    129            }),
    130        };
    131        counters.push(CounterPair {
    132            name,
    133            value,
    134            is_reversed,
    135        });
    136    }
    137 
    138    if !counters.is_empty() {
    139        Ok(counters)
    140    } else {
    141        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
    142    }
    143 }
    144 
    145 /// The specified value for the `content` property.
    146 pub type Content = generics::GenericContent<Image>;
    147 
    148 /// The specified value for a content item in the `content` property.
    149 pub type ContentItem = generics::GenericContentItem<Image>;
    150 
    151 impl Content {
    152    #[cfg(feature = "servo")]
    153    fn parse_counter_style(_: &ParserContext, input: &mut Parser) -> ListStyleType {
    154        input
    155            .try_parse(|input| {
    156                input.expect_comma()?;
    157                ListStyleType::parse(input)
    158            })
    159            .unwrap_or(ListStyleType::Decimal)
    160    }
    161 
    162    #[cfg(feature = "gecko")]
    163    fn parse_counter_style(context: &ParserContext, input: &mut Parser) -> CounterStyle {
    164        use crate::counter_style::CounterStyleParsingFlags;
    165        input
    166            .try_parse(|input| {
    167                input.expect_comma()?;
    168                CounterStyle::parse(context, input, CounterStyleParsingFlags::empty())
    169            })
    170            .unwrap_or_else(|_| CounterStyle::decimal())
    171    }
    172 }
    173 
    174 impl Parse for Content {
    175    // normal | none | [ <string> | <counter> | open-quote | close-quote | no-open-quote |
    176    // no-close-quote ]+
    177    // TODO: <uri>, attr(<identifier>)
    178    #[cfg_attr(feature = "servo", allow(unused_mut))]
    179    fn parse<'i, 't>(
    180        context: &ParserContext,
    181        input: &mut Parser<'i, 't>,
    182    ) -> Result<Self, ParseError<'i>> {
    183        if input
    184            .try_parse(|input| input.expect_ident_matching("normal"))
    185            .is_ok()
    186        {
    187            return Ok(generics::Content::Normal);
    188        }
    189        if input
    190            .try_parse(|input| input.expect_ident_matching("none"))
    191            .is_ok()
    192        {
    193            return Ok(generics::Content::None);
    194        }
    195 
    196        let mut items = thin_vec::ThinVec::new();
    197        let mut alt_start = None;
    198        loop {
    199            if alt_start.is_none() {
    200                if let Ok(image) = input.try_parse(|i| Image::parse_forbid_none(context, i)) {
    201                    items.push(generics::ContentItem::Image(image));
    202                    continue;
    203                }
    204            }
    205            let Ok(t) = input.next() else { break };
    206            match *t {
    207                Token::QuotedString(ref value) => {
    208                    items.push(generics::ContentItem::String(
    209                        value.as_ref().to_owned().into(),
    210                    ));
    211                },
    212                Token::Function(ref name) => {
    213                    // FIXME(emilio): counter() / counters() should be valid per spec past
    214                    // the alt marker, but it's likely non-trivial to support and other
    215                    // browsers don't support it either, so restricting it for now.
    216                    let result = match_ignore_ascii_case! { &name,
    217                        "counter" if alt_start.is_none() => input.parse_nested_block(|input| {
    218                            let name = CustomIdent::parse(input, &[])?;
    219                            let style = Content::parse_counter_style(context, input);
    220                            Ok(generics::ContentItem::Counter(name, style))
    221                        }),
    222                        "counters" if alt_start.is_none() => input.parse_nested_block(|input| {
    223                            let name = CustomIdent::parse(input, &[])?;
    224                            input.expect_comma()?;
    225                            let separator = input.expect_string()?.as_ref().to_owned().into();
    226                            let style = Content::parse_counter_style(context, input);
    227                            Ok(generics::ContentItem::Counters(name, separator, style))
    228                        }),
    229                        "attr" if !static_prefs::pref!("layout.css.attr.enabled") => input.parse_nested_block(|input| {
    230                            Ok(generics::ContentItem::Attr(Attr::parse_function(context, input)?))
    231                        }),
    232                        _ => {
    233                            use style_traits::StyleParseErrorKind;
    234                            let name = name.clone();
    235                            return Err(input.new_custom_error(
    236                                StyleParseErrorKind::UnexpectedFunction(name),
    237                            ))
    238                        }
    239                    }?;
    240                    items.push(result);
    241                },
    242                Token::Ident(ref ident) if alt_start.is_none() => {
    243                    items.push(match_ignore_ascii_case! { &ident,
    244                        "open-quote" => generics::ContentItem::OpenQuote,
    245                        "close-quote" => generics::ContentItem::CloseQuote,
    246                        "no-open-quote" => generics::ContentItem::NoOpenQuote,
    247                        "no-close-quote" => generics::ContentItem::NoCloseQuote,
    248                        #[cfg(feature = "gecko")]
    249                        "-moz-alt-content" if context.in_ua_sheet() => {
    250                            generics::ContentItem::MozAltContent
    251                        },
    252                        #[cfg(feature = "gecko")]
    253                        "-moz-label-content" if context.chrome_rules_enabled() => {
    254                            generics::ContentItem::MozLabelContent
    255                        },
    256                        _ =>{
    257                            let ident = ident.clone();
    258                            return Err(input.new_custom_error(
    259                                SelectorParseErrorKind::UnexpectedIdent(ident)
    260                            ));
    261                        }
    262                    });
    263                },
    264                Token::Delim('/')
    265                    if alt_start.is_none()
    266                        && !items.is_empty()
    267                        && static_prefs::pref!("layout.css.content.alt-text.enabled") =>
    268                {
    269                    alt_start = Some(items.len());
    270                },
    271                ref t => {
    272                    let t = t.clone();
    273                    return Err(input.new_unexpected_token_error(t));
    274                },
    275            }
    276        }
    277        if items.is_empty() {
    278            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    279        }
    280        let alt_start = alt_start.unwrap_or(items.len());
    281        Ok(generics::Content::Items(generics::GenericContentItems {
    282            items,
    283            alt_start,
    284        }))
    285    }
    286 }