1use crate::derives::*;
10use crate::error_reporting::ContextualParseError;
11use crate::parser::{Parse, ParserContext};
12use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
13use crate::values::specified::Integer;
14use crate::values::{AtomString, CustomIdent};
15use crate::Atom;
16use cssparser::{
17 ascii_case_insensitive_phf_map, match_ignore_ascii_case, CowRcStr, Parser, RuleBodyParser,
18 SourceLocation, Token,
19};
20use std::fmt::{self, Write};
21use std::mem;
22use std::num::Wrapping;
23use style_traits::{
24 Comma, CssStringWriter, CssWriter, KeywordsCollectFn, OneOrMoreSeparated, ParseError,
25 SpecifiedValueInfo, StyleParseErrorKind, ToCss,
26};
27
28pub use crate::properties::counter_style::{DescriptorId, DescriptorParser, Descriptors};
29
30#[allow(missing_docs)]
32#[derive(
33 Clone,
34 Copy,
35 Debug,
36 Deserialize,
37 Eq,
38 MallocSizeOf,
39 Parse,
40 PartialEq,
41 Serialize,
42 ToComputedValue,
43 ToCss,
44 ToResolvedValue,
45 ToShmem,
46)]
47#[repr(u8)]
48pub enum SymbolsType {
49 Cyclic,
50 Numeric,
51 Alphabetic,
52 Symbolic,
53 Fixed,
54}
55
56#[derive(
61 Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
62)]
63#[repr(u8)]
64pub enum CounterStyle {
65 None,
67 Name(CustomIdent),
69 #[css(function)]
71 Symbols {
72 #[css(skip_if = "is_symbolic")]
74 ty: SymbolsType,
75 symbols: Symbols,
77 },
78 String(AtomString),
80}
81
82#[inline]
83fn is_symbolic(symbols_type: &SymbolsType) -> bool {
84 *symbols_type == SymbolsType::Symbolic
85}
86
87impl CounterStyle {
88 pub fn disc() -> Self {
90 CounterStyle::Name(CustomIdent(atom!("disc")))
91 }
92
93 pub fn decimal() -> Self {
95 CounterStyle::Name(CustomIdent(atom!("decimal")))
96 }
97
98 #[inline]
100 pub fn is_bullet(&self) -> bool {
101 match self {
102 CounterStyle::Name(CustomIdent(ref name)) => {
103 name == &atom!("disc")
104 || name == &atom!("circle")
105 || name == &atom!("square")
106 || name == &atom!("disclosure-closed")
107 || name == &atom!("disclosure-open")
108 },
109 _ => false,
110 }
111 }
112}
113
114bitflags! {
115 #[derive(Clone, Copy)]
116 pub struct CounterStyleParsingFlags: u8 {
118 const ALLOW_NONE = 1 << 0;
120 const ALLOW_STRING = 1 << 1;
122 }
123}
124
125impl CounterStyle {
126 pub fn parse<'i, 't>(
128 context: &ParserContext,
129 input: &mut Parser<'i, 't>,
130 flags: CounterStyleParsingFlags,
131 ) -> Result<Self, ParseError<'i>> {
132 use self::CounterStyleParsingFlags as Flags;
133 let location = input.current_source_location();
134 match input.next()? {
135 Token::QuotedString(ref string) if flags.intersects(Flags::ALLOW_STRING) => {
136 Ok(Self::String(AtomString::from(string.as_ref())))
137 },
138 Token::Ident(ref ident) => {
139 if flags.intersects(Flags::ALLOW_NONE) && ident.eq_ignore_ascii_case("none") {
140 return Ok(Self::None);
141 }
142 Ok(Self::Name(counter_style_name_from_ident(ident, location)?))
143 },
144 Token::Function(ref name) if name.eq_ignore_ascii_case("symbols") => {
145 input.parse_nested_block(|input| {
146 let symbols_type = input
147 .try_parse(SymbolsType::parse)
148 .unwrap_or(SymbolsType::Symbolic);
149 let symbols = Symbols::parse(context, input)?;
150 if (symbols_type == SymbolsType::Alphabetic
153 || symbols_type == SymbolsType::Numeric)
154 && symbols.0.len() < 2
155 {
156 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
157 }
158 if symbols.0.iter().any(|sym| !sym.is_allowed_in_symbols()) {
160 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
161 }
162 Ok(Self::Symbols {
163 ty: symbols_type,
164 symbols,
165 })
166 })
167 },
168 t => Err(location.new_unexpected_token_error(t.clone())),
169 }
170 }
171}
172
173impl SpecifiedValueInfo for CounterStyle {
174 fn collect_completion_keywords(f: KeywordsCollectFn) {
175 macro_rules! predefined {
181 ($($name:expr,)+) => {
182 f(&["symbols", "none", $($name,)+])
183 }
184 }
185 include!("predefined.rs");
186 }
187}
188
189fn parse_counter_style_name<'i>(input: &mut Parser<'i, '_>) -> Result<CustomIdent, ParseError<'i>> {
190 let location = input.current_source_location();
191 let ident = input.expect_ident()?;
192 counter_style_name_from_ident(ident, location)
193}
194
195fn counter_style_name_from_ident<'i>(
197 ident: &CowRcStr<'i>,
198 location: SourceLocation,
199) -> Result<CustomIdent, ParseError<'i>> {
200 macro_rules! predefined {
201 ($($name: tt,)+) => {{
202 ascii_case_insensitive_phf_map! {
203 predefined -> Atom = {
204 $(
205 $name => atom!($name),
206 )+
207 }
208 }
209
210 if let Some(lower_case) = predefined::get(&ident) {
212 Ok(CustomIdent(lower_case.clone()))
213 } else {
214 CustomIdent::from_ident(location, ident, &["none"])
216 }
217 }}
218 }
219 include!("predefined.rs")
220}
221
222fn is_valid_name_definition(ident: &CustomIdent) -> bool {
223 ident.0 != atom!("decimal")
224 && ident.0 != atom!("disc")
225 && ident.0 != atom!("circle")
226 && ident.0 != atom!("square")
227 && ident.0 != atom!("disclosure-closed")
228 && ident.0 != atom!("disclosure-open")
229}
230
231pub fn parse_counter_style_name_definition<'i, 't>(
233 input: &mut Parser<'i, 't>,
234) -> Result<CustomIdent, ParseError<'i>> {
235 parse_counter_style_name(input).and_then(|ident| {
236 if !is_valid_name_definition(&ident) {
237 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
238 } else {
239 Ok(ident)
240 }
241 })
242}
243
244#[derive(Clone, Debug, ToShmem)]
246pub struct CounterStyleRule {
247 name: CustomIdent,
248 generation: Wrapping<u32>,
249 descriptors: Descriptors,
250 pub source_location: SourceLocation,
252}
253
254pub fn parse_counter_style_body<'i, 't>(
256 name: CustomIdent,
257 context: &ParserContext,
258 input: &mut Parser<'i, 't>,
259 location: SourceLocation,
260) -> Result<CounterStyleRule, ParseError<'i>> {
261 let start = input.current_source_location();
262 let mut rule = CounterStyleRule::empty(name, location);
263 {
264 let mut parser = DescriptorParser {
265 context,
266 descriptors: &mut rule.descriptors,
267 };
268 let mut iter = RuleBodyParser::new(input, &mut parser);
269 while let Some(declaration) = iter.next() {
270 if let Err((error, slice)) = declaration {
271 let location = error.location;
272 let error = ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(
273 slice, error,
274 );
275 context.log_css_error(location, error)
276 }
277 }
278 }
279 let error = match *rule.resolved_system() {
280 ref system @ System::Cyclic
281 | ref system @ System::Fixed { .. }
282 | ref system @ System::Symbolic
283 | ref system @ System::Alphabetic
284 | ref system @ System::Numeric
285 if rule.descriptors.symbols.is_none() =>
286 {
287 let system = system.to_css_string();
288 Some(ContextualParseError::InvalidCounterStyleWithoutSymbols(
289 system,
290 ))
291 },
292 ref system @ System::Alphabetic | ref system @ System::Numeric
293 if rule.descriptors.symbols.as_ref().unwrap().0.len() < 2 =>
294 {
295 let system = system.to_css_string();
296 Some(ContextualParseError::InvalidCounterStyleNotEnoughSymbols(
297 system,
298 ))
299 },
300 System::Additive if rule.descriptors.additive_symbols.is_none() => {
301 Some(ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols)
302 },
303 System::Extends(_) if rule.descriptors.symbols.is_some() => {
304 Some(ContextualParseError::InvalidCounterStyleExtendsWithSymbols)
305 },
306 System::Extends(_) if rule.descriptors.additive_symbols.is_some() => {
307 Some(ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols)
308 },
309 _ => None,
310 };
311 if let Some(error) = error {
312 context.log_css_error(start, error);
313 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
314 } else {
315 Ok(rule)
316 }
317}
318
319impl ToCssWithGuard for CounterStyleRule {
320 fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
321 dest.write_str("@counter-style ")?;
322 self.name.to_css(&mut CssWriter::new(dest))?;
323 dest.write_str(" { ")?;
324 self.descriptors.to_css(&mut CssWriter::new(dest))?;
325 dest.write_char('}')
326 }
327}
328
329impl CounterStyleRule {
332 fn empty(name: CustomIdent, source_location: SourceLocation) -> Self {
333 Self {
334 name: name,
335 generation: Wrapping(0),
336 descriptors: Descriptors::default(),
337 source_location,
338 }
339 }
340
341 pub fn descriptors(&self) -> &Descriptors {
344 &self.descriptors
345 }
346
347 pub fn set_descriptor<'i>(
349 &mut self,
350 id: DescriptorId,
351 context: &ParserContext,
352 input: &mut Parser<'i, '_>,
353 ) -> Result<bool, ParseError<'i>> {
354 if id == DescriptorId::AdditiveSymbols
357 && matches!(*self.resolved_system(), System::Extends(..))
358 {
359 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
361 }
362 let changed = match id {
363 DescriptorId::System => {
364 let system = input.parse_entirely(|i| System::parse(context, i))?;
365 if !self.check_system(&system) {
366 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
367 }
368 let new = Some(system);
369 if self.descriptors.system == new {
370 return Ok(false);
371 }
372 self.descriptors.system = new;
373 true
374 },
375 DescriptorId::Symbols => {
376 let symbols = input.parse_entirely(|i| Symbols::parse(context, i))?;
377 if !self.check_symbols(&symbols) {
378 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
379 }
380 let new = Some(symbols);
381 if self.descriptors.symbols == new {
382 return Ok(false);
383 }
384 self.descriptors.symbols = new;
385 true
386 },
387 _ => self.descriptors.set(id, context, input)?,
388 };
389 if changed {
390 self.generation += Wrapping(1);
391 }
392 Ok(changed)
393 }
394
395 fn check_system(&self, value: &System) -> bool {
398 mem::discriminant(self.resolved_system()) == mem::discriminant(value)
399 }
400
401 fn check_symbols(&self, value: &Symbols) -> bool {
402 match *self.resolved_system() {
403 System::Numeric | System::Alphabetic => value.0.len() >= 2,
405 System::Extends(_) => false,
407 _ => true,
408 }
409 }
410
411 pub fn name(&self) -> &CustomIdent {
413 &self.name
414 }
415
416 pub fn set_name(&mut self, name: CustomIdent) {
419 debug_assert!(is_valid_name_definition(&name));
420 self.name = name;
421 }
422
423 pub fn generation(&self) -> u32 {
425 self.generation.0
426 }
427
428 pub fn resolved_system(&self) -> &System {
431 match self.descriptors.system {
432 Some(ref system) => system,
433 None => &System::Symbolic,
434 }
435 }
436}
437
438#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
440pub enum System {
441 Cyclic,
443 Numeric,
445 Alphabetic,
447 Symbolic,
449 Additive,
451 Fixed {
453 first_symbol_value: Option<Integer>,
455 },
456 Extends(CustomIdent),
458}
459
460impl Parse for System {
461 fn parse<'i, 't>(
462 context: &ParserContext,
463 input: &mut Parser<'i, 't>,
464 ) -> Result<Self, ParseError<'i>> {
465 try_match_ident_ignore_ascii_case! { input,
466 "cyclic" => Ok(System::Cyclic),
467 "numeric" => Ok(System::Numeric),
468 "alphabetic" => Ok(System::Alphabetic),
469 "symbolic" => Ok(System::Symbolic),
470 "additive" => Ok(System::Additive),
471 "fixed" => {
472 let first_symbol_value = input.try_parse(|i| Integer::parse(context, i)).ok();
473 Ok(System::Fixed { first_symbol_value })
474 },
475 "extends" => {
476 let other = parse_counter_style_name(input)?;
477 Ok(System::Extends(other))
478 },
479 }
480 }
481}
482
483impl ToCss for System {
484 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
485 where
486 W: Write,
487 {
488 match self {
489 System::Cyclic => dest.write_str("cyclic"),
490 System::Numeric => dest.write_str("numeric"),
491 System::Alphabetic => dest.write_str("alphabetic"),
492 System::Symbolic => dest.write_str("symbolic"),
493 System::Additive => dest.write_str("additive"),
494 System::Fixed { first_symbol_value } => {
495 if let Some(value) = first_symbol_value {
496 dest.write_str("fixed ")?;
497 value.to_css(dest)
498 } else {
499 dest.write_str("fixed")
500 }
501 },
502 System::Extends(ref other) => {
503 dest.write_str("extends ")?;
504 other.to_css(dest)
505 },
506 }
507 }
508}
509
510#[derive(
512 Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem,
513)]
514#[repr(u8)]
515pub enum Symbol {
516 String(crate::OwnedStr),
518 Ident(CustomIdent),
520 }
524
525impl Parse for Symbol {
526 fn parse<'i, 't>(
527 _context: &ParserContext,
528 input: &mut Parser<'i, 't>,
529 ) -> Result<Self, ParseError<'i>> {
530 let location = input.current_source_location();
531 match *input.next()? {
532 Token::QuotedString(ref s) => Ok(Symbol::String(s.as_ref().to_owned().into())),
533 Token::Ident(ref s) => Ok(Symbol::Ident(CustomIdent::from_ident(location, s, &[])?)),
534 ref t => Err(location.new_unexpected_token_error(t.clone())),
535 }
536 }
537}
538
539impl Symbol {
540 pub fn is_allowed_in_symbols(&self) -> bool {
542 match self {
543 &Symbol::Ident(_) => false,
545 _ => true,
546 }
547 }
548}
549
550#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
552pub struct Negative(pub Symbol, pub Option<Symbol>);
553
554impl Parse for Negative {
555 fn parse<'i, 't>(
556 context: &ParserContext,
557 input: &mut Parser<'i, 't>,
558 ) -> Result<Self, ParseError<'i>> {
559 Ok(Negative(
560 Symbol::parse(context, input)?,
561 input.try_parse(|input| Symbol::parse(context, input)).ok(),
562 ))
563 }
564}
565
566#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
568pub struct CounterRange {
569 pub start: CounterBound,
571 pub end: CounterBound,
573}
574
575#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
579#[css(comma)]
580pub struct CounterRanges(#[css(iterable, if_empty = "auto")] pub crate::OwnedSlice<CounterRange>);
581
582#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
584pub enum CounterBound {
585 Integer(Integer),
587 Infinite,
589}
590
591impl Parse for CounterRanges {
592 fn parse<'i, 't>(
593 context: &ParserContext,
594 input: &mut Parser<'i, 't>,
595 ) -> Result<Self, ParseError<'i>> {
596 if input
597 .try_parse(|input| input.expect_ident_matching("auto"))
598 .is_ok()
599 {
600 return Ok(CounterRanges(Default::default()));
601 }
602
603 let ranges = input.parse_comma_separated(|input| {
604 let start = parse_bound(context, input)?;
605 let end = parse_bound(context, input)?;
606 if let (CounterBound::Integer(ref s), CounterBound::Integer(ref e)) = (&start, &end) {
607 let s = s.resolve();
610 let e = e.resolve();
611 if s.is_none() || e.is_none() || s.unwrap() > e.unwrap() {
612 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
613 }
614 }
615 Ok(CounterRange { start, end })
616 })?;
617
618 Ok(CounterRanges(ranges.into()))
619 }
620}
621
622fn parse_bound<'i, 't>(
623 context: &ParserContext,
624 input: &mut Parser<'i, 't>,
625) -> Result<CounterBound, ParseError<'i>> {
626 if let Ok(integer) = input.try_parse(|input| Integer::parse(context, input)) {
627 return Ok(CounterBound::Integer(integer));
628 }
629 input.expect_ident_matching("infinite")?;
630 Ok(CounterBound::Infinite)
631}
632
633#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
635pub struct Pad(pub Integer, pub Symbol);
636
637impl Parse for Pad {
638 fn parse<'i, 't>(
639 context: &ParserContext,
640 input: &mut Parser<'i, 't>,
641 ) -> Result<Self, ParseError<'i>> {
642 let pad_with = input.try_parse(|input| Symbol::parse(context, input));
643 let min_length = Integer::parse_non_negative(context, input)?;
644 if min_length.resolve().is_none() {
647 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
648 }
649 let pad_with = pad_with.or_else(|_| Symbol::parse(context, input))?;
650 Ok(Pad(min_length, pad_with))
651 }
652}
653
654#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
656pub struct Fallback(pub CustomIdent);
657
658impl Parse for Fallback {
659 fn parse<'i, 't>(
660 _context: &ParserContext,
661 input: &mut Parser<'i, 't>,
662 ) -> Result<Self, ParseError<'i>> {
663 Ok(Fallback(parse_counter_style_name(input)?))
664 }
665}
666
667#[derive(
669 Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem,
670)]
671#[repr(C)]
672pub struct Symbols(
673 #[css(iterable)]
674 #[ignore_malloc_size_of = "Arc"]
675 pub crate::ArcSlice<Symbol>,
676);
677
678impl Parse for Symbols {
679 fn parse<'i, 't>(
680 context: &ParserContext,
681 input: &mut Parser<'i, 't>,
682 ) -> Result<Self, ParseError<'i>> {
683 let mut symbols = smallvec::SmallVec::<[_; 5]>::new();
684 while let Ok(s) = input.try_parse(|input| Symbol::parse(context, input)) {
685 symbols.push(s);
686 }
687 if symbols.is_empty() {
688 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
689 }
690 Ok(Symbols(crate::ArcSlice::from_iter(symbols.drain(..))))
691 }
692}
693
694#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
696#[css(comma)]
697pub struct AdditiveSymbols(#[css(iterable)] pub crate::OwnedSlice<AdditiveTuple>);
698
699impl Parse for AdditiveSymbols {
700 fn parse<'i, 't>(
701 context: &ParserContext,
702 input: &mut Parser<'i, 't>,
703 ) -> Result<Self, ParseError<'i>> {
704 let tuples = Vec::<AdditiveTuple>::parse(context, input)?;
705 if tuples.iter().any(|t| t.weight.resolve().is_none()) {
706 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
707 }
708 if tuples
710 .windows(2)
711 .any(|window| window[0].weight.get() <= window[1].weight.get())
712 {
713 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
714 }
715 Ok(AdditiveSymbols(tuples.into()))
716 }
717}
718
719#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
721pub struct AdditiveTuple {
722 pub weight: Integer,
724 pub symbol: Symbol,
726}
727
728impl OneOrMoreSeparated for AdditiveTuple {
729 type S = Comma;
730}
731
732impl Parse for AdditiveTuple {
733 fn parse<'i, 't>(
734 context: &ParserContext,
735 input: &mut Parser<'i, 't>,
736 ) -> Result<Self, ParseError<'i>> {
737 let symbol = input.try_parse(|input| Symbol::parse(context, input));
738 let weight = Integer::parse_non_negative(context, input)?;
739 let symbol = symbol.or_else(|_| Symbol::parse(context, input))?;
740 Ok(Self { weight, symbol })
741 }
742}
743
744#[derive(Clone, Debug, MallocSizeOf, ToCss, PartialEq, ToShmem)]
746pub enum SpeakAs {
747 Auto,
749 Bullets,
751 Numbers,
753 Words,
755 Other(CustomIdent),
759}
760
761impl Parse for SpeakAs {
762 fn parse<'i, 't>(
763 _context: &ParserContext,
764 input: &mut Parser<'i, 't>,
765 ) -> Result<Self, ParseError<'i>> {
766 let mut is_spell_out = false;
767 let result = input.try_parse(|input| {
768 let ident = input.expect_ident().map_err(|_| ())?;
769 match_ignore_ascii_case! { &*ident,
770 "auto" => Ok(SpeakAs::Auto),
771 "bullets" => Ok(SpeakAs::Bullets),
772 "numbers" => Ok(SpeakAs::Numbers),
773 "words" => Ok(SpeakAs::Words),
774 "spell-out" => {
775 is_spell_out = true;
776 Err(())
777 },
778 _ => Err(()),
779 }
780 });
781 if is_spell_out {
782 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
785 }
786 result.or_else(|_| Ok(SpeakAs::Other(parse_counter_style_name(input)?)))
787 }
788}