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