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