1#[cfg(feature = "servo")]
8use crate::computed_values::list_style_type::T as ListStyleType;
9#[cfg(feature = "gecko")]
10use crate::counter_style::CounterStyle;
11use crate::parser::{Parse, ParserContext};
12use crate::values::generics::counters as generics;
13use crate::values::generics::counters::CounterPair;
14use crate::values::specified::image::Image;
15use crate::values::specified::Attr;
16use crate::values::specified::Integer;
17use crate::values::CustomIdent;
18use cssparser::{Parser, Token};
19use selectors::parser::SelectorParseErrorKind;
20use style_traits::{ParseError, StyleParseErrorKind};
21
22#[derive(PartialEq)]
23enum CounterType {
24 Increment,
25 Set,
26 Reset,
27}
28
29impl CounterType {
30 fn default_value(&self) -> i32 {
31 match *self {
32 Self::Increment => 1,
33 Self::Reset | Self::Set => 0,
34 }
35 }
36}
37
38pub type CounterIncrement = generics::GenericCounterIncrement<Integer>;
40
41impl 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
54pub type CounterSet = generics::GenericCounterSet<Integer>;
56
57impl 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
66pub type CounterReset = generics::GenericCounterReset<Integer>;
68
69impl 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
82fn 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 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
145pub type Content = generics::GenericContent<Image>;
147
148pub type ContentItem = generics::GenericContentItem<Image>;
150
151impl 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
174impl Parse for Content {
175 #[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 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" => 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}