1use std::borrow::Cow;
4
5use crate::context::PropertyHandlerContext;
6use crate::declaration::{DeclarationBlock, DeclarationList};
7use crate::error::{ParserError, PrinterError};
8use crate::macros::*;
9use crate::prefixes::Feature;
10use crate::printer::Printer;
11use crate::properties::{Property, PropertyId, TokenOrValue, VendorPrefix};
12use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero};
13use crate::values::ident::DashedIdent;
14use crate::values::number::CSSNumber;
15use crate::values::percentage::Percentage;
16use crate::values::size::Size2D;
17use crate::values::string::CSSString;
18use crate::values::{easing::EasingFunction, ident::CustomIdent, time::Time};
19#[cfg(feature = "visitor")]
20use crate::visitor::Visit;
21use cssparser::*;
22use itertools::izip;
23use smallvec::SmallVec;
24
25use super::{LengthPercentage, LengthPercentageOrAuto};
26
27#[derive(Debug, Clone, PartialEq, Parse)]
29#[cfg_attr(feature = "visitor", derive(Visit))]
30#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
31#[cfg_attr(
32 feature = "serde",
33 derive(serde::Serialize, serde::Deserialize),
34 serde(tag = "type", content = "value", rename_all = "kebab-case")
35)]
36#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
37pub enum AnimationName<'i> {
38 None,
40 #[cfg_attr(feature = "serde", serde(borrow))]
42 Ident(CustomIdent<'i>),
43 #[cfg_attr(feature = "serde", serde(borrow))]
45 String(CSSString<'i>),
46}
47
48impl<'i> ToCss for AnimationName<'i> {
49 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
50 where
51 W: std::fmt::Write,
52 {
53 let css_module_animation_enabled =
54 dest.css_module.as_ref().map_or(false, |css_module| css_module.config.animation);
55
56 match self {
57 AnimationName::None => dest.write_str("none"),
58 AnimationName::Ident(s) => {
59 if css_module_animation_enabled {
60 if let Some(css_module) = &mut dest.css_module {
61 css_module.reference(&s.0, dest.loc.source_index)
62 }
63 }
64 s.to_css_with_options(dest, css_module_animation_enabled)
65 }
66 AnimationName::String(s) => {
67 if css_module_animation_enabled {
68 if let Some(css_module) = &mut dest.css_module {
69 css_module.reference(&s, dest.loc.source_index)
70 }
71 }
72
73 match_ignore_ascii_case! { &*s,
75 "none" | "initial" | "inherit" | "unset" | "default" | "revert" | "revert-layer" => {
76 serialize_string(&s, dest)?;
77 Ok(())
78 },
79 _ => {
80 dest.write_ident(s.as_ref(), css_module_animation_enabled)
81 }
82 }
83 }
84 }
85 }
86}
87
88pub type AnimationNameList<'i> = SmallVec<[AnimationName<'i>; 1]>;
90
91#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
93#[cfg_attr(feature = "visitor", derive(Visit))]
94#[cfg_attr(
95 feature = "serde",
96 derive(serde::Serialize, serde::Deserialize),
97 serde(tag = "type", content = "value", rename_all = "kebab-case")
98)]
99#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
100#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
101pub enum AnimationIterationCount {
102 Number(CSSNumber),
104 Infinite,
106}
107
108impl Default for AnimationIterationCount {
109 fn default() -> Self {
110 AnimationIterationCount::Number(1.0)
111 }
112}
113
114enum_property! {
115 pub enum AnimationDirection {
117 Normal,
119 Reverse,
121 Alternate,
123 AlternateReverse,
125 }
126}
127
128impl Default for AnimationDirection {
129 fn default() -> Self {
130 AnimationDirection::Normal
131 }
132}
133
134enum_property! {
135 pub enum AnimationPlayState {
137 Running,
139 Paused,
141 }
142}
143
144impl Default for AnimationPlayState {
145 fn default() -> Self {
146 AnimationPlayState::Running
147 }
148}
149
150enum_property! {
151 pub enum AnimationFillMode {
153 None,
155 Forwards,
157 Backwards,
159 Both,
161 }
162}
163
164impl Default for AnimationFillMode {
165 fn default() -> Self {
166 AnimationFillMode::None
167 }
168}
169
170enum_property! {
171 pub enum AnimationComposition {
173 Replace,
175 Add,
177 Accumulate,
179 }
180}
181
182#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
184#[cfg_attr(feature = "visitor", derive(Visit))]
185#[cfg_attr(
186 feature = "serde",
187 derive(serde::Serialize, serde::Deserialize),
188 serde(tag = "type", content = "value", rename_all = "kebab-case")
189)]
190#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
191#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
192pub enum AnimationTimeline<'i> {
193 Auto,
195 None,
197 #[cfg_attr(feature = "serde", serde(borrow))]
199 DashedIdent(DashedIdent<'i>),
200 Scroll(ScrollTimeline),
202 View(ViewTimeline),
204}
205
206impl<'i> Default for AnimationTimeline<'i> {
207 fn default() -> Self {
208 AnimationTimeline::Auto
209 }
210}
211
212#[derive(Debug, Clone, PartialEq)]
214#[cfg_attr(feature = "visitor", derive(Visit))]
215#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
216#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
217#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
218pub struct ScrollTimeline {
219 pub scroller: Scroller,
221 pub axis: ScrollAxis,
223}
224
225impl<'i> Parse<'i> for ScrollTimeline {
226 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
227 input.expect_function_matching("scroll")?;
228 input.parse_nested_block(|input| {
229 let mut scroller = None;
230 let mut axis = None;
231 loop {
232 if scroller.is_none() {
233 scroller = input.try_parse(Scroller::parse).ok();
234 }
235
236 if axis.is_none() {
237 axis = input.try_parse(ScrollAxis::parse).ok();
238 if axis.is_some() {
239 continue;
240 }
241 }
242 break;
243 }
244
245 Ok(ScrollTimeline {
246 scroller: scroller.unwrap_or_default(),
247 axis: axis.unwrap_or_default(),
248 })
249 })
250 }
251}
252
253impl ToCss for ScrollTimeline {
254 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
255 where
256 W: std::fmt::Write,
257 {
258 dest.write_str("scroll(")?;
259
260 let mut needs_space = false;
261 if self.scroller != Scroller::default() {
262 self.scroller.to_css(dest)?;
263 needs_space = true;
264 }
265
266 if self.axis != ScrollAxis::default() {
267 if needs_space {
268 dest.write_char(' ')?;
269 }
270 self.axis.to_css(dest)?;
271 }
272
273 dest.write_char(')')
274 }
275}
276
277enum_property! {
278 pub enum Scroller {
280 "root": Root,
282 "nearest": Nearest,
284 "self": SelfElement,
286 }
287}
288
289impl Default for Scroller {
290 fn default() -> Self {
291 Scroller::Nearest
292 }
293}
294
295enum_property! {
296 pub enum ScrollAxis {
298 Block,
300 Inline,
302 X,
304 Y,
306 }
307}
308
309impl Default for ScrollAxis {
310 fn default() -> Self {
311 ScrollAxis::Block
312 }
313}
314
315#[derive(Debug, Clone, PartialEq)]
317#[cfg_attr(feature = "visitor", derive(Visit))]
318#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
319#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
320#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
321pub struct ViewTimeline {
322 pub axis: ScrollAxis,
324 pub inset: Size2D<LengthPercentageOrAuto>,
326}
327
328impl<'i> Parse<'i> for ViewTimeline {
329 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
330 input.expect_function_matching("view")?;
331 input.parse_nested_block(|input| {
332 let mut axis = None;
333 let mut inset = None;
334 loop {
335 if axis.is_none() {
336 axis = input.try_parse(ScrollAxis::parse).ok();
337 }
338
339 if inset.is_none() {
340 inset = input.try_parse(Size2D::parse).ok();
341 if inset.is_some() {
342 continue;
343 }
344 }
345 break;
346 }
347
348 Ok(ViewTimeline {
349 axis: axis.unwrap_or_default(),
350 inset: inset.unwrap_or(Size2D(LengthPercentageOrAuto::Auto, LengthPercentageOrAuto::Auto)),
351 })
352 })
353 }
354}
355
356impl ToCss for ViewTimeline {
357 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
358 where
359 W: std::fmt::Write,
360 {
361 dest.write_str("view(")?;
362 let mut needs_space = false;
363 if self.axis != ScrollAxis::default() {
364 self.axis.to_css(dest)?;
365 needs_space = true;
366 }
367
368 if self.inset.0 != LengthPercentageOrAuto::Auto || self.inset.1 != LengthPercentageOrAuto::Auto {
369 if needs_space {
370 dest.write_char(' ')?;
371 }
372 self.inset.to_css(dest)?;
373 }
374
375 dest.write_char(')')
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
381#[cfg_attr(feature = "visitor", derive(Visit))]
382#[cfg_attr(
383 feature = "serde",
384 derive(serde::Serialize, serde::Deserialize),
385 serde(rename_all = "kebab-case")
386)]
387#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
388#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
389pub enum TimelineRangeName {
390 Cover,
392 Contain,
395 Entry,
397 Exit,
399 EntryCrossing,
401 ExitCrossing,
403}
404
405#[derive(Debug, Clone, PartialEq)]
408#[cfg_attr(feature = "visitor", derive(Visit))]
409#[cfg_attr(
410 feature = "serde",
411 derive(serde::Serialize, serde::Deserialize),
412 serde(rename_all = "lowercase")
413)]
414#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
415#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
416pub enum AnimationAttachmentRange {
417 Normal,
419 #[cfg_attr(feature = "serde", serde(untagged))]
421 LengthPercentage(LengthPercentage),
422 #[cfg_attr(feature = "serde", serde(untagged))]
424 TimelineRange {
425 name: TimelineRangeName,
427 offset: LengthPercentage,
429 },
430}
431
432impl<'i> AnimationAttachmentRange {
433 fn parse<'t>(input: &mut Parser<'i, 't>, default: f32) -> Result<Self, ParseError<'i, ParserError<'i>>> {
434 if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
435 return Ok(AnimationAttachmentRange::Normal);
436 }
437
438 if let Ok(val) = input.try_parse(LengthPercentage::parse) {
439 return Ok(AnimationAttachmentRange::LengthPercentage(val));
440 }
441
442 let name = TimelineRangeName::parse(input)?;
443 let offset = input
444 .try_parse(LengthPercentage::parse)
445 .unwrap_or(LengthPercentage::Percentage(Percentage(default)));
446 Ok(AnimationAttachmentRange::TimelineRange { name, offset })
447 }
448
449 fn to_css<W>(&self, dest: &mut Printer<W>, default: f32) -> Result<(), PrinterError>
450 where
451 W: std::fmt::Write,
452 {
453 match self {
454 Self::Normal => dest.write_str("normal"),
455 Self::LengthPercentage(val) => val.to_css(dest),
456 Self::TimelineRange { name, offset } => {
457 name.to_css(dest)?;
458 if *offset != LengthPercentage::Percentage(Percentage(default)) {
459 dest.write_char(' ')?;
460 offset.to_css(dest)?;
461 }
462 Ok(())
463 }
464 }
465 }
466}
467
468impl Default for AnimationAttachmentRange {
469 fn default() -> Self {
470 AnimationAttachmentRange::Normal
471 }
472}
473
474#[derive(Debug, Clone, PartialEq)]
476#[cfg_attr(feature = "visitor", derive(Visit))]
477#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
478#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
479#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
480pub struct AnimationRangeStart(pub AnimationAttachmentRange);
481
482impl<'i> Parse<'i> for AnimationRangeStart {
483 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
484 let range = AnimationAttachmentRange::parse(input, 0.0)?;
485 Ok(Self(range))
486 }
487}
488
489impl ToCss for AnimationRangeStart {
490 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
491 where
492 W: std::fmt::Write,
493 {
494 self.0.to_css(dest, 0.0)
495 }
496}
497
498#[derive(Debug, Clone, PartialEq)]
500#[cfg_attr(feature = "visitor", derive(Visit))]
501#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
502#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
503#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
504pub struct AnimationRangeEnd(pub AnimationAttachmentRange);
505
506impl<'i> Parse<'i> for AnimationRangeEnd {
507 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
508 let range = AnimationAttachmentRange::parse(input, 1.0)?;
509 Ok(Self(range))
510 }
511}
512
513impl ToCss for AnimationRangeEnd {
514 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
515 where
516 W: std::fmt::Write,
517 {
518 self.0.to_css(dest, 1.0)
519 }
520}
521
522#[derive(Debug, Clone, PartialEq)]
524#[cfg_attr(feature = "visitor", derive(Visit))]
525#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
526#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
527#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
528pub struct AnimationRange {
529 pub start: AnimationRangeStart,
531 pub end: AnimationRangeEnd,
533}
534
535impl<'i> Parse<'i> for AnimationRange {
536 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
537 let start = AnimationRangeStart::parse(input)?;
538 let end = input
539 .try_parse(AnimationRangeStart::parse)
540 .map(|r| AnimationRangeEnd(r.0))
541 .unwrap_or_else(|_| {
542 match &start.0 {
545 AnimationAttachmentRange::TimelineRange { name, .. } => {
546 AnimationRangeEnd(AnimationAttachmentRange::TimelineRange {
547 name: name.clone(),
548 offset: LengthPercentage::Percentage(Percentage(1.0)),
549 })
550 }
551 _ => AnimationRangeEnd(AnimationAttachmentRange::default()),
552 }
553 });
554 Ok(AnimationRange { start, end })
555 }
556}
557
558impl ToCss for AnimationRange {
559 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
560 where
561 W: std::fmt::Write,
562 {
563 self.start.to_css(dest)?;
564
565 let omit_end = match (&self.start.0, &self.end.0) {
566 (
567 AnimationAttachmentRange::TimelineRange { name: start_name, .. },
568 AnimationAttachmentRange::TimelineRange {
569 name: end_name,
570 offset: end_offset,
571 },
572 ) => start_name == end_name && *end_offset == LengthPercentage::Percentage(Percentage(1.0)),
573 (_, end) => *end == AnimationAttachmentRange::default(),
574 };
575
576 if !omit_end {
577 dest.write_char(' ')?;
578 self.end.to_css(dest)?;
579 }
580 Ok(())
581 }
582}
583
584define_list_shorthand! {
585 pub struct Animation<'i>(VendorPrefix) {
587 #[cfg_attr(feature = "serde", serde(borrow))]
589 name: AnimationName(AnimationName<'i>, VendorPrefix),
590 duration: AnimationDuration(Time, VendorPrefix),
592 timing_function: AnimationTimingFunction(EasingFunction, VendorPrefix),
594 iteration_count: AnimationIterationCount(AnimationIterationCount, VendorPrefix),
596 direction: AnimationDirection(AnimationDirection, VendorPrefix),
598 play_state: AnimationPlayState(AnimationPlayState, VendorPrefix),
600 delay: AnimationDelay(Time, VendorPrefix),
602 fill_mode: AnimationFillMode(AnimationFillMode, VendorPrefix),
604 timeline: AnimationTimeline(AnimationTimeline<'i>),
606 }
607}
608
609impl<'i> Parse<'i> for Animation<'i> {
610 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
611 let mut name = None;
612 let mut duration = None;
613 let mut timing_function = None;
614 let mut iteration_count = None;
615 let mut direction = None;
616 let mut play_state = None;
617 let mut delay = None;
618 let mut fill_mode = None;
619 let mut timeline = None;
620
621 macro_rules! parse_prop {
622 ($var: ident, $type: ident) => {
623 if $var.is_none() {
624 if let Ok(value) = input.try_parse($type::parse) {
625 $var = Some(value);
626 continue;
627 }
628 }
629 };
630 }
631
632 loop {
633 parse_prop!(duration, Time);
634 parse_prop!(timing_function, EasingFunction);
635 parse_prop!(delay, Time);
636 parse_prop!(iteration_count, AnimationIterationCount);
637 parse_prop!(direction, AnimationDirection);
638 parse_prop!(fill_mode, AnimationFillMode);
639 parse_prop!(play_state, AnimationPlayState);
640 parse_prop!(name, AnimationName);
641 parse_prop!(timeline, AnimationTimeline);
642 break;
643 }
644
645 Ok(Animation {
646 name: name.unwrap_or(AnimationName::None),
647 duration: duration.unwrap_or(Time::Seconds(0.0)),
648 timing_function: timing_function.unwrap_or(EasingFunction::Ease),
649 iteration_count: iteration_count.unwrap_or(AnimationIterationCount::Number(1.0)),
650 direction: direction.unwrap_or(AnimationDirection::Normal),
651 play_state: play_state.unwrap_or(AnimationPlayState::Running),
652 delay: delay.unwrap_or(Time::Seconds(0.0)),
653 fill_mode: fill_mode.unwrap_or(AnimationFillMode::None),
654 timeline: timeline.unwrap_or(AnimationTimeline::Auto),
655 })
656 }
657}
658
659impl<'i> ToCss for Animation<'i> {
660 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
661 where
662 W: std::fmt::Write,
663 {
664 match &self.name {
665 AnimationName::None => {}
666 AnimationName::Ident(CustomIdent(name)) | AnimationName::String(CSSString(name)) => {
667 if !self.duration.is_zero() || !self.delay.is_zero() {
668 self.duration.to_css(dest)?;
669 dest.write_char(' ')?;
670 }
671
672 if !self.timing_function.is_ease() || EasingFunction::is_ident(&name) {
673 self.timing_function.to_css(dest)?;
674 dest.write_char(' ')?;
675 }
676
677 if !self.delay.is_zero() {
678 self.delay.to_css(dest)?;
679 dest.write_char(' ')?;
680 }
681
682 if self.iteration_count != AnimationIterationCount::default() || name.as_ref() == "infinite" {
683 self.iteration_count.to_css(dest)?;
684 dest.write_char(' ')?;
685 }
686
687 if self.direction != AnimationDirection::default() || AnimationDirection::parse_string(&name).is_ok() {
688 self.direction.to_css(dest)?;
689 dest.write_char(' ')?;
690 }
691
692 if self.fill_mode != AnimationFillMode::default()
693 || (!name.eq_ignore_ascii_case("none") && AnimationFillMode::parse_string(&name).is_ok())
694 {
695 self.fill_mode.to_css(dest)?;
696 dest.write_char(' ')?;
697 }
698
699 if self.play_state != AnimationPlayState::default() || AnimationPlayState::parse_string(&name).is_ok() {
700 self.play_state.to_css(dest)?;
701 dest.write_char(' ')?;
702 }
703 }
704 }
705
706 self.name.to_css(dest)?;
709
710 if self.name != AnimationName::None && self.timeline != AnimationTimeline::default() {
711 dest.write_char(' ')?;
712 self.timeline.to_css(dest)?;
713 }
714
715 Ok(())
716 }
717}
718
719pub type AnimationList<'i> = SmallVec<[Animation<'i>; 1]>;
721
722#[derive(Default)]
723pub(crate) struct AnimationHandler<'i> {
724 names: Option<(SmallVec<[AnimationName<'i>; 1]>, VendorPrefix)>,
725 durations: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,
726 timing_functions: Option<(SmallVec<[EasingFunction; 1]>, VendorPrefix)>,
727 iteration_counts: Option<(SmallVec<[AnimationIterationCount; 1]>, VendorPrefix)>,
728 directions: Option<(SmallVec<[AnimationDirection; 1]>, VendorPrefix)>,
729 play_states: Option<(SmallVec<[AnimationPlayState; 1]>, VendorPrefix)>,
730 delays: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,
731 fill_modes: Option<(SmallVec<[AnimationFillMode; 1]>, VendorPrefix)>,
732 timelines: Option<SmallVec<[AnimationTimeline<'i>; 1]>>,
733 range_starts: Option<SmallVec<[AnimationRangeStart; 1]>>,
734 range_ends: Option<SmallVec<[AnimationRangeEnd; 1]>>,
735 has_any: bool,
736}
737
738impl<'i> PropertyHandler<'i> for AnimationHandler<'i> {
739 fn handle_property(
740 &mut self,
741 property: &Property<'i>,
742 dest: &mut DeclarationList<'i>,
743 context: &mut PropertyHandlerContext<'i, '_>,
744 ) -> bool {
745 macro_rules! maybe_flush {
746 ($prop: ident, $val: expr, $vp: ident) => {{
747 if let Some((val, prefixes)) = &self.$prop {
750 if val != $val && !prefixes.contains(*$vp) {
751 self.flush(dest, context);
752 }
753 }
754 }};
755 }
756
757 macro_rules! property {
758 ($prop: ident, $val: expr, $vp: ident) => {{
759 maybe_flush!($prop, $val, $vp);
760
761 if let Some((val, prefixes)) = &mut self.$prop {
763 *val = $val.clone();
764 *prefixes |= *$vp;
765 } else {
766 self.$prop = Some(($val.clone(), *$vp));
767 self.has_any = true;
768 }
769 }};
770 }
771
772 match property {
773 Property::AnimationName(val, vp) => property!(names, val, vp),
774 Property::AnimationDuration(val, vp) => property!(durations, val, vp),
775 Property::AnimationTimingFunction(val, vp) => property!(timing_functions, val, vp),
776 Property::AnimationIterationCount(val, vp) => property!(iteration_counts, val, vp),
777 Property::AnimationDirection(val, vp) => property!(directions, val, vp),
778 Property::AnimationPlayState(val, vp) => property!(play_states, val, vp),
779 Property::AnimationDelay(val, vp) => property!(delays, val, vp),
780 Property::AnimationFillMode(val, vp) => property!(fill_modes, val, vp),
781 Property::AnimationTimeline(val) => {
782 self.timelines = Some(val.clone());
783 self.has_any = true;
784 }
785 Property::AnimationRangeStart(val) => {
786 self.range_starts = Some(val.clone());
787 self.has_any = true;
788 }
789 Property::AnimationRangeEnd(val) => {
790 self.range_ends = Some(val.clone());
791 self.has_any = true;
792 }
793 Property::AnimationRange(val) => {
794 self.range_starts = Some(val.iter().map(|v| v.start.clone()).collect());
795 self.range_ends = Some(val.iter().map(|v| v.end.clone()).collect());
796 self.has_any = true;
797 }
798 Property::Animation(val, vp) => {
799 let names = val.iter().map(|b| b.name.clone()).collect();
800 maybe_flush!(names, &names, vp);
801
802 let durations = val.iter().map(|b| b.duration.clone()).collect();
803 maybe_flush!(durations, &durations, vp);
804
805 let timing_functions = val.iter().map(|b| b.timing_function.clone()).collect();
806 maybe_flush!(timing_functions, &timing_functions, vp);
807
808 let iteration_counts = val.iter().map(|b| b.iteration_count.clone()).collect();
809 maybe_flush!(iteration_counts, &iteration_counts, vp);
810
811 let directions = val.iter().map(|b| b.direction.clone()).collect();
812 maybe_flush!(directions, &directions, vp);
813
814 let play_states = val.iter().map(|b| b.play_state.clone()).collect();
815 maybe_flush!(play_states, &play_states, vp);
816
817 let delays = val.iter().map(|b| b.delay.clone()).collect();
818 maybe_flush!(delays, &delays, vp);
819
820 let fill_modes = val.iter().map(|b| b.fill_mode.clone()).collect();
821 maybe_flush!(fill_modes, &fill_modes, vp);
822
823 self.timelines = Some(val.iter().map(|b| b.timeline.clone()).collect());
824
825 property!(names, &names, vp);
826 property!(durations, &durations, vp);
827 property!(timing_functions, &timing_functions, vp);
828 property!(iteration_counts, &iteration_counts, vp);
829 property!(directions, &directions, vp);
830 property!(play_states, &play_states, vp);
831 property!(delays, &delays, vp);
832 property!(fill_modes, &fill_modes, vp);
833
834 self.range_starts = None;
837 self.range_ends = None;
838 }
839 Property::Unparsed(val) if is_animation_property(&val.property_id) => {
840 let mut val = Cow::Borrowed(val);
841 if matches!(val.property_id, PropertyId::Animation(_)) {
842 use crate::properties::custom::Token;
843
844 for token in &mut val.to_mut().value.0 {
847 match token {
848 TokenOrValue::Token(Token::Ident(id)) => {
849 if AnimationDirection::parse_string(&id).is_err()
850 && AnimationPlayState::parse_string(&id).is_err()
851 && AnimationFillMode::parse_string(&id).is_err()
852 && !EasingFunction::is_ident(&id)
853 && id.as_ref() != "infinite"
854 && id.as_ref() != "auto"
855 {
856 *token = TokenOrValue::AnimationName(AnimationName::Ident(CustomIdent(id.clone())));
857 }
858 }
859 TokenOrValue::Token(Token::String(s)) => {
860 *token = TokenOrValue::AnimationName(AnimationName::String(CSSString(s.clone())));
861 }
862 _ => {}
863 }
864 }
865
866 self.range_starts = None;
867 self.range_ends = None;
868 }
869
870 self.flush(dest, context);
871 dest.push(Property::Unparsed(
872 val.get_prefixed(context.targets, Feature::Animation),
873 ));
874 }
875 _ => return false,
876 }
877
878 true
879 }
880
881 fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
882 self.flush(dest, context);
883 }
884}
885
886impl<'i> AnimationHandler<'i> {
887 fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
888 if !self.has_any {
889 return;
890 }
891
892 self.has_any = false;
893
894 let mut names = std::mem::take(&mut self.names);
895 let mut durations = std::mem::take(&mut self.durations);
896 let mut timing_functions = std::mem::take(&mut self.timing_functions);
897 let mut iteration_counts = std::mem::take(&mut self.iteration_counts);
898 let mut directions = std::mem::take(&mut self.directions);
899 let mut play_states = std::mem::take(&mut self.play_states);
900 let mut delays = std::mem::take(&mut self.delays);
901 let mut fill_modes = std::mem::take(&mut self.fill_modes);
902 let mut timelines_value = std::mem::take(&mut self.timelines);
903 let range_starts = std::mem::take(&mut self.range_starts);
904 let range_ends = std::mem::take(&mut self.range_ends);
905
906 if let (
907 Some((names, names_vp)),
908 Some((durations, durations_vp)),
909 Some((timing_functions, timing_functions_vp)),
910 Some((iteration_counts, iteration_counts_vp)),
911 Some((directions, directions_vp)),
912 Some((play_states, play_states_vp)),
913 Some((delays, delays_vp)),
914 Some((fill_modes, fill_modes_vp)),
915 ) = (
916 &mut names,
917 &mut durations,
918 &mut timing_functions,
919 &mut iteration_counts,
920 &mut directions,
921 &mut play_states,
922 &mut delays,
923 &mut fill_modes,
924 ) {
925 let len = names.len();
927 let intersection = *names_vp
928 & *durations_vp
929 & *timing_functions_vp
930 & *iteration_counts_vp
931 & *directions_vp
932 & *play_states_vp
933 & *delays_vp
934 & *fill_modes_vp;
935 let mut timelines = if let Some(timelines) = &mut timelines_value {
936 Cow::Borrowed(timelines)
937 } else if !intersection.contains(VendorPrefix::None) {
938 Cow::Owned(std::iter::repeat(AnimationTimeline::Auto).take(len).collect())
940 } else {
941 Cow::Owned(SmallVec::new())
942 };
943
944 if !intersection.is_empty()
945 && durations.len() == len
946 && timing_functions.len() == len
947 && iteration_counts.len() == len
948 && directions.len() == len
949 && play_states.len() == len
950 && delays.len() == len
951 && fill_modes.len() == len
952 && timelines.len() == len
953 {
954 let timeline_property = if timelines.iter().any(|t| *t != AnimationTimeline::Auto)
955 && (intersection != VendorPrefix::None
956 || !context
957 .targets
958 .is_compatible(crate::compat::Feature::AnimationTimelineShorthand))
959 {
960 Some(Property::AnimationTimeline(timelines.clone().into_owned()))
961 } else {
962 None
963 };
964
965 let animations = izip!(
966 names.drain(..),
967 durations.drain(..),
968 timing_functions.drain(..),
969 iteration_counts.drain(..),
970 directions.drain(..),
971 play_states.drain(..),
972 delays.drain(..),
973 fill_modes.drain(..),
974 timelines.to_mut().drain(..)
975 )
976 .map(
977 |(
978 name,
979 duration,
980 timing_function,
981 iteration_count,
982 direction,
983 play_state,
984 delay,
985 fill_mode,
986 timeline,
987 )| {
988 Animation {
989 name,
990 duration,
991 timing_function,
992 iteration_count,
993 direction,
994 play_state,
995 delay,
996 fill_mode,
997 timeline: if timeline_property.is_some() {
998 AnimationTimeline::Auto
999 } else {
1000 timeline
1001 },
1002 }
1003 },
1004 )
1005 .collect();
1006 let prefix = context.targets.prefixes(intersection, Feature::Animation);
1007 dest.push(Property::Animation(animations, prefix));
1008 names_vp.remove(intersection);
1009 durations_vp.remove(intersection);
1010 timing_functions_vp.remove(intersection);
1011 iteration_counts_vp.remove(intersection);
1012 directions_vp.remove(intersection);
1013 play_states_vp.remove(intersection);
1014 delays_vp.remove(intersection);
1015 fill_modes_vp.remove(intersection);
1016
1017 if let Some(p) = timeline_property {
1018 dest.push(p);
1019 }
1020 timelines_value = None;
1021 }
1022 }
1023
1024 macro_rules! prop {
1025 ($var: ident, $property: ident) => {
1026 if let Some((val, vp)) = $var {
1027 if !vp.is_empty() {
1028 let prefix = context.targets.prefixes(vp, Feature::$property);
1029 dest.push(Property::$property(val, prefix))
1030 }
1031 }
1032 };
1033 }
1034
1035 prop!(names, AnimationName);
1036 prop!(durations, AnimationDuration);
1037 prop!(timing_functions, AnimationTimingFunction);
1038 prop!(iteration_counts, AnimationIterationCount);
1039 prop!(directions, AnimationDirection);
1040 prop!(play_states, AnimationPlayState);
1041 prop!(delays, AnimationDelay);
1042 prop!(fill_modes, AnimationFillMode);
1043
1044 if let Some(val) = timelines_value {
1045 dest.push(Property::AnimationTimeline(val));
1046 }
1047
1048 match (range_starts, range_ends) {
1049 (Some(range_starts), Some(range_ends)) => {
1050 if range_starts.len() == range_ends.len() {
1051 dest.push(Property::AnimationRange(
1052 range_starts
1053 .into_iter()
1054 .zip(range_ends.into_iter())
1055 .map(|(start, end)| AnimationRange { start, end })
1056 .collect(),
1057 ));
1058 } else {
1059 dest.push(Property::AnimationRangeStart(range_starts));
1060 dest.push(Property::AnimationRangeEnd(range_ends));
1061 }
1062 }
1063 (range_starts, range_ends) => {
1064 if let Some(range_starts) = range_starts {
1065 dest.push(Property::AnimationRangeStart(range_starts));
1066 }
1067
1068 if let Some(range_ends) = range_ends {
1069 dest.push(Property::AnimationRangeEnd(range_ends));
1070 }
1071 }
1072 }
1073 }
1074}
1075
1076#[inline]
1077fn is_animation_property(property_id: &PropertyId) -> bool {
1078 match property_id {
1079 PropertyId::AnimationName(_)
1080 | PropertyId::AnimationDuration(_)
1081 | PropertyId::AnimationTimingFunction(_)
1082 | PropertyId::AnimationIterationCount(_)
1083 | PropertyId::AnimationDirection(_)
1084 | PropertyId::AnimationPlayState(_)
1085 | PropertyId::AnimationDelay(_)
1086 | PropertyId::AnimationFillMode(_)
1087 | PropertyId::AnimationComposition
1088 | PropertyId::AnimationTimeline
1089 | PropertyId::AnimationRange
1090 | PropertyId::AnimationRangeStart
1091 | PropertyId::AnimationRangeEnd
1092 | PropertyId::Animation(_) => true,
1093 _ => false,
1094 }
1095}