1use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::properties::{NonCustomPropertyId, PropertyId, ShorthandId};
10use crate::values::generics::animation as generics;
11use crate::values::generics::position::{IsTreeScoped, TreeScoped};
12use crate::values::specified::{LengthPercentage, NonNegativeNumber, Time};
13use crate::values::{AtomIdent, CustomIdent, DashedIdent, KeyframesName};
14use crate::Atom;
15use cssparser::{match_ignore_ascii_case, Parser};
16use std::fmt::{self, Write};
17use style_traits::{
18 CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss,
19 ToTyped,
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<'i, 't>(
58 context: &ParserContext,
59 input: &mut Parser<'i, 't>,
60 ) -> Result<Self, ParseError<'i>> {
61 let location = input.current_source_location();
62 let ident = input.expect_ident()?;
63
64 let id = match PropertyId::parse_ignoring_rule_type(&ident, context) {
65 Ok(id) => id,
66 Err(..) => {
67 return Ok(TransitionProperty::Unsupported(CustomIdent::from_ident(
69 location,
70 ident,
71 &["none"],
72 )?));
73 },
74 };
75
76 Ok(match id {
77 PropertyId::NonCustom(id) => TransitionProperty::NonCustom(id.unaliased()),
78 PropertyId::Custom(name) => TransitionProperty::Custom(name),
79 })
80 }
81}
82
83impl SpecifiedValueInfo for TransitionProperty {
84 fn collect_completion_keywords(f: KeywordsCollectFn) {
85 f(&["all"]);
89 }
90}
91
92impl TransitionProperty {
93 #[inline]
95 pub fn none() -> Self {
96 TransitionProperty::Unsupported(CustomIdent(atom!("none")))
97 }
98
99 #[inline]
101 pub fn is_none(&self) -> bool {
102 matches!(*self, TransitionProperty::Unsupported(ref ident) if ident.0 == atom!("none"))
103 }
104
105 #[inline]
107 pub fn all() -> Self {
108 TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(ShorthandId::All))
109 }
110
111 #[inline]
113 pub fn is_all(&self) -> bool {
114 self == &TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(
115 ShorthandId::All,
116 ))
117 }
118}
119
120#[derive(
124 Clone,
125 Copy,
126 Debug,
127 MallocSizeOf,
128 Parse,
129 PartialEq,
130 SpecifiedValueInfo,
131 ToComputedValue,
132 ToCss,
133 ToResolvedValue,
134 ToShmem,
135 ToTyped,
136)]
137#[repr(u8)]
138pub enum TransitionBehavior {
139 Normal,
141 AllowDiscrete,
143}
144
145impl TransitionBehavior {
146 #[inline]
148 pub fn normal() -> Self {
149 Self::Normal
150 }
151
152 #[inline]
154 pub fn is_normal(&self) -> bool {
155 matches!(*self, Self::Normal)
156 }
157}
158
159pub type AnimationDuration = generics::GenericAnimationDuration<Time>;
161
162impl Parse for AnimationDuration {
163 fn parse<'i, 't>(
164 context: &ParserContext,
165 input: &mut Parser<'i, 't>,
166 ) -> Result<Self, ParseError<'i>> {
167 if static_prefs::pref!("layout.css.scroll-driven-animations.enabled")
168 && input.try_parse(|i| i.expect_ident_matching("auto")).is_ok()
169 {
170 return Ok(Self::auto());
171 }
172
173 Time::parse_non_negative(context, input).map(AnimationDuration::Time)
174 }
175}
176
177#[derive(
179 Copy, Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
180)]
181pub enum AnimationIterationCount {
182 Number(NonNegativeNumber),
184 Infinite,
186}
187
188impl AnimationIterationCount {
189 #[inline]
191 pub fn one() -> Self {
192 Self::Number(NonNegativeNumber::new(1.0))
193 }
194
195 #[inline]
197 pub fn is_one(&self) -> bool {
198 *self == Self::one()
199 }
200}
201
202#[derive(
204 Clone,
205 Debug,
206 Eq,
207 Hash,
208 MallocSizeOf,
209 PartialEq,
210 SpecifiedValueInfo,
211 ToComputedValue,
212 ToCss,
213 ToResolvedValue,
214 ToShmem,
215 ToTyped,
216)]
217#[value_info(other_values = "none")]
218#[repr(C)]
219pub struct AnimationName(pub KeyframesName);
220
221impl AnimationName {
222 pub fn as_atom(&self) -> Option<&Atom> {
224 if self.is_none() {
225 return None;
226 }
227 Some(self.0.as_atom())
228 }
229
230 pub fn none() -> Self {
232 AnimationName(KeyframesName::none())
233 }
234
235 pub fn is_none(&self) -> bool {
237 self.0.is_none()
238 }
239}
240
241impl Parse for AnimationName {
242 fn parse<'i, 't>(
243 context: &ParserContext,
244 input: &mut Parser<'i, 't>,
245 ) -> Result<Self, ParseError<'i>> {
246 if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) {
247 return Ok(AnimationName(name));
248 }
249
250 input.expect_ident_matching("none")?;
251 Ok(AnimationName(KeyframesName::none()))
252 }
253}
254
255#[derive(
257 Copy,
258 Clone,
259 Debug,
260 MallocSizeOf,
261 Parse,
262 PartialEq,
263 SpecifiedValueInfo,
264 ToComputedValue,
265 ToCss,
266 ToResolvedValue,
267 ToShmem,
268 ToTyped,
269)]
270#[repr(u8)]
271#[allow(missing_docs)]
272pub enum AnimationDirection {
273 Normal,
274 Reverse,
275 Alternate,
276 AlternateReverse,
277}
278
279impl AnimationDirection {
280 #[inline]
282 pub fn match_keywords(name: &AnimationName) -> bool {
283 if let Some(name) = name.as_atom() {
284 #[cfg(feature = "gecko")]
285 return name.with_str(|n| Self::from_ident(n).is_ok());
286 #[cfg(feature = "servo")]
287 return Self::from_ident(name).is_ok();
288 }
289 false
290 }
291}
292
293#[derive(
295 Copy,
296 Clone,
297 Debug,
298 MallocSizeOf,
299 Parse,
300 PartialEq,
301 SpecifiedValueInfo,
302 ToComputedValue,
303 ToCss,
304 ToResolvedValue,
305 ToShmem,
306 ToTyped,
307)]
308#[repr(u8)]
309#[allow(missing_docs)]
310pub enum AnimationPlayState {
311 Running,
312 Paused,
313}
314
315impl AnimationPlayState {
316 #[inline]
318 pub fn match_keywords(name: &AnimationName) -> bool {
319 if let Some(name) = name.as_atom() {
320 #[cfg(feature = "gecko")]
321 return name.with_str(|n| Self::from_ident(n).is_ok());
322 #[cfg(feature = "servo")]
323 return Self::from_ident(name).is_ok();
324 }
325 false
326 }
327}
328
329#[derive(
331 Copy,
332 Clone,
333 Debug,
334 MallocSizeOf,
335 Parse,
336 PartialEq,
337 SpecifiedValueInfo,
338 ToComputedValue,
339 ToCss,
340 ToResolvedValue,
341 ToShmem,
342 ToTyped,
343)]
344#[repr(u8)]
345#[allow(missing_docs)]
346pub enum AnimationFillMode {
347 None,
348 Forwards,
349 Backwards,
350 Both,
351}
352
353impl AnimationFillMode {
354 #[inline]
357 pub fn match_keywords(name: &AnimationName) -> bool {
358 if let Some(name) = name.as_atom() {
359 #[cfg(feature = "gecko")]
360 return name.with_str(|n| Self::from_ident(n).is_ok());
361 #[cfg(feature = "servo")]
362 return Self::from_ident(name).is_ok();
363 }
364 false
365 }
366}
367
368#[derive(
370 Copy,
371 Clone,
372 Debug,
373 MallocSizeOf,
374 Parse,
375 PartialEq,
376 SpecifiedValueInfo,
377 ToComputedValue,
378 ToCss,
379 ToResolvedValue,
380 ToShmem,
381 ToTyped,
382)]
383#[repr(u8)]
384#[allow(missing_docs)]
385pub enum AnimationComposition {
386 Replace,
387 Add,
388 Accumulate,
389}
390
391#[derive(
395 Copy,
396 Clone,
397 Debug,
398 Eq,
399 Hash,
400 MallocSizeOf,
401 Parse,
402 PartialEq,
403 SpecifiedValueInfo,
404 ToComputedValue,
405 ToCss,
406 ToResolvedValue,
407 ToShmem,
408)]
409#[repr(u8)]
410pub enum Scroller {
411 Nearest,
413 Root,
415 #[css(keyword = "self")]
417 SelfElement,
418}
419
420impl Scroller {
421 #[inline]
423 fn is_default(&self) -> bool {
424 matches!(*self, Self::Nearest)
425 }
426}
427
428impl Default for Scroller {
429 fn default() -> Self {
430 Self::Nearest
431 }
432}
433
434#[derive(
440 Copy,
441 Clone,
442 Debug,
443 Eq,
444 Hash,
445 MallocSizeOf,
446 Parse,
447 PartialEq,
448 SpecifiedValueInfo,
449 ToComputedValue,
450 ToCss,
451 ToResolvedValue,
452 ToShmem,
453 ToTyped,
454)]
455#[repr(u8)]
456pub enum ScrollAxis {
457 Block = 0,
459 Inline = 1,
461 X = 2,
463 Y = 3,
465}
466
467impl ScrollAxis {
468 #[inline]
470 pub fn is_default(&self) -> bool {
471 matches!(*self, Self::Block)
472 }
473}
474
475impl Default for ScrollAxis {
476 fn default() -> Self {
477 Self::Block
478 }
479}
480
481#[derive(
484 Copy,
485 Clone,
486 Debug,
487 MallocSizeOf,
488 PartialEq,
489 SpecifiedValueInfo,
490 ToComputedValue,
491 ToCss,
492 ToResolvedValue,
493 ToShmem,
494)]
495#[css(function = "scroll")]
496#[repr(C)]
497pub struct ScrollFunction {
498 #[css(skip_if = "Scroller::is_default")]
500 pub scroller: Scroller,
501 #[css(skip_if = "ScrollAxis::is_default")]
503 pub axis: ScrollAxis,
504}
505
506impl ScrollFunction {
507 fn parse_arguments<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
509 let mut scroller = None;
512 let mut axis = None;
513 loop {
514 if scroller.is_none() {
515 scroller = input.try_parse(Scroller::parse).ok();
516 }
517
518 if axis.is_none() {
519 axis = input.try_parse(ScrollAxis::parse).ok();
520 if axis.is_some() {
521 continue;
522 }
523 }
524 break;
525 }
526
527 Ok(Self {
528 scroller: scroller.unwrap_or_default(),
529 axis: axis.unwrap_or_default(),
530 })
531 }
532}
533
534impl generics::ViewFunction<LengthPercentage> {
535 fn parse_arguments<'i, 't>(
537 context: &ParserContext,
538 input: &mut Parser<'i, 't>,
539 ) -> Result<Self, ParseError<'i>> {
540 let mut axis = None;
543 let mut inset = None;
544 loop {
545 if axis.is_none() {
546 axis = input.try_parse(ScrollAxis::parse).ok();
547 }
548
549 if inset.is_none() {
550 inset = input
551 .try_parse(|i| ViewTimelineInset::parse(context, i))
552 .ok();
553 if inset.is_some() {
554 continue;
555 }
556 }
557 break;
558 }
559
560 Ok(Self {
561 inset: inset.unwrap_or_default(),
562 axis: axis.unwrap_or_default(),
563 })
564 }
565}
566
567pub type TimelineName = TreeScoped<TimelineIdent>;
572
573impl TimelineName {
574 pub fn none() -> Self {
576 Self::with_default_level(TimelineIdent::none())
577 }
578}
579
580#[derive(
582 Clone,
583 Debug,
584 Eq,
585 Hash,
586 MallocSizeOf,
587 PartialEq,
588 SpecifiedValueInfo,
589 ToComputedValue,
590 ToResolvedValue,
591 ToShmem,
592)]
593#[repr(C)]
594pub struct TimelineIdent(DashedIdent);
595
596impl TimelineIdent {
597 pub fn none() -> Self {
599 Self(DashedIdent::empty())
600 }
601
602 pub fn is_none(&self) -> bool {
604 self.0.is_empty()
605 }
606}
607
608impl IsTreeScoped for TimelineIdent {
609 fn is_tree_scoped(&self) -> bool {
610 !self.is_none()
611 }
612}
613
614impl Parse for TimelineIdent {
615 fn parse<'i, 't>(
616 context: &ParserContext,
617 input: &mut Parser<'i, 't>,
618 ) -> Result<Self, ParseError<'i>> {
619 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
620 return Ok(Self::none());
621 }
622
623 DashedIdent::parse(context, input).map(TimelineIdent)
624 }
625}
626
627impl ToCss for TimelineIdent {
628 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
629 where
630 W: Write,
631 {
632 if self.is_none() {
633 return dest.write_str("none");
634 }
635
636 self.0.to_css(dest)
637 }
638}
639
640impl ToTyped for TimelineName {}
641
642pub type AnimationTimeline = generics::GenericAnimationTimeline<LengthPercentage>;
644
645impl Parse for AnimationTimeline {
646 fn parse<'i, 't>(
647 context: &ParserContext,
648 input: &mut Parser<'i, 't>,
649 ) -> Result<Self, ParseError<'i>> {
650 use crate::values::generics::animation::ViewFunction;
651
652 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
656 return Ok(Self::Auto);
657 }
658
659 if let Ok(name) = input.try_parse(|i| TimelineName::parse(context, i)) {
661 return Ok(AnimationTimeline::Timeline(name));
662 }
663
664 let location = input.current_source_location();
666 let function = input.expect_function()?.clone();
667 input.parse_nested_block(move |i| {
668 match_ignore_ascii_case! { &function,
669 "scroll" => ScrollFunction::parse_arguments(i).map(Self::Scroll),
670 "view" => ViewFunction::parse_arguments(context, i).map(Self::View),
671 _ => {
672 Err(location.new_custom_error(
673 StyleParseErrorKind::UnexpectedFunction(function.clone())
674 ))
675 },
676 }
677 })
678 }
679}
680
681pub type ViewTimelineInset = generics::GenericViewTimelineInset<LengthPercentage>;
683
684impl Parse for ViewTimelineInset {
685 fn parse<'i, 't>(
686 context: &ParserContext,
687 input: &mut Parser<'i, 't>,
688 ) -> Result<Self, ParseError<'i>> {
689 use crate::values::specified::LengthPercentageOrAuto;
690
691 let start = LengthPercentageOrAuto::parse(context, input)?;
692 let end = match input.try_parse(|input| LengthPercentageOrAuto::parse(context, input)) {
693 Ok(end) => end,
694 Err(_) => start.clone(),
695 };
696
697 Ok(Self { start, end })
698 }
699}
700
701#[derive(
707 Clone,
708 Debug,
709 Eq,
710 Hash,
711 PartialEq,
712 MallocSizeOf,
713 SpecifiedValueInfo,
714 ToCss,
715 ToComputedValue,
716 ToResolvedValue,
717 ToShmem,
718 ToTyped,
719)]
720#[repr(transparent)]
721#[typed(todo_derive_fields)]
722#[value_info(other_values = "none, match-element")]
723pub struct ViewTransitionNameKeyword(AtomIdent);
724
725impl ViewTransitionNameKeyword {
726 pub fn none() -> Self {
728 Self(AtomIdent::new(atom!("none")))
729 }
730}
731
732impl IsTreeScoped for ViewTransitionNameKeyword {
733 fn is_tree_scoped(&self) -> bool {
734 self.0 .0 != atom!("none")
735 }
736}
737
738impl Parse for ViewTransitionNameKeyword {
739 fn parse<'i, 't>(
740 _: &ParserContext,
741 input: &mut Parser<'i, 't>,
742 ) -> Result<Self, ParseError<'i>> {
743 let location = input.current_source_location();
744 let ident = input.expect_ident()?;
745 if ident.eq_ignore_ascii_case("none") {
746 return Ok(Self::none());
747 }
748
749 if ident.eq_ignore_ascii_case("match-element") {
750 return Ok(Self(AtomIdent::new(atom!("match-element"))));
751 }
752
753 CustomIdent::from_ident(location, ident, &["auto"]).map(|i| Self(AtomIdent::new(i.0)))
756 }
757}
758
759pub type ViewTransitionName = TreeScoped<ViewTransitionNameKeyword>;
761
762impl ViewTransitionName {
763 pub fn none() -> Self {
765 Self::with_default_level(ViewTransitionNameKeyword::none())
766 }
767}
768
769#[derive(
775 Clone,
776 Debug,
777 Default,
778 Eq,
779 Hash,
780 PartialEq,
781 MallocSizeOf,
782 SpecifiedValueInfo,
783 ToComputedValue,
784 ToCss,
785 ToResolvedValue,
786 ToShmem,
787 ToTyped,
788)]
789#[repr(C)]
790#[value_info(other_values = "none")]
791pub struct ViewTransitionClassList(
792 #[css(iterable, if_empty = "none")]
793 #[ignore_malloc_size_of = "Arc"]
794 crate::ArcSlice<CustomIdent>,
795);
796
797impl IsTreeScoped for ViewTransitionClassList {
798 fn is_tree_scoped(&self) -> bool {
799 !self.is_none()
800 }
801}
802
803impl ViewTransitionClassList {
804 pub fn none() -> Self {
806 Self(Default::default())
807 }
808
809 pub fn is_none(&self) -> bool {
811 self.0.is_empty()
812 }
813
814 pub fn iter(&self) -> impl Iterator<Item = &CustomIdent> {
816 self.0.iter()
817 }
818}
819
820impl Parse for ViewTransitionClassList {
821 fn parse<'i, 't>(
822 _: &ParserContext,
823 input: &mut Parser<'i, 't>,
824 ) -> Result<Self, ParseError<'i>> {
825 use style_traits::{Separator, Space};
826
827 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
828 return Ok(Self::none());
829 }
830
831 Ok(Self(crate::ArcSlice::from_iter(
832 Space::parse(input, |i| CustomIdent::parse(i, &["none"]))?.into_iter(),
833 )))
834 }
835}
836
837pub type ViewTransitionClass = TreeScoped<ViewTransitionClassList>;
839
840impl ViewTransitionClass {
841 pub fn none() -> Self {
843 Self::with_default_level(ViewTransitionClassList::none())
844 }
845}
846
847#[derive(
854 Copy,
855 Clone,
856 Debug,
857 Eq,
858 MallocSizeOf,
859 Parse,
860 PartialEq,
861 SpecifiedValueInfo,
862 ToComputedValue,
863 ToCss,
864 ToResolvedValue,
865 ToShmem,
866 ToTyped,
867)]
868#[repr(u8)]
869pub enum TimelineRangeName {
870 #[css(skip)]
872 Normal,
873 #[css(skip)]
875 None,
876 Cover,
878 Contain,
881 Entry,
884 Exit,
887 EntryCrossing,
889 ExitCrossing,
891 Scroll,
894}
895
896impl TimelineRangeName {
897 #[inline]
899 pub fn is_normal(&self) -> bool {
900 matches!(*self, Self::Normal)
901 }
902
903 #[inline]
905 pub fn is_none(&self) -> bool {
906 matches!(*self, Self::None)
907 }
908}
909
910pub type AnimationRangeValue = generics::GenericAnimationRangeValue<LengthPercentage>;
912
913fn parse_animation_range<'i, 't>(
914 context: &ParserContext,
915 input: &mut Parser<'i, 't>,
916 default: LengthPercentage,
917) -> Result<AnimationRangeValue, ParseError<'i>> {
918 if input
919 .try_parse(|i| i.expect_ident_matching("normal"))
920 .is_ok()
921 {
922 return Ok(AnimationRangeValue::normal(default));
923 }
924
925 if let Ok(lp) = input.try_parse(|i| LengthPercentage::parse(context, i)) {
926 return Ok(AnimationRangeValue::length_percentage(lp));
927 }
928
929 let name = TimelineRangeName::parse(input)?;
930 let lp = input
931 .try_parse(|i| LengthPercentage::parse(context, i))
932 .unwrap_or(default);
933 Ok(AnimationRangeValue::new(name, lp))
934}
935
936pub type AnimationRangeStart = generics::GenericAnimationRangeStart<LengthPercentage>;
938
939impl Parse for AnimationRangeStart {
940 fn parse<'i, 't>(
941 context: &ParserContext,
942 input: &mut Parser<'i, 't>,
943 ) -> Result<Self, ParseError<'i>> {
944 parse_animation_range(context, input, LengthPercentage::zero_percent()).map(Self)
945 }
946}
947
948pub type AnimationRangeEnd = generics::GenericAnimationRangeEnd<LengthPercentage>;
950
951impl Parse for AnimationRangeEnd {
952 fn parse<'i, 't>(
953 context: &ParserContext,
954 input: &mut Parser<'i, 't>,
955 ) -> Result<Self, ParseError<'i>> {
956 parse_animation_range(context, input, LengthPercentage::hundred_percent()).map(Self)
957 }
958}