diff options
Diffstat (limited to 'servo/components/style/values/specified/counters.rs')
-rw-r--r-- | servo/components/style/values/specified/counters.rs | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/servo/components/style/values/specified/counters.rs b/servo/components/style/values/specified/counters.rs new file mode 100644 index 0000000000..804a519556 --- /dev/null +++ b/servo/components/style/values/specified/counters.rs @@ -0,0 +1,303 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! Specified types for counter properties. + +#[cfg(feature = "servo-layout-2013")] +use crate::computed_values::list_style_type::T as ListStyleType; +use crate::parser::{Parse, ParserContext}; +use crate::values::generics::counters as generics; +use crate::values::generics::counters::CounterPair; +#[cfg(feature = "gecko")] +use crate::values::generics::CounterStyle; +use crate::values::specified::image::Image; +#[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] +use crate::values::specified::Attr; +use crate::values::specified::Integer; +use crate::values::CustomIdent; +use cssparser::{Parser, Token}; +#[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] +use selectors::parser::SelectorParseErrorKind; +use style_traits::{KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind}; + +#[derive(PartialEq)] +enum CounterType { + Increment, + Set, + Reset, +} + +impl CounterType { + fn default_value(&self) -> i32 { + match *self { + Self::Increment => 1, + Self::Reset | Self::Set => 0, + } + } +} + +/// A specified value for the `counter-increment` property. +pub type CounterIncrement = generics::GenericCounterIncrement<Integer>; + +impl Parse for CounterIncrement { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Ok(Self::new(parse_counters( + context, + input, + CounterType::Increment, + )?)) + } +} + +/// A specified value for the `counter-set` property. +pub type CounterSet = generics::GenericCounterSet<Integer>; + +impl Parse for CounterSet { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Ok(Self::new(parse_counters(context, input, CounterType::Set)?)) + } +} + +/// A specified value for the `counter-reset` property. +pub type CounterReset = generics::GenericCounterReset<Integer>; + +impl Parse for CounterReset { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Ok(Self::new(parse_counters( + context, + input, + CounterType::Reset, + )?)) + } +} + +fn parse_counters<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + counter_type: CounterType, +) -> Result<Vec<CounterPair<Integer>>, ParseError<'i>> { + if input + .try_parse(|input| input.expect_ident_matching("none")) + .is_ok() + { + return Ok(vec![]); + } + + let mut counters = Vec::new(); + loop { + let location = input.current_source_location(); + let (name, is_reversed) = match input.next() { + Ok(&Token::Ident(ref ident)) => { + (CustomIdent::from_ident(location, ident, &["none"])?, false) + }, + Ok(&Token::Function(ref name)) + if counter_type == CounterType::Reset && name.eq_ignore_ascii_case("reversed") => + { + input.parse_nested_block(|input| { + let location = input.current_source_location(); + Ok(( + CustomIdent::from_ident(location, input.expect_ident()?, &["none"])?, + true, + )) + })? + }, + Ok(t) => { + let t = t.clone(); + return Err(location.new_unexpected_token_error(t)); + }, + Err(_) => break, + }; + + let value = match input.try_parse(|input| Integer::parse(context, input)) { + Ok(start) => { + if start.value == i32::min_value() { + // The spec says that values must be clamped to the valid range, + // and we reserve i32::min_value() as an internal magic value. + // https://drafts.csswg.org/css-lists/#auto-numbering + Integer::new(i32::min_value() + 1) + } else { + start + } + }, + _ => Integer::new(if is_reversed { + i32::min_value() + } else { + counter_type.default_value() + }), + }; + counters.push(CounterPair { + name, + value, + is_reversed, + }); + } + + if !counters.is_empty() { + Ok(counters) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } +} + +/// The specified value for the `content` property. +pub type Content = generics::GenericContent<Image>; + +/// The specified value for a content item in the `content` property. +pub type ContentItem = generics::GenericContentItem<Image>; + +impl Content { + #[cfg(feature = "servo-layout-2013")] + fn parse_counter_style(_: &ParserContext, input: &mut Parser) -> ListStyleType { + input + .try_parse(|input| { + input.expect_comma()?; + ListStyleType::parse(input) + }) + .unwrap_or(ListStyleType::Decimal) + } + + #[cfg(feature = "gecko")] + fn parse_counter_style(context: &ParserContext, input: &mut Parser) -> CounterStyle { + input + .try_parse(|input| { + input.expect_comma()?; + CounterStyle::parse(context, input) + }) + .unwrap_or(CounterStyle::decimal()) + } +} + +impl Parse for Content { + // normal | none | [ <string> | <counter> | open-quote | close-quote | no-open-quote | + // no-close-quote ]+ + // TODO: <uri>, attr(<identifier>) + #[cfg_attr(feature = "servo", allow(unused_mut))] + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + if input + .try_parse(|input| input.expect_ident_matching("normal")) + .is_ok() + { + return Ok(generics::Content::Normal); + } + if input + .try_parse(|input| input.expect_ident_matching("none")) + .is_ok() + { + return Ok(generics::Content::None); + } + + let mut content = vec![]; + let mut has_alt_content = false; + loop { + #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] + { + if let Ok(image) = input.try_parse(|i| Image::parse_only_url(context, i)) { + content.push(generics::ContentItem::Image(image)); + continue; + } + } + match input.next() { + Ok(&Token::QuotedString(ref value)) => { + content.push(generics::ContentItem::String( + value.as_ref().to_owned().into(), + )); + }, + Ok(&Token::Function(ref name)) => { + let result = match_ignore_ascii_case! { &name, + #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] + "counter" => input.parse_nested_block(|input| { + let location = input.current_source_location(); + let name = CustomIdent::from_ident(location, input.expect_ident()?, &[])?; + let style = Content::parse_counter_style(context, input); + Ok(generics::ContentItem::Counter(name, style)) + }), + #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] + "counters" => input.parse_nested_block(|input| { + let location = input.current_source_location(); + let name = CustomIdent::from_ident(location, input.expect_ident()?, &[])?; + input.expect_comma()?; + let separator = input.expect_string()?.as_ref().to_owned().into(); + let style = Content::parse_counter_style(context, input); + Ok(generics::ContentItem::Counters(name, separator, style)) + }), + #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] + "attr" => input.parse_nested_block(|input| { + Ok(generics::ContentItem::Attr(Attr::parse_function(context, input)?)) + }), + _ => { + use style_traits::StyleParseErrorKind; + let name = name.clone(); + return Err(input.new_custom_error( + StyleParseErrorKind::UnexpectedFunction(name), + )) + } + }?; + content.push(result); + }, + #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] + Ok(&Token::Ident(ref ident)) => { + content.push(match_ignore_ascii_case! { &ident, + "open-quote" => generics::ContentItem::OpenQuote, + "close-quote" => generics::ContentItem::CloseQuote, + "no-open-quote" => generics::ContentItem::NoOpenQuote, + "no-close-quote" => generics::ContentItem::NoCloseQuote, + #[cfg(feature = "gecko")] + "-moz-alt-content" => { + has_alt_content = true; + generics::ContentItem::MozAltContent + }, + "-moz-label-content" if context.chrome_rules_enabled() => { + generics::ContentItem::MozLabelContent + }, + _ =>{ + let ident = ident.clone(); + return Err(input.new_custom_error( + SelectorParseErrorKind::UnexpectedIdent(ident) + )); + } + }); + }, + Err(_) => break, + Ok(t) => { + let t = t.clone(); + return Err(input.new_unexpected_token_error(t)); + }, + } + } + // We don't allow to parse `-moz-alt-content` in multiple positions. + if content.is_empty() || (has_alt_content && content.len() != 1) { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + Ok(generics::Content::Items(content.into())) + } +} + +impl<Image> SpecifiedValueInfo for generics::GenericContentItem<Image> { + fn collect_completion_keywords(f: KeywordsCollectFn) { + f(&[ + "url", + "image-set", + "counter", + "counters", + "attr", + "open-quote", + "close-quote", + "no-open-quote", + "no-close-quote", + "-moz-alt-content", + ]); + } +} |