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)]
181#[typed_value(derive_fields)]
182pub enum AnimationIterationCount {
183 Number(NonNegativeNumber),
185 Infinite,
187}
188
189impl AnimationIterationCount {
190 #[inline]
192 pub fn one() -> Self {
193 Self::Number(NonNegativeNumber::new(1.0))
194 }
195
196 #[inline]
198 pub fn is_one(&self) -> bool {
199 *self == Self::one()
200 }
201}
202
203#[derive(
205 Clone,
206 Debug,
207 Eq,
208 Hash,
209 MallocSizeOf,
210 PartialEq,
211 SpecifiedValueInfo,
212 ToComputedValue,
213 ToCss,
214 ToResolvedValue,
215 ToShmem,
216 ToTyped,
217)]
218#[value_info(other_values = "none")]
219#[repr(C)]
220#[typed_value(derive_fields)]
221pub struct AnimationName(pub KeyframesName);
222
223impl AnimationName {
224 pub fn as_atom(&self) -> Option<&Atom> {
226 if self.is_none() {
227 return None;
228 }
229 Some(self.0.as_atom())
230 }
231
232 pub fn none() -> Self {
234 AnimationName(KeyframesName::none())
235 }
236
237 pub fn is_none(&self) -> bool {
239 self.0.is_none()
240 }
241}
242
243impl Parse for AnimationName {
244 fn parse<'i, 't>(
245 context: &ParserContext,
246 input: &mut Parser<'i, 't>,
247 ) -> Result<Self, ParseError<'i>> {
248 if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) {
249 return Ok(AnimationName(name));
250 }
251
252 input.expect_ident_matching("none")?;
253 Ok(AnimationName(KeyframesName::none()))
254 }
255}
256
257#[derive(
259 Copy,
260 Clone,
261 Debug,
262 MallocSizeOf,
263 Parse,
264 PartialEq,
265 SpecifiedValueInfo,
266 ToComputedValue,
267 ToCss,
268 ToResolvedValue,
269 ToShmem,
270 ToTyped,
271)]
272#[repr(u8)]
273#[allow(missing_docs)]
274pub enum AnimationDirection {
275 Normal,
276 Reverse,
277 Alternate,
278 AlternateReverse,
279}
280
281impl AnimationDirection {
282 #[inline]
284 pub fn match_keywords(name: &AnimationName) -> bool {
285 if let Some(name) = name.as_atom() {
286 #[cfg(feature = "gecko")]
287 return name.with_str(|n| Self::from_ident(n).is_ok());
288 #[cfg(feature = "servo")]
289 return Self::from_ident(name).is_ok();
290 }
291 false
292 }
293}
294
295#[derive(
297 Copy,
298 Clone,
299 Debug,
300 MallocSizeOf,
301 Parse,
302 PartialEq,
303 SpecifiedValueInfo,
304 ToComputedValue,
305 ToCss,
306 ToResolvedValue,
307 ToShmem,
308 ToTyped,
309)]
310#[repr(u8)]
311#[allow(missing_docs)]
312pub enum AnimationPlayState {
313 Running,
314 Paused,
315}
316
317impl AnimationPlayState {
318 #[inline]
320 pub fn match_keywords(name: &AnimationName) -> bool {
321 if let Some(name) = name.as_atom() {
322 #[cfg(feature = "gecko")]
323 return name.with_str(|n| Self::from_ident(n).is_ok());
324 #[cfg(feature = "servo")]
325 return Self::from_ident(name).is_ok();
326 }
327 false
328 }
329}
330
331#[derive(
333 Copy,
334 Clone,
335 Debug,
336 MallocSizeOf,
337 Parse,
338 PartialEq,
339 SpecifiedValueInfo,
340 ToComputedValue,
341 ToCss,
342 ToResolvedValue,
343 ToShmem,
344 ToTyped,
345)]
346#[repr(u8)]
347#[allow(missing_docs)]
348pub enum AnimationFillMode {
349 None,
350 Forwards,
351 Backwards,
352 Both,
353}
354
355impl AnimationFillMode {
356 #[inline]
359 pub fn match_keywords(name: &AnimationName) -> bool {
360 if let Some(name) = name.as_atom() {
361 #[cfg(feature = "gecko")]
362 return name.with_str(|n| Self::from_ident(n).is_ok());
363 #[cfg(feature = "servo")]
364 return Self::from_ident(name).is_ok();
365 }
366 false
367 }
368}
369
370#[derive(
372 Copy,
373 Clone,
374 Debug,
375 MallocSizeOf,
376 Parse,
377 PartialEq,
378 SpecifiedValueInfo,
379 ToComputedValue,
380 ToCss,
381 ToResolvedValue,
382 ToShmem,
383 ToTyped,
384)]
385#[repr(u8)]
386#[allow(missing_docs)]
387pub enum AnimationComposition {
388 Replace,
389 Add,
390 Accumulate,
391}
392
393#[derive(
397 Copy,
398 Clone,
399 Debug,
400 Eq,
401 Hash,
402 MallocSizeOf,
403 Parse,
404 PartialEq,
405 SpecifiedValueInfo,
406 ToComputedValue,
407 ToCss,
408 ToResolvedValue,
409 ToShmem,
410)]
411#[repr(u8)]
412pub enum Scroller {
413 Nearest,
415 Root,
417 #[css(keyword = "self")]
419 SelfElement,
420}
421
422impl Scroller {
423 #[inline]
425 fn is_default(&self) -> bool {
426 matches!(*self, Self::Nearest)
427 }
428}
429
430impl Default for Scroller {
431 fn default() -> Self {
432 Self::Nearest
433 }
434}
435
436#[derive(
442 Copy,
443 Clone,
444 Debug,
445 Eq,
446 Hash,
447 MallocSizeOf,
448 Parse,
449 PartialEq,
450 SpecifiedValueInfo,
451 ToComputedValue,
452 ToCss,
453 ToResolvedValue,
454 ToShmem,
455 ToTyped,
456)]
457#[repr(u8)]
458pub enum ScrollAxis {
459 Block = 0,
461 Inline = 1,
463 X = 2,
465 Y = 3,
467}
468
469impl ScrollAxis {
470 #[inline]
472 pub fn is_default(&self) -> bool {
473 matches!(*self, Self::Block)
474 }
475}
476
477impl Default for ScrollAxis {
478 fn default() -> Self {
479 Self::Block
480 }
481}
482
483#[derive(
486 Copy,
487 Clone,
488 Debug,
489 MallocSizeOf,
490 PartialEq,
491 SpecifiedValueInfo,
492 ToComputedValue,
493 ToCss,
494 ToResolvedValue,
495 ToShmem,
496)]
497#[css(function = "scroll")]
498#[repr(C)]
499pub struct ScrollFunction {
500 #[css(skip_if = "Scroller::is_default")]
502 pub scroller: Scroller,
503 #[css(skip_if = "ScrollAxis::is_default")]
505 pub axis: ScrollAxis,
506}
507
508impl ScrollFunction {
509 fn parse_arguments<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
511 let mut scroller = None;
514 let mut axis = None;
515 loop {
516 if scroller.is_none() {
517 scroller = input.try_parse(Scroller::parse).ok();
518 }
519
520 if axis.is_none() {
521 axis = input.try_parse(ScrollAxis::parse).ok();
522 if axis.is_some() {
523 continue;
524 }
525 }
526 break;
527 }
528
529 Ok(Self {
530 scroller: scroller.unwrap_or_default(),
531 axis: axis.unwrap_or_default(),
532 })
533 }
534}
535
536impl generics::ViewFunction<LengthPercentage> {
537 fn parse_arguments<'i, 't>(
539 context: &ParserContext,
540 input: &mut Parser<'i, 't>,
541 ) -> Result<Self, ParseError<'i>> {
542 let mut axis = None;
545 let mut inset = None;
546 loop {
547 if axis.is_none() {
548 axis = input.try_parse(ScrollAxis::parse).ok();
549 }
550
551 if inset.is_none() {
552 inset = input
553 .try_parse(|i| ViewTimelineInset::parse(context, i))
554 .ok();
555 if inset.is_some() {
556 continue;
557 }
558 }
559 break;
560 }
561
562 Ok(Self {
563 inset: inset.unwrap_or_default(),
564 axis: axis.unwrap_or_default(),
565 })
566 }
567}
568
569pub type TimelineName = TreeScoped<TimelineIdent>;
574
575impl TimelineName {
576 pub fn none() -> Self {
578 Self::with_default_level(TimelineIdent::none())
579 }
580}
581
582#[derive(
584 Clone,
585 Debug,
586 Eq,
587 Hash,
588 MallocSizeOf,
589 PartialEq,
590 SpecifiedValueInfo,
591 ToComputedValue,
592 ToResolvedValue,
593 ToShmem,
594)]
595#[repr(C)]
596pub struct TimelineIdent(DashedIdent);
597
598impl TimelineIdent {
599 pub fn none() -> Self {
601 Self(DashedIdent::empty())
602 }
603
604 pub fn is_none(&self) -> bool {
606 self.0.is_empty()
607 }
608}
609
610impl IsTreeScoped for TimelineIdent {
611 fn is_tree_scoped(&self) -> bool {
612 !self.is_none()
613 }
614}
615
616impl Parse for TimelineIdent {
617 fn parse<'i, 't>(
618 context: &ParserContext,
619 input: &mut Parser<'i, 't>,
620 ) -> Result<Self, ParseError<'i>> {
621 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
622 return Ok(Self::none());
623 }
624
625 DashedIdent::parse(context, input).map(TimelineIdent)
626 }
627}
628
629impl ToCss for TimelineIdent {
630 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
631 where
632 W: Write,
633 {
634 if self.is_none() {
635 return dest.write_str("none");
636 }
637
638 self.0.to_css(dest)
639 }
640}
641
642impl ToTyped for TimelineName {}
643
644pub type AnimationTimeline = generics::GenericAnimationTimeline<LengthPercentage>;
646
647impl Parse for AnimationTimeline {
648 fn parse<'i, 't>(
649 context: &ParserContext,
650 input: &mut Parser<'i, 't>,
651 ) -> Result<Self, ParseError<'i>> {
652 use crate::values::generics::animation::ViewFunction;
653
654 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
658 return Ok(Self::Auto);
659 }
660
661 if let Ok(name) = input.try_parse(|i| TimelineName::parse(context, i)) {
663 return Ok(AnimationTimeline::Timeline(name));
664 }
665
666 let location = input.current_source_location();
668 let function = input.expect_function()?.clone();
669 input.parse_nested_block(move |i| {
670 match_ignore_ascii_case! { &function,
671 "scroll" => ScrollFunction::parse_arguments(i).map(Self::Scroll),
672 "view" => ViewFunction::parse_arguments(context, i).map(Self::View),
673 _ => {
674 Err(location.new_custom_error(
675 StyleParseErrorKind::UnexpectedFunction(function.clone())
676 ))
677 },
678 }
679 })
680 }
681}
682
683pub type ViewTimelineInset = generics::GenericViewTimelineInset<LengthPercentage>;
685
686impl Parse for ViewTimelineInset {
687 fn parse<'i, 't>(
688 context: &ParserContext,
689 input: &mut Parser<'i, 't>,
690 ) -> Result<Self, ParseError<'i>> {
691 use crate::values::specified::LengthPercentageOrAuto;
692
693 let start = LengthPercentageOrAuto::parse(context, input)?;
694 let end = match input.try_parse(|input| LengthPercentageOrAuto::parse(context, input)) {
695 Ok(end) => end,
696 Err(_) => start.clone(),
697 };
698
699 Ok(Self { start, end })
700 }
701}
702
703#[derive(
709 Clone,
710 Debug,
711 Eq,
712 Hash,
713 PartialEq,
714 MallocSizeOf,
715 SpecifiedValueInfo,
716 ToCss,
717 ToComputedValue,
718 ToResolvedValue,
719 ToShmem,
720 ToTyped,
721)]
722#[repr(transparent)]
723#[value_info(other_values = "none, match-element")]
724pub struct ViewTransitionNameKeyword(AtomIdent);
725
726impl ViewTransitionNameKeyword {
727 pub fn none() -> Self {
729 Self(AtomIdent::new(atom!("none")))
730 }
731}
732
733impl IsTreeScoped for ViewTransitionNameKeyword {
734 fn is_tree_scoped(&self) -> bool {
735 self.0 .0 != atom!("none")
736 }
737}
738
739impl Parse for ViewTransitionNameKeyword {
740 fn parse<'i, 't>(
741 _: &ParserContext,
742 input: &mut Parser<'i, 't>,
743 ) -> Result<Self, ParseError<'i>> {
744 let location = input.current_source_location();
745 let ident = input.expect_ident()?;
746 if ident.eq_ignore_ascii_case("none") {
747 return Ok(Self::none());
748 }
749
750 if ident.eq_ignore_ascii_case("match-element") {
751 return Ok(Self(AtomIdent::new(atom!("match-element"))));
752 }
753
754 CustomIdent::from_ident(location, ident, &["auto"]).map(|i| Self(AtomIdent::new(i.0)))
757 }
758}
759
760pub type ViewTransitionName = TreeScoped<ViewTransitionNameKeyword>;
762
763impl ViewTransitionName {
764 pub fn none() -> Self {
766 Self::with_default_level(ViewTransitionNameKeyword::none())
767 }
768}
769
770#[derive(
776 Clone,
777 Debug,
778 Default,
779 Eq,
780 Hash,
781 PartialEq,
782 MallocSizeOf,
783 SpecifiedValueInfo,
784 ToComputedValue,
785 ToCss,
786 ToResolvedValue,
787 ToShmem,
788 ToTyped,
789)]
790#[repr(C)]
791#[value_info(other_values = "none")]
792pub struct ViewTransitionClassList(
793 #[css(iterable, if_empty = "none")]
794 #[ignore_malloc_size_of = "Arc"]
795 crate::ArcSlice<CustomIdent>,
796);
797
798impl IsTreeScoped for ViewTransitionClassList {
799 fn is_tree_scoped(&self) -> bool {
800 !self.is_none()
801 }
802}
803
804impl ViewTransitionClassList {
805 pub fn none() -> Self {
807 Self(Default::default())
808 }
809
810 pub fn is_none(&self) -> bool {
812 self.0.is_empty()
813 }
814
815 pub fn iter(&self) -> impl Iterator<Item = &CustomIdent> {
817 self.0.iter()
818 }
819}
820
821impl Parse for ViewTransitionClassList {
822 fn parse<'i, 't>(
823 _: &ParserContext,
824 input: &mut Parser<'i, 't>,
825 ) -> Result<Self, ParseError<'i>> {
826 use style_traits::{Separator, Space};
827
828 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
829 return Ok(Self::none());
830 }
831
832 Ok(Self(crate::ArcSlice::from_iter(
833 Space::parse(input, |i| CustomIdent::parse(i, &["none"]))?.into_iter(),
834 )))
835 }
836}
837
838pub type ViewTransitionClass = TreeScoped<ViewTransitionClassList>;
840
841impl ViewTransitionClass {
842 pub fn none() -> Self {
844 Self::with_default_level(ViewTransitionClassList::none())
845 }
846}
847
848#[derive(
855 Copy,
856 Clone,
857 Debug,
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}