1use crate::Atom;
8use crate::derives::*;
9use crate::parser::{Parse, ParserContext};
10use crate::properties::{NonCustomPropertyId, PropertyId, ShorthandId};
11use crate::typed_om::ToTyped;
12use crate::values::generics::animation as generics;
13use crate::values::generics::position::{IsTreeScoped, TreeScoped};
14use crate::values::specified::{LengthPercentage, NonNegativeNumber, Time};
15use crate::values::{AtomIdent, CustomIdent, DashedIdent, KeyframesName};
16use cssparser::{Parser, match_ignore_ascii_case};
17use std::fmt::{self, Write};
18use style_traits::{
19 CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss,
20};
21
22#[derive(
25 Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
26)]
27#[repr(u8)]
28pub enum TransitionProperty {
29 NonCustom(NonCustomPropertyId),
31 Custom(Atom),
33 Unsupported(CustomIdent),
36}
37
38impl ToCss for TransitionProperty {
39 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
40 where
41 W: Write,
42 {
43 match *self {
44 TransitionProperty::NonCustom(ref id) => id.to_css(dest),
45 TransitionProperty::Custom(ref name) => {
46 dest.write_str("--")?;
47 crate::values::serialize_atom_name(name, dest)
48 },
49 TransitionProperty::Unsupported(ref i) => i.to_css(dest),
50 }
51 }
52}
53
54impl ToTyped for TransitionProperty {}
55
56impl Parse for TransitionProperty {
57 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
58 let ident = input.expect_ident()?;
59
60 let id = match PropertyId::parse_ignoring_rule_type(ident, context) {
61 Ok(id) => id,
62 Err(..) => {
63 return Ok(TransitionProperty::Unsupported(CustomIdent::from_ident(
65 ident,
66 &["none"],
67 )?));
68 },
69 };
70
71 Ok(match id {
72 PropertyId::NonCustom(id) => TransitionProperty::NonCustom(id.unaliased()),
73 PropertyId::Custom(name) => TransitionProperty::Custom(name),
74 })
75 }
76}
77
78impl SpecifiedValueInfo for TransitionProperty {
79 fn collect_completion_keywords(f: KeywordsCollectFn) {
80 f(&["all"]);
84 }
85}
86
87impl TransitionProperty {
88 #[inline]
90 pub fn none() -> Self {
91 TransitionProperty::Unsupported(CustomIdent(atom!("none")))
92 }
93
94 #[inline]
96 pub fn is_none(&self) -> bool {
97 matches!(*self, TransitionProperty::Unsupported(ref ident) if ident.0 == atom!("none"))
98 }
99
100 #[inline]
102 pub fn all() -> Self {
103 TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(ShorthandId::All))
104 }
105
106 #[inline]
108 pub fn is_all(&self) -> bool {
109 self == &TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(
110 ShorthandId::All,
111 ))
112 }
113}
114
115#[derive(
119 Clone,
120 Copy,
121 Debug,
122 MallocSizeOf,
123 Parse,
124 PartialEq,
125 SpecifiedValueInfo,
126 ToComputedValue,
127 ToCss,
128 ToResolvedValue,
129 ToShmem,
130 ToTyped,
131)]
132#[repr(u8)]
133pub enum TransitionBehavior {
134 Normal,
136 AllowDiscrete,
138}
139
140impl TransitionBehavior {
141 #[inline]
143 pub fn normal() -> Self {
144 Self::Normal
145 }
146
147 #[inline]
149 pub fn is_normal(&self) -> bool {
150 matches!(*self, Self::Normal)
151 }
152}
153
154pub type AnimationDuration = generics::GenericAnimationDuration<Time>;
156
157impl Parse for AnimationDuration {
158 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
159 if crate::pref!("layout.css.scroll-driven-animations.enabled")
160 && input.try_parse(|i| i.expect_ident_matching("auto")).is_ok()
161 {
162 return Ok(Self::auto());
163 }
164
165 Time::parse_non_negative(context, input).map(AnimationDuration::Time)
166 }
167}
168
169#[derive(
171 Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
172)]
173pub enum AnimationIterationCount {
174 Number(NonNegativeNumber),
176 Infinite,
178}
179
180impl AnimationIterationCount {
181 #[inline]
183 pub fn one() -> Self {
184 Self::Number(NonNegativeNumber::new(1.0))
185 }
186
187 #[inline]
189 pub fn is_one(&self) -> bool {
190 *self == Self::one()
191 }
192}
193
194#[derive(
196 Clone,
197 Debug,
198 Eq,
199 Hash,
200 MallocSizeOf,
201 PartialEq,
202 SpecifiedValueInfo,
203 ToComputedValue,
204 ToCss,
205 ToResolvedValue,
206 ToShmem,
207 ToTyped,
208)]
209#[value_info(other_values = "none")]
210#[repr(C)]
211pub struct AnimationName(pub KeyframesName);
212
213impl AnimationName {
214 pub fn as_atom(&self) -> Option<&Atom> {
216 if self.is_none() {
217 return None;
218 }
219 Some(self.0.as_atom())
220 }
221
222 pub fn none() -> Self {
224 AnimationName(KeyframesName::none())
225 }
226
227 pub fn is_none(&self) -> bool {
229 self.0.is_none()
230 }
231}
232
233impl Parse for AnimationName {
234 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
235 if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) {
236 return Ok(AnimationName(name));
237 }
238
239 input.expect_ident_matching("none")?;
240 Ok(AnimationName(KeyframesName::none()))
241 }
242}
243
244#[derive(
246 Copy,
247 Clone,
248 Debug,
249 MallocSizeOf,
250 Parse,
251 PartialEq,
252 SpecifiedValueInfo,
253 ToComputedValue,
254 ToCss,
255 ToResolvedValue,
256 ToShmem,
257 ToTyped,
258)]
259#[repr(u8)]
260#[allow(missing_docs)]
261pub enum AnimationDirection {
262 Normal,
263 Reverse,
264 Alternate,
265 AlternateReverse,
266}
267
268impl AnimationDirection {
269 #[inline]
271 pub fn match_keywords(name: &AnimationName) -> bool {
272 if let Some(name) = name.as_atom() {
273 #[cfg(feature = "gecko")]
274 return name.with_str(|n| Self::from_ident(n).is_ok());
275 #[cfg(feature = "servo")]
276 return Self::from_ident(name).is_ok();
277 }
278 false
279 }
280}
281
282#[derive(
284 Copy,
285 Clone,
286 Debug,
287 MallocSizeOf,
288 Parse,
289 PartialEq,
290 SpecifiedValueInfo,
291 ToComputedValue,
292 ToCss,
293 ToResolvedValue,
294 ToShmem,
295 ToTyped,
296)]
297#[repr(u8)]
298#[allow(missing_docs)]
299pub enum AnimationPlayState {
300 Running,
301 Paused,
302}
303
304impl AnimationPlayState {
305 #[inline]
307 pub fn match_keywords(name: &AnimationName) -> bool {
308 if let Some(name) = name.as_atom() {
309 #[cfg(feature = "gecko")]
310 return name.with_str(|n| Self::from_ident(n).is_ok());
311 #[cfg(feature = "servo")]
312 return Self::from_ident(name).is_ok();
313 }
314 false
315 }
316}
317
318#[derive(
320 Copy,
321 Clone,
322 Debug,
323 MallocSizeOf,
324 Parse,
325 PartialEq,
326 SpecifiedValueInfo,
327 ToComputedValue,
328 ToCss,
329 ToResolvedValue,
330 ToShmem,
331 ToTyped,
332)]
333#[repr(u8)]
334#[allow(missing_docs)]
335pub enum AnimationFillMode {
336 None,
337 Forwards,
338 Backwards,
339 Both,
340}
341
342impl AnimationFillMode {
343 #[inline]
346 pub fn match_keywords(name: &AnimationName) -> bool {
347 if let Some(name) = name.as_atom() {
348 #[cfg(feature = "gecko")]
349 return name.with_str(|n| Self::from_ident(n).is_ok());
350 #[cfg(feature = "servo")]
351 return Self::from_ident(name).is_ok();
352 }
353 false
354 }
355}
356
357#[derive(
359 Copy,
360 Clone,
361 Debug,
362 MallocSizeOf,
363 Parse,
364 PartialEq,
365 SpecifiedValueInfo,
366 ToComputedValue,
367 ToCss,
368 ToResolvedValue,
369 ToShmem,
370 ToTyped,
371)]
372#[repr(u8)]
373#[allow(missing_docs)]
374pub enum AnimationComposition {
375 Replace,
376 Add,
377 Accumulate,
378}
379
380#[derive(
384 Copy,
385 Clone,
386 Debug,
387 Eq,
388 Hash,
389 MallocSizeOf,
390 Parse,
391 PartialEq,
392 SpecifiedValueInfo,
393 ToComputedValue,
394 ToCss,
395 ToResolvedValue,
396 ToShmem,
397)]
398#[repr(u8)]
399#[derive(Default)]
400pub enum Scroller {
401 #[default]
403 Nearest,
404 Root,
406 #[css(keyword = "self")]
408 SelfElement,
409}
410
411impl Scroller {
412 #[inline]
414 fn is_default(&self) -> bool {
415 matches!(*self, Self::Nearest)
416 }
417}
418
419#[derive(
425 Copy,
426 Clone,
427 Debug,
428 Eq,
429 Hash,
430 MallocSizeOf,
431 Parse,
432 PartialEq,
433 SpecifiedValueInfo,
434 ToComputedValue,
435 ToCss,
436 ToResolvedValue,
437 ToShmem,
438 ToTyped,
439)]
440#[repr(u8)]
441#[derive(Default)]
442pub enum ScrollAxis {
443 #[default]
445 Block = 0,
446 Inline = 1,
448 X = 2,
450 Y = 3,
452}
453
454impl ScrollAxis {
455 #[inline]
457 pub fn is_default(&self) -> bool {
458 matches!(*self, Self::Block)
459 }
460}
461
462#[derive(
465 Copy,
466 Clone,
467 Debug,
468 MallocSizeOf,
469 PartialEq,
470 SpecifiedValueInfo,
471 ToComputedValue,
472 ToCss,
473 ToResolvedValue,
474 ToShmem,
475)]
476#[css(function = "scroll")]
477#[repr(C)]
478pub struct ScrollFunction {
479 #[css(skip_if = "Scroller::is_default")]
481 pub scroller: Scroller,
482 #[css(skip_if = "ScrollAxis::is_default")]
484 pub axis: ScrollAxis,
485}
486
487impl ScrollFunction {
488 fn parse_arguments(input: &mut Parser) -> Result<Self, ParseError> {
490 let mut scroller = None;
493 let mut axis = None;
494 loop {
495 if scroller.is_none() {
496 scroller = input.try_parse(Scroller::parse).ok();
497 }
498
499 if axis.is_none() {
500 axis = input.try_parse(ScrollAxis::parse).ok();
501 if axis.is_some() {
502 continue;
503 }
504 }
505 break;
506 }
507
508 Ok(Self {
509 scroller: scroller.unwrap_or_default(),
510 axis: axis.unwrap_or_default(),
511 })
512 }
513}
514
515impl generics::ViewFunction<LengthPercentage> {
516 fn parse_arguments(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
518 let mut axis = None;
521 let mut inset = None;
522 loop {
523 if axis.is_none() {
524 axis = input.try_parse(ScrollAxis::parse).ok();
525 }
526
527 if inset.is_none() {
528 inset = input
529 .try_parse(|i| ViewTimelineInset::parse(context, i))
530 .ok();
531 if inset.is_some() {
532 continue;
533 }
534 }
535 break;
536 }
537
538 Ok(Self {
539 inset: inset.unwrap_or_default(),
540 axis: axis.unwrap_or_default(),
541 })
542 }
543}
544
545pub type TimelineName = TreeScoped<TimelineIdent>;
550
551impl TimelineName {
552 pub fn none() -> Self {
554 Self::with_default_level(TimelineIdent::none())
555 }
556}
557
558#[derive(
560 Clone,
561 Debug,
562 Eq,
563 Hash,
564 MallocSizeOf,
565 PartialEq,
566 SpecifiedValueInfo,
567 ToComputedValue,
568 ToResolvedValue,
569 ToShmem,
570)]
571#[repr(C)]
572pub struct TimelineIdent(DashedIdent);
573
574impl TimelineIdent {
575 pub fn none() -> Self {
577 Self(DashedIdent::empty())
578 }
579
580 pub fn is_none(&self) -> bool {
582 self.0.is_empty()
583 }
584}
585
586impl IsTreeScoped for TimelineIdent {
587 fn is_tree_scoped(&self) -> bool {
588 !self.is_none()
589 }
590}
591
592impl Parse for TimelineIdent {
593 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
594 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
595 return Ok(Self::none());
596 }
597
598 DashedIdent::parse(context, input).map(TimelineIdent)
599 }
600}
601
602impl ToCss for TimelineIdent {
603 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
604 where
605 W: Write,
606 {
607 if self.is_none() {
608 return dest.write_str("none");
609 }
610
611 self.0.to_css(dest)
612 }
613}
614
615impl ToTyped for TimelineName {}
616
617pub type AnimationTimeline = generics::GenericAnimationTimeline<LengthPercentage>;
619
620impl Parse for AnimationTimeline {
621 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
622 use crate::values::generics::animation::ViewFunction;
623
624 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
628 return Ok(Self::Auto);
629 }
630
631 if let Ok(name) = input.try_parse(|i| TimelineName::parse(context, i)) {
633 return Ok(AnimationTimeline::Timeline(name));
634 }
635
636 let function = input.expect_function()?.clone();
638 input.parse_nested_block(move |i| {
639 match_ignore_ascii_case! { &function,
640 "scroll" => ScrollFunction::parse_arguments(i).map(Self::Scroll),
641 "view" => ViewFunction::parse_arguments(context, i).map(Self::View),
642 _ => {
643 Err(ParseError::custom(
644 StyleParseErrorKind::UnexpectedFunction
645 ))
646 },
647 }
648 })
649 }
650}
651
652pub type ViewTimelineInset = generics::GenericViewTimelineInset<LengthPercentage>;
654
655impl Parse for ViewTimelineInset {
656 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
657 use crate::values::specified::LengthPercentageOrAuto;
658
659 let start = LengthPercentageOrAuto::parse(context, input)?;
660 let end = match input.try_parse(|input| LengthPercentageOrAuto::parse(context, input)) {
661 Ok(end) => end,
662 Err(_) => start.clone(),
663 };
664
665 Ok(Self { start, end })
666 }
667}
668
669#[derive(
675 Clone,
676 Debug,
677 Eq,
678 Hash,
679 PartialEq,
680 MallocSizeOf,
681 SpecifiedValueInfo,
682 ToCss,
683 ToComputedValue,
684 ToResolvedValue,
685 ToShmem,
686 ToTyped,
687)]
688#[repr(transparent)]
689#[typed(todo_derive_fields)]
690#[value_info(other_values = "none, match-element")]
691pub struct ViewTransitionNameKeyword(AtomIdent);
692
693impl ViewTransitionNameKeyword {
694 pub fn none() -> Self {
696 Self(AtomIdent::new(atom!("none")))
697 }
698}
699
700impl IsTreeScoped for ViewTransitionNameKeyword {
701 fn is_tree_scoped(&self) -> bool {
702 self.0.0 != atom!("none")
703 }
704}
705
706impl Parse for ViewTransitionNameKeyword {
707 fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
708 let ident = input.expect_ident()?;
709 if ident.eq_ignore_ascii_case("none") {
710 return Ok(Self::none());
711 }
712
713 if ident.eq_ignore_ascii_case("match-element") {
714 return Ok(Self(AtomIdent::new(atom!("match-element"))));
715 }
716
717 CustomIdent::from_ident(ident, &["auto"]).map(|i| Self(AtomIdent::new(i.0)))
720 }
721}
722
723pub type ViewTransitionName = TreeScoped<ViewTransitionNameKeyword>;
725
726impl ViewTransitionName {
727 pub fn none() -> Self {
729 Self::with_default_level(ViewTransitionNameKeyword::none())
730 }
731}
732
733#[derive(
739 Clone,
740 Debug,
741 Default,
742 Eq,
743 Hash,
744 PartialEq,
745 MallocSizeOf,
746 SpecifiedValueInfo,
747 ToComputedValue,
748 ToCss,
749 ToResolvedValue,
750 ToShmem,
751 ToTyped,
752)]
753#[repr(C)]
754#[value_info(other_values = "none")]
755pub struct ViewTransitionClassList(
756 #[css(iterable, if_empty = "none")]
757 #[ignore_malloc_size_of = "Arc"]
758 crate::ArcSlice<CustomIdent>,
759);
760
761impl IsTreeScoped for ViewTransitionClassList {
762 fn is_tree_scoped(&self) -> bool {
763 !self.is_none()
764 }
765}
766
767impl ViewTransitionClassList {
768 pub fn none() -> Self {
770 Self(Default::default())
771 }
772
773 pub fn is_none(&self) -> bool {
775 self.0.is_empty()
776 }
777
778 pub fn iter(&self) -> impl Iterator<Item = &CustomIdent> {
780 self.0.iter()
781 }
782}
783
784impl Parse for ViewTransitionClassList {
785 fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
786 use style_traits::{Separator, Space};
787
788 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
789 return Ok(Self::none());
790 }
791
792 Ok(Self(crate::ArcSlice::from_iter(
793 Space::parse(input, |i| CustomIdent::parse(i, &["none"]))?.into_iter(),
794 )))
795 }
796}
797
798pub type ViewTransitionClass = TreeScoped<ViewTransitionClassList>;
800
801impl ViewTransitionClass {
802 pub fn none() -> Self {
804 Self::with_default_level(ViewTransitionClassList::none())
805 }
806}
807
808#[derive(
815 Copy,
816 Clone,
817 Debug,
818 Eq,
819 MallocSizeOf,
820 Parse,
821 PartialEq,
822 SpecifiedValueInfo,
823 ToComputedValue,
824 ToCss,
825 ToResolvedValue,
826 ToShmem,
827 ToTyped,
828)]
829#[repr(u8)]
830pub enum TimelineRangeName {
831 #[css(skip)]
833 Normal,
834 #[css(skip)]
836 None,
837 Cover,
839 Contain,
842 Entry,
845 Exit,
848 EntryCrossing,
850 ExitCrossing,
852 Scroll,
855}
856
857impl TimelineRangeName {
858 #[inline]
860 pub fn is_normal(&self) -> bool {
861 matches!(*self, Self::Normal)
862 }
863
864 #[inline]
866 pub fn is_none(&self) -> bool {
867 matches!(*self, Self::None)
868 }
869}
870
871pub type AnimationRangeValue = generics::GenericAnimationRangeValue<LengthPercentage>;
873
874fn parse_animation_range(
875 context: &ParserContext,
876 input: &mut Parser,
877 default: LengthPercentage,
878) -> Result<AnimationRangeValue, ParseError> {
879 if input
880 .try_parse(|i| i.expect_ident_matching("normal"))
881 .is_ok()
882 {
883 return Ok(AnimationRangeValue::normal(default));
884 }
885
886 if let Ok(lp) = input.try_parse(|i| LengthPercentage::parse(context, i)) {
887 return Ok(AnimationRangeValue::length_percentage(lp));
888 }
889
890 let name = TimelineRangeName::parse(input)?;
891 let lp = input
892 .try_parse(|i| LengthPercentage::parse(context, i))
893 .unwrap_or(default);
894 Ok(AnimationRangeValue::new(name, lp))
895}
896
897pub type AnimationRangeStart = generics::GenericAnimationRangeStart<LengthPercentage>;
899
900impl Parse for AnimationRangeStart {
901 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
902 parse_animation_range(context, input, LengthPercentage::zero_percent()).map(Self)
903 }
904}
905
906pub type AnimationRangeEnd = generics::GenericAnimationRangeEnd<LengthPercentage>;
908
909impl Parse for AnimationRangeEnd {
910 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
911 parse_animation_range(context, input, LengthPercentage::hundred_percent()).map(Self)
912 }
913}