Skip to main content

style/values/specified/
animation.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Specified types for properties related to animations and transitions.
6
7use 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/// A given transition property, that is either `All`, a longhand or shorthand
22/// property, or an unsupported or custom property.
23#[derive(
24    Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
25)]
26#[repr(u8)]
27pub enum TransitionProperty {
28    /// A non-custom property.
29    NonCustom(NonCustomPropertyId),
30    /// A custom property.
31    Custom(Atom),
32    /// Unrecognized property which could be any non-transitionable, custom property, or
33    /// unknown property.
34    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                // None is not acceptable as a single transition-property.
65                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        // `transition-property` can actually accept all properties and
83        // arbitrary identifiers, but `all` is a special one we'd like
84        // to list.
85        f(&["all"]);
86    }
87}
88
89impl TransitionProperty {
90    /// Returns the `none` value.
91    #[inline]
92    pub fn none() -> Self {
93        TransitionProperty::Unsupported(CustomIdent(atom!("none")))
94    }
95
96    /// Returns whether we're the `none` value.
97    #[inline]
98    pub fn is_none(&self) -> bool {
99        matches!(*self, TransitionProperty::Unsupported(ref ident) if ident.0 == atom!("none"))
100    }
101
102    /// Returns `all`.
103    #[inline]
104    pub fn all() -> Self {
105        TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(ShorthandId::All))
106    }
107
108    /// Returns true if it is `all`.
109    #[inline]
110    pub fn is_all(&self) -> bool {
111        self == &TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(
112            ShorthandId::All,
113        ))
114    }
115}
116
117/// A specified value for <transition-behavior-value>.
118///
119/// https://drafts.csswg.org/css-transitions-2/#transition-behavior-property
120#[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    /// Transitions will not be started for discrete properties, only for interpolable properties.
136    Normal,
137    /// Transitions will be started for discrete properties as well as interpolable properties.
138    AllowDiscrete,
139}
140
141impl TransitionBehavior {
142    /// Return normal, the initial value.
143    #[inline]
144    pub fn normal() -> Self {
145        Self::Normal
146    }
147
148    /// Return true if it is normal.
149    #[inline]
150    pub fn is_normal(&self) -> bool {
151        matches!(*self, Self::Normal)
152    }
153}
154
155/// A specified value for the `animation-duration` property.
156pub 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/// https://drafts.csswg.org/css-animations/#animation-iteration-count
174#[derive(
175    Copy, Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
176)]
177pub enum AnimationIterationCount {
178    /// A `<number>` value.
179    Number(NonNegativeNumber),
180    /// The `infinite` keyword.
181    Infinite,
182}
183
184impl AnimationIterationCount {
185    /// Returns the value `1.0`.
186    #[inline]
187    pub fn one() -> Self {
188        Self::Number(NonNegativeNumber::new(1.0))
189    }
190
191    /// Returns true if it's `1.0`.
192    #[inline]
193    pub fn is_one(&self) -> bool {
194        *self == Self::one()
195    }
196}
197
198/// A value for the `animation-name` property.
199#[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    /// Get the name of the animation as an `Atom`.
219    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    /// Returns the `none` value.
227    pub fn none() -> Self {
228        AnimationName(KeyframesName::none())
229    }
230
231    /// Returns whether this is the none value.
232    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/// https://drafts.csswg.org/css-animations/#propdef-animation-direction
252#[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    /// Returns true if the name matches any animation-direction keyword.
277    #[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/// https://drafts.csswg.org/css-animations/#animation-play-state
290#[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    /// Returns true if the name matches any animation-play-state keyword.
313    #[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/// https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode
326#[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    /// Returns true if the name matches any animation-fill-mode keyword.
351    /// Note: animation-name:none is its initial value, so we don't have to match none here.
352    #[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/// https://drafts.csswg.org/css-animations-2/#animation-composition
365#[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/// A value for the <Scroller> used in scroll().
388///
389/// https://drafts.csswg.org/scroll-animations-1/rewrite#typedef-scroller
390#[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    /// The nearest ancestor scroll container. (Default.)
408    Nearest,
409    /// The document viewport as the scroll container.
410    Root,
411    /// Specifies to use the element’s own principal box as the scroll container.
412    #[css(keyword = "self")]
413    SelfElement,
414}
415
416impl Scroller {
417    /// Returns true if it is default.
418    #[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/// A value for the <Axis> used in scroll(), or a value for {scroll|view}-timeline-axis.
431///
432/// https://drafts.csswg.org/scroll-animations-1/#typedef-axis
433/// https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-axis
434/// https://drafts.csswg.org/scroll-animations-1/#view-timeline-axis
435#[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    /// The block axis of the scroll container. (Default.)
453    Block = 0,
454    /// The inline axis of the scroll container.
455    Inline = 1,
456    /// The horizontal axis of the scroll container.
457    X = 2,
458    /// The vertical axis of the scroll container.
459    Y = 3,
460}
461
462impl ScrollAxis {
463    /// Returns true if it is default.
464    #[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/// The scroll() notation.
477/// https://drafts.csswg.org/scroll-animations-1/#scroll-notation
478#[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    /// The scroll container element whose scroll position drives the progress of the timeline.
494    #[css(skip_if = "Scroller::is_default")]
495    pub scroller: Scroller,
496    /// The axis of scrolling that drives the progress of the timeline.
497    #[css(skip_if = "ScrollAxis::is_default")]
498    pub axis: ScrollAxis,
499}
500
501impl ScrollFunction {
502    /// Parse the inner function arguments of `scroll()`.
503    fn parse_arguments<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
504        // <scroll()> = scroll( [ <scroller> || <axis> ]? )
505        // https://drafts.csswg.org/scroll-animations-1/#funcdef-scroll
506        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    /// Parse the inner function arguments of `view()`.
531    fn parse_arguments<'i, 't>(
532        context: &ParserContext,
533        input: &mut Parser<'i, 't>,
534    ) -> Result<Self, ParseError<'i>> {
535        // <view()> = view( [ <axis> || <'view-timeline-inset'> ]? )
536        // https://drafts.csswg.org/scroll-animations-1/#funcdef-view
537        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
562/// The typedef of scroll-timeline-name or view-timeline-name.
563///
564/// https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-name
565/// https://drafts.csswg.org/scroll-animations-1/#view-timeline-name
566pub type TimelineName = TreeScoped<TimelineIdent>;
567
568impl TimelineName {
569    /// Return the `none` value.
570    pub fn none() -> Self {
571        Self::with_default_level(TimelineIdent::none())
572    }
573}
574
575/// The identifier for a timeline name.
576#[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    /// Returns the `none` value.
593    pub fn none() -> Self {
594        Self(DashedIdent::empty())
595    }
596
597    /// Check if this is `none` value.
598    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
629/// A specified value for the `animation-timeline` property.
630pub 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        // <single-animation-timeline> = auto | none | <dashed-ident> | <scroll()> | <view()>
640        // https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline
641
642        if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
643            return Ok(Self::Auto);
644        }
645
646        // This parses none or <dashed-indent>.
647        if let Ok(name) = input.try_parse(|i| TimelineName::parse(context, i)) {
648            return Ok(AnimationTimeline::Timeline(name));
649        }
650
651        // Parse <scroll()> or <view()>.
652        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
668/// A specified value for the `view-timeline-inset` property.
669pub 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/// The view-transition-name: `none | <custom-ident> | match-element`.
689///
690/// https://drafts.csswg.org/css-view-transitions-1/#view-transition-name-prop
691/// https://drafts.csswg.org/css-view-transitions-2/#auto-vt-name
692// TODO: auto keyword.
693#[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 keyword.
709    None,
710    /// match-element keyword.
711    /// https://drafts.csswg.org/css-view-transitions-2/#auto-vt-name
712    MatchElement,
713    /// A `<custom-ident>`.
714    Ident(Atom),
715}
716
717impl ViewTransitionName {
718    /// Returns the `none` value.
719    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        // We check none already, so don't need to exclude none here.
740        // Note: "auto" is not supported yet so we exclude it.
741        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/// The view-transition-class: `none | <custom-ident>+`.
760///
761/// https://drafts.csswg.org/css-view-transitions-2/#view-transition-class-prop
762///
763/// Empty slice represents `none`.
764#[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    /// Returns the default value, `none`. We use the default slice (i.e. empty) to represent it.
788    pub fn none() -> Self {
789        Self(Default::default())
790    }
791
792    /// Returns whether this is the `none` value.
793    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/// The <timeline-range-name> value type, which indicates a CSS identifier representing one of the
816/// predefined named timeline ranges.
817/// https://drafts.csswg.org/scroll-animations-1/#named-ranges
818///
819/// For now, only view timeline ranges use this type.
820/// https://drafts.csswg.org/scroll-animations-1/#view-timelines-ranges
821#[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    /// The default value. No timeline range name specified.
838    #[css(skip)]
839    None,
840    /// Represents the full range of the view progress timeline
841    Cover,
842    /// Represents the range during which the principal box is either fully contained by, or fully
843    /// covers, its view progress visibility range within the scrollport.
844    Contain,
845    /// Represents the range during which the principal box is entering the view progress
846    /// visibility range.
847    Entry,
848    /// Represents the range during which the principal box is exiting the view progress visibility
849    /// range.
850    Exit,
851    /// Represents the range during which the principal box crosses the end border edge.
852    EntryCrossing,
853    /// Represents the range during which the principal box crosses the start border edge.
854    ExitCrossing,
855    /// Represents the full range of the scroll container on which the view progress timeline is
856    /// defined.
857    Scroll,
858}
859
860impl TimelineRangeName {
861    /// Returns true if it is None.
862    #[inline]
863    pub fn is_none(&self) -> bool {
864        matches!(*self, Self::None)
865    }
866}
867
868/// The internal value for `animation-range-start` and `animation-range-end`.
869pub 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
894/// A specified value for the `animation-range-start`.
895pub 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
906/// A specified value for the `animation-range-end`.
907pub 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}