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::TreeScoped;
12use crate::values::specified::{LengthPercentage, NonNegativeNumber, Time};
13use crate::values::{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};
20
21#[derive(
24 Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
25)]
26#[repr(u8)]
27pub enum TransitionProperty {
28 NonCustom(NonCustomPropertyId),
30 Custom(Atom),
32 Unsupported(CustomIdent),
35}
36
37impl ToCss for TransitionProperty {
38 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
39 where
40 W: Write,
41 {
42 match *self {
43 TransitionProperty::NonCustom(ref id) => id.to_css(dest),
44 TransitionProperty::Custom(ref name) => {
45 dest.write_str("--")?;
46 crate::values::serialize_atom_name(name, dest)
47 },
48 TransitionProperty::Unsupported(ref i) => i.to_css(dest),
49 }
50 }
51}
52
53impl Parse for TransitionProperty {
54 fn parse<'i, 't>(
55 context: &ParserContext,
56 input: &mut Parser<'i, 't>,
57 ) -> Result<Self, ParseError<'i>> {
58 let location = input.current_source_location();
59 let ident = input.expect_ident()?;
60
61 let id = match PropertyId::parse_ignoring_rule_type(&ident, context) {
62 Ok(id) => id,
63 Err(..) => {
64 return Ok(TransitionProperty::Unsupported(CustomIdent::from_ident(
66 location,
67 ident,
68 &["none"],
69 )?));
70 },
71 };
72
73 Ok(match id {
74 PropertyId::NonCustom(id) => TransitionProperty::NonCustom(id.unaliased()),
75 PropertyId::Custom(name) => TransitionProperty::Custom(name),
76 })
77 }
78}
79
80impl SpecifiedValueInfo for TransitionProperty {
81 fn collect_completion_keywords(f: KeywordsCollectFn) {
82 f(&["all"]);
86 }
87}
88
89impl TransitionProperty {
90 #[inline]
92 pub fn none() -> Self {
93 TransitionProperty::Unsupported(CustomIdent(atom!("none")))
94 }
95
96 #[inline]
98 pub fn is_none(&self) -> bool {
99 matches!(*self, TransitionProperty::Unsupported(ref ident) if ident.0 == atom!("none"))
100 }
101
102 #[inline]
104 pub fn all() -> Self {
105 TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(ShorthandId::All))
106 }
107
108 #[inline]
110 pub fn is_all(&self) -> bool {
111 self == &TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(
112 ShorthandId::All,
113 ))
114 }
115}
116
117#[derive(
121 Clone,
122 Copy,
123 Debug,
124 MallocSizeOf,
125 Parse,
126 PartialEq,
127 SpecifiedValueInfo,
128 ToComputedValue,
129 ToCss,
130 ToResolvedValue,
131 ToShmem,
132)]
133#[repr(u8)]
134pub enum TransitionBehavior {
135 Normal,
137 AllowDiscrete,
139}
140
141impl TransitionBehavior {
142 #[inline]
144 pub fn normal() -> Self {
145 Self::Normal
146 }
147
148 #[inline]
150 pub fn is_normal(&self) -> bool {
151 matches!(*self, Self::Normal)
152 }
153}
154
155pub type AnimationDuration = generics::GenericAnimationDuration<Time>;
157
158impl Parse for AnimationDuration {
159 fn parse<'i, 't>(
160 context: &ParserContext,
161 input: &mut Parser<'i, 't>,
162 ) -> Result<Self, ParseError<'i>> {
163 if static_prefs::pref!("layout.css.scroll-driven-animations.enabled")
164 && input.try_parse(|i| i.expect_ident_matching("auto")).is_ok()
165 {
166 return Ok(Self::auto());
167 }
168
169 Time::parse_non_negative(context, input).map(AnimationDuration::Time)
170 }
171}
172
173#[derive(
175 Copy, Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
176)]
177pub enum AnimationIterationCount {
178 Number(NonNegativeNumber),
180 Infinite,
182}
183
184impl AnimationIterationCount {
185 #[inline]
187 pub fn one() -> Self {
188 Self::Number(NonNegativeNumber::new(1.0))
189 }
190
191 #[inline]
193 pub fn is_one(&self) -> bool {
194 *self == Self::one()
195 }
196}
197
198#[derive(
200 Clone,
201 Debug,
202 Eq,
203 Hash,
204 MallocSizeOf,
205 PartialEq,
206 SpecifiedValueInfo,
207 ToComputedValue,
208 ToCss,
209 ToResolvedValue,
210 ToShmem,
211 ToTyped,
212)]
213#[value_info(other_values = "none")]
214#[repr(C)]
215pub struct AnimationName(pub KeyframesName);
216
217impl AnimationName {
218 pub fn as_atom(&self) -> Option<&Atom> {
220 if self.is_none() {
221 return None;
222 }
223 Some(self.0.as_atom())
224 }
225
226 pub fn none() -> Self {
228 AnimationName(KeyframesName::none())
229 }
230
231 pub fn is_none(&self) -> bool {
233 self.0.is_none()
234 }
235}
236
237impl Parse for AnimationName {
238 fn parse<'i, 't>(
239 context: &ParserContext,
240 input: &mut Parser<'i, 't>,
241 ) -> Result<Self, ParseError<'i>> {
242 if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) {
243 return Ok(AnimationName(name));
244 }
245
246 input.expect_ident_matching("none")?;
247 Ok(AnimationName(KeyframesName::none()))
248 }
249}
250
251#[derive(
253 Copy,
254 Clone,
255 Debug,
256 MallocSizeOf,
257 Parse,
258 PartialEq,
259 SpecifiedValueInfo,
260 ToComputedValue,
261 ToCss,
262 ToResolvedValue,
263 ToShmem,
264 ToTyped,
265)]
266#[repr(u8)]
267#[allow(missing_docs)]
268pub enum AnimationDirection {
269 Normal,
270 Reverse,
271 Alternate,
272 AlternateReverse,
273}
274
275impl AnimationDirection {
276 #[inline]
278 pub fn match_keywords(name: &AnimationName) -> bool {
279 if let Some(name) = name.as_atom() {
280 #[cfg(feature = "gecko")]
281 return name.with_str(|n| Self::from_ident(n).is_ok());
282 #[cfg(feature = "servo")]
283 return Self::from_ident(name).is_ok();
284 }
285 false
286 }
287}
288
289#[derive(
291 Copy,
292 Clone,
293 Debug,
294 MallocSizeOf,
295 Parse,
296 PartialEq,
297 SpecifiedValueInfo,
298 ToComputedValue,
299 ToCss,
300 ToResolvedValue,
301 ToShmem,
302 ToTyped,
303)]
304#[repr(u8)]
305#[allow(missing_docs)]
306pub enum AnimationPlayState {
307 Running,
308 Paused,
309}
310
311impl AnimationPlayState {
312 #[inline]
314 pub fn match_keywords(name: &AnimationName) -> bool {
315 if let Some(name) = name.as_atom() {
316 #[cfg(feature = "gecko")]
317 return name.with_str(|n| Self::from_ident(n).is_ok());
318 #[cfg(feature = "servo")]
319 return Self::from_ident(name).is_ok();
320 }
321 false
322 }
323}
324
325#[derive(
327 Copy,
328 Clone,
329 Debug,
330 MallocSizeOf,
331 Parse,
332 PartialEq,
333 SpecifiedValueInfo,
334 ToComputedValue,
335 ToCss,
336 ToResolvedValue,
337 ToShmem,
338 ToTyped,
339)]
340#[repr(u8)]
341#[allow(missing_docs)]
342pub enum AnimationFillMode {
343 None,
344 Forwards,
345 Backwards,
346 Both,
347}
348
349impl AnimationFillMode {
350 #[inline]
353 pub fn match_keywords(name: &AnimationName) -> bool {
354 if let Some(name) = name.as_atom() {
355 #[cfg(feature = "gecko")]
356 return name.with_str(|n| Self::from_ident(n).is_ok());
357 #[cfg(feature = "servo")]
358 return Self::from_ident(name).is_ok();
359 }
360 false
361 }
362}
363
364#[derive(
366 Copy,
367 Clone,
368 Debug,
369 MallocSizeOf,
370 Parse,
371 PartialEq,
372 SpecifiedValueInfo,
373 ToComputedValue,
374 ToCss,
375 ToResolvedValue,
376 ToShmem,
377 ToTyped,
378)]
379#[repr(u8)]
380#[allow(missing_docs)]
381pub enum AnimationComposition {
382 Replace,
383 Add,
384 Accumulate,
385}
386
387#[derive(
391 Copy,
392 Clone,
393 Debug,
394 Eq,
395 Hash,
396 MallocSizeOf,
397 Parse,
398 PartialEq,
399 SpecifiedValueInfo,
400 ToComputedValue,
401 ToCss,
402 ToResolvedValue,
403 ToShmem,
404)]
405#[repr(u8)]
406pub enum Scroller {
407 Nearest,
409 Root,
411 #[css(keyword = "self")]
413 SelfElement,
414}
415
416impl Scroller {
417 #[inline]
419 fn is_default(&self) -> bool {
420 matches!(*self, Self::Nearest)
421 }
422}
423
424impl Default for Scroller {
425 fn default() -> Self {
426 Self::Nearest
427 }
428}
429
430#[derive(
436 Copy,
437 Clone,
438 Debug,
439 Eq,
440 Hash,
441 MallocSizeOf,
442 Parse,
443 PartialEq,
444 SpecifiedValueInfo,
445 ToComputedValue,
446 ToCss,
447 ToResolvedValue,
448 ToShmem,
449)]
450#[repr(u8)]
451pub enum ScrollAxis {
452 Block = 0,
454 Inline = 1,
456 X = 2,
458 Y = 3,
460}
461
462impl ScrollAxis {
463 #[inline]
465 pub fn is_default(&self) -> bool {
466 matches!(*self, Self::Block)
467 }
468}
469
470impl Default for ScrollAxis {
471 fn default() -> Self {
472 Self::Block
473 }
474}
475
476#[derive(
479 Copy,
480 Clone,
481 Debug,
482 MallocSizeOf,
483 PartialEq,
484 SpecifiedValueInfo,
485 ToComputedValue,
486 ToCss,
487 ToResolvedValue,
488 ToShmem,
489)]
490#[css(function = "scroll")]
491#[repr(C)]
492pub struct ScrollFunction {
493 #[css(skip_if = "Scroller::is_default")]
495 pub scroller: Scroller,
496 #[css(skip_if = "ScrollAxis::is_default")]
498 pub axis: ScrollAxis,
499}
500
501impl ScrollFunction {
502 fn parse_arguments<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
504 let mut scroller = None;
507 let mut axis = None;
508 loop {
509 if scroller.is_none() {
510 scroller = input.try_parse(Scroller::parse).ok();
511 }
512
513 if axis.is_none() {
514 axis = input.try_parse(ScrollAxis::parse).ok();
515 if axis.is_some() {
516 continue;
517 }
518 }
519 break;
520 }
521
522 Ok(Self {
523 scroller: scroller.unwrap_or_default(),
524 axis: axis.unwrap_or_default(),
525 })
526 }
527}
528
529impl generics::ViewFunction<LengthPercentage> {
530 fn parse_arguments<'i, 't>(
532 context: &ParserContext,
533 input: &mut Parser<'i, 't>,
534 ) -> Result<Self, ParseError<'i>> {
535 let mut axis = None;
538 let mut inset = None;
539 loop {
540 if axis.is_none() {
541 axis = input.try_parse(ScrollAxis::parse).ok();
542 }
543
544 if inset.is_none() {
545 inset = input
546 .try_parse(|i| ViewTimelineInset::parse(context, i))
547 .ok();
548 if inset.is_some() {
549 continue;
550 }
551 }
552 break;
553 }
554
555 Ok(Self {
556 inset: inset.unwrap_or_default(),
557 axis: axis.unwrap_or_default(),
558 })
559 }
560}
561
562pub type TimelineName = TreeScoped<TimelineIdent>;
567
568impl TimelineName {
569 pub fn none() -> Self {
571 Self::with_default_level(TimelineIdent::none())
572 }
573}
574
575#[derive(
577 Clone,
578 Debug,
579 Eq,
580 Hash,
581 MallocSizeOf,
582 PartialEq,
583 SpecifiedValueInfo,
584 ToComputedValue,
585 ToResolvedValue,
586 ToShmem,
587)]
588#[repr(C)]
589pub struct TimelineIdent(DashedIdent);
590
591impl TimelineIdent {
592 pub fn none() -> Self {
594 Self(DashedIdent::empty())
595 }
596
597 pub fn is_none(&self) -> bool {
599 self.0.is_empty()
600 }
601}
602
603impl Parse for TimelineIdent {
604 fn parse<'i, 't>(
605 context: &ParserContext,
606 input: &mut Parser<'i, 't>,
607 ) -> Result<Self, ParseError<'i>> {
608 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
609 return Ok(Self::none());
610 }
611
612 DashedIdent::parse(context, input).map(TimelineIdent)
613 }
614}
615
616impl ToCss for TimelineIdent {
617 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
618 where
619 W: Write,
620 {
621 if self.is_none() {
622 return dest.write_str("none");
623 }
624
625 self.0.to_css(dest)
626 }
627}
628
629pub type AnimationTimeline = generics::GenericAnimationTimeline<LengthPercentage>;
631
632impl Parse for AnimationTimeline {
633 fn parse<'i, 't>(
634 context: &ParserContext,
635 input: &mut Parser<'i, 't>,
636 ) -> Result<Self, ParseError<'i>> {
637 use crate::values::generics::animation::ViewFunction;
638
639 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
643 return Ok(Self::Auto);
644 }
645
646 if let Ok(name) = input.try_parse(|i| TimelineName::parse(context, i)) {
648 return Ok(AnimationTimeline::Timeline(name));
649 }
650
651 let location = input.current_source_location();
653 let function = input.expect_function()?.clone();
654 input.parse_nested_block(move |i| {
655 match_ignore_ascii_case! { &function,
656 "scroll" => ScrollFunction::parse_arguments(i).map(Self::Scroll),
657 "view" => ViewFunction::parse_arguments(context, i).map(Self::View),
658 _ => {
659 Err(location.new_custom_error(
660 StyleParseErrorKind::UnexpectedFunction(function.clone())
661 ))
662 },
663 }
664 })
665 }
666}
667
668pub type ViewTimelineInset = generics::GenericViewTimelineInset<LengthPercentage>;
670
671impl Parse for ViewTimelineInset {
672 fn parse<'i, 't>(
673 context: &ParserContext,
674 input: &mut Parser<'i, 't>,
675 ) -> Result<Self, ParseError<'i>> {
676 use crate::values::specified::LengthPercentageOrAuto;
677
678 let start = LengthPercentageOrAuto::parse(context, input)?;
679 let end = match input.try_parse(|input| LengthPercentageOrAuto::parse(context, input)) {
680 Ok(end) => end,
681 Err(_) => start.clone(),
682 };
683
684 Ok(Self { start, end })
685 }
686}
687
688#[derive(
694 Clone,
695 Debug,
696 Eq,
697 Hash,
698 PartialEq,
699 MallocSizeOf,
700 SpecifiedValueInfo,
701 ToComputedValue,
702 ToResolvedValue,
703 ToShmem,
704 ToTyped,
705)]
706#[repr(C, u8)]
707pub enum ViewTransitionName {
708 None,
710 MatchElement,
713 Ident(Atom),
715}
716
717impl ViewTransitionName {
718 pub fn none() -> Self {
720 Self::None
721 }
722}
723
724impl Parse for ViewTransitionName {
725 fn parse<'i, 't>(
726 _: &ParserContext,
727 input: &mut Parser<'i, 't>,
728 ) -> Result<Self, ParseError<'i>> {
729 let location = input.current_source_location();
730 let ident = input.expect_ident()?;
731 if ident.eq_ignore_ascii_case("none") {
732 return Ok(Self::none());
733 }
734
735 if ident.eq_ignore_ascii_case("match-element") {
736 return Ok(Self::MatchElement);
737 }
738
739 CustomIdent::from_ident(location, ident, &["auto"]).map(|i| Self::Ident(i.0))
742 }
743}
744
745impl ToCss for ViewTransitionName {
746 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
747 where
748 W: Write,
749 {
750 use crate::values::serialize_atom_identifier;
751 match *self {
752 Self::None => dest.write_str("none"),
753 Self::MatchElement => dest.write_str("match-element"),
754 Self::Ident(ref ident) => serialize_atom_identifier(ident, dest),
755 }
756 }
757}
758
759#[derive(
765 Clone,
766 Debug,
767 Eq,
768 Hash,
769 PartialEq,
770 MallocSizeOf,
771 SpecifiedValueInfo,
772 ToComputedValue,
773 ToCss,
774 ToResolvedValue,
775 ToShmem,
776 ToTyped,
777)]
778#[repr(C)]
779#[value_info(other_values = "none")]
780pub struct ViewTransitionClass(
781 #[css(iterable, if_empty = "none")]
782 #[ignore_malloc_size_of = "Arc"]
783 crate::ArcSlice<CustomIdent>,
784);
785
786impl ViewTransitionClass {
787 pub fn none() -> Self {
789 Self(Default::default())
790 }
791
792 pub fn is_none(&self) -> bool {
794 self.0.is_empty()
795 }
796}
797
798impl Parse for ViewTransitionClass {
799 fn parse<'i, 't>(
800 _: &ParserContext,
801 input: &mut Parser<'i, 't>,
802 ) -> Result<Self, ParseError<'i>> {
803 use style_traits::{Separator, Space};
804
805 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
806 return Ok(Self::none());
807 }
808
809 Ok(Self(crate::ArcSlice::from_iter(
810 Space::parse(input, |i| CustomIdent::parse(i, &["none"]))?.into_iter(),
811 )))
812 }
813}
814
815#[derive(
822 Copy,
823 Clone,
824 Debug,
825 MallocSizeOf,
826 Parse,
827 PartialEq,
828 SpecifiedValueInfo,
829 ToComputedValue,
830 ToCss,
831 ToResolvedValue,
832 ToShmem,
833 ToTyped,
834)]
835#[repr(u8)]
836pub enum TimelineRangeName {
837 #[css(skip)]
839 None,
840 Cover,
842 Contain,
845 Entry,
848 Exit,
851 EntryCrossing,
853 ExitCrossing,
855 Scroll,
858}
859
860impl TimelineRangeName {
861 #[inline]
863 pub fn is_none(&self) -> bool {
864 matches!(*self, Self::None)
865 }
866}
867
868pub type AnimationRangeValue = generics::GenericAnimationRangeValue<LengthPercentage>;
870
871fn parse_animation_range<'i, 't>(
872 context: &ParserContext,
873 input: &mut Parser<'i, 't>,
874 default_percentage: LengthPercentage,
875) -> Result<AnimationRangeValue, ParseError<'i>> {
876 if input
877 .try_parse(|i| i.expect_ident_matching("normal"))
878 .is_ok()
879 {
880 return Ok(AnimationRangeValue::default());
881 }
882
883 if let Ok(lp) = input.try_parse(|i| LengthPercentage::parse(context, i)) {
884 return Ok(AnimationRangeValue::length_percentage(lp));
885 }
886
887 let name = TimelineRangeName::parse(input)?;
888 let lp = input
889 .try_parse(|i| LengthPercentage::parse(context, i))
890 .unwrap_or(default_percentage);
891 Ok(AnimationRangeValue::timeline_range(name, lp))
892}
893
894pub type AnimationRangeStart = generics::GenericAnimationRangeStart<LengthPercentage>;
896
897impl Parse for AnimationRangeStart {
898 fn parse<'i, 't>(
899 context: &ParserContext,
900 input: &mut Parser<'i, 't>,
901 ) -> Result<Self, ParseError<'i>> {
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<'i, 't>(
911 context: &ParserContext,
912 input: &mut Parser<'i, 't>,
913 ) -> Result<Self, ParseError<'i>> {
914 parse_animation_range(context, input, LengthPercentage::hundred_percent()).map(Self)
915 }
916}