summaryrefslogtreecommitdiffstats
path: root/servo/components/style/values/specified/counters.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/values/specified/counters.rs')
-rw-r--r--servo/components/style/values/specified/counters.rs303
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",
+ ]);
+ }
+}