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::Atom;
8use crate::derives::*;
9use crate::parser::{Parse, ParserContext};
10use crate::properties::{NonCustomPropertyId, PropertyId, ShorthandId};
11use crate::typed_om::ToTyped;
12use crate::values::generics::animation as generics;
13use crate::values::generics::position::{IsTreeScoped, TreeScoped};
14use crate::values::specified::{LengthPercentage, NonNegativeNumber, Time};
15use crate::values::{AtomIdent, CustomIdent, DashedIdent, KeyframesName};
16use cssparser::{Parser, match_ignore_ascii_case};
17use std::fmt::{self, Write};
18use style_traits::{
19    CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss,
20};
21
22/// A given transition property, that is either `All`, a longhand or shorthand
23/// property, or an unsupported or custom property.
24#[derive(
25    Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
26)]
27#[repr(u8)]
28pub enum TransitionProperty {
29    /// A non-custom property.
30    NonCustom(NonCustomPropertyId),
31    /// A custom property.
32    Custom(Atom),
33    /// Unrecognized property which could be any non-transitionable, custom property, or
34    /// unknown property.
35    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(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
58        let ident = input.expect_ident()?;
59
60        let id = match PropertyId::parse_ignoring_rule_type(ident, context) {
61            Ok(id) => id,
62            Err(..) => {
63                // None is not acceptable as a single transition-property.
64                return Ok(TransitionProperty::Unsupported(CustomIdent::from_ident(
65                    ident,
66                    &["none"],
67                )?));
68            },
69        };
70
71        Ok(match id {
72            PropertyId::NonCustom(id) => TransitionProperty::NonCustom(id.unaliased()),
73            PropertyId::Custom(name) => TransitionProperty::Custom(name),
74        })
75    }
76}
77
78impl SpecifiedValueInfo for TransitionProperty {
79    fn collect_completion_keywords(f: KeywordsCollectFn) {
80        // `transition-property` can actually accept all properties and
81        // arbitrary identifiers, but `all` is a special one we'd like
82        // to list.
83        f(&["all"]);
84    }
85}
86
87impl TransitionProperty {
88    /// Returns the `none` value.
89    #[inline]
90    pub fn none() -> Self {
91        TransitionProperty::Unsupported(CustomIdent(atom!("none")))
92    }
93
94    /// Returns whether we're the `none` value.
95    #[inline]
96    pub fn is_none(&self) -> bool {
97        matches!(*self, TransitionProperty::Unsupported(ref ident) if ident.0 == atom!("none"))
98    }
99
100    /// Returns `all`.
101    #[inline]
102    pub fn all() -> Self {
103        TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(ShorthandId::All))
104    }
105
106    /// Returns true if it is `all`.
107    #[inline]
108    pub fn is_all(&self) -> bool {
109        self == &TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(
110            ShorthandId::All,
111        ))
112    }
113}
114
115/// A specified value for <transition-behavior-value>.
116///
117/// https://drafts.csswg.org/css-transitions-2/#transition-behavior-property
118#[derive(
119    Clone,
120    Copy,
121    Debug,
122    MallocSizeOf,
123    Parse,
124    PartialEq,
125    SpecifiedValueInfo,
126    ToComputedValue,
127    ToCss,
128    ToResolvedValue,
129    ToShmem,
130    ToTyped,
131)]
132#[repr(u8)]
133pub enum TransitionBehavior {
134    /// Transitions will not be started for discrete properties, only for interpolable properties.
135    Normal,
136    /// Transitions will be started for discrete properties as well as interpolable properties.
137    AllowDiscrete,
138}
139
140impl TransitionBehavior {
141    /// Return normal, the initial value.
142    #[inline]
143    pub fn normal() -> Self {
144        Self::Normal
145    }
146
147    /// Return true if it is normal.
148    #[inline]
149    pub fn is_normal(&self) -> bool {
150        matches!(*self, Self::Normal)
151    }
152}
153
154/// A specified value for the `animation-duration` property.
155pub type AnimationDuration = generics::GenericAnimationDuration<Time>;
156
157impl Parse for AnimationDuration {
158    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
159        if crate::pref!("layout.css.scroll-driven-animations.enabled")
160            && input.try_parse(|i| i.expect_ident_matching("auto")).is_ok()
161        {
162            return Ok(Self::auto());
163        }
164
165        Time::parse_non_negative(context, input).map(AnimationDuration::Time)
166    }
167}
168
169/// https://drafts.csswg.org/css-animations/#animation-iteration-count
170#[derive(
171    Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
172)]
173pub enum AnimationIterationCount {
174    /// A `<number>` value.
175    Number(NonNegativeNumber),
176    /// The `infinite` keyword.
177    Infinite,
178}
179
180impl AnimationIterationCount {
181    /// Returns the value `1.0`.
182    #[inline]
183    pub fn one() -> Self {
184        Self::Number(NonNegativeNumber::new(1.0))
185    }
186
187    /// Returns true if it's `1.0`.
188    #[inline]
189    pub fn is_one(&self) -> bool {
190        *self == Self::one()
191    }
192}
193
194/// A value for the `animation-name` property.
195#[derive(
196    Clone,
197    Debug,
198    Eq,
199    Hash,
200    MallocSizeOf,
201    PartialEq,
202    SpecifiedValueInfo,
203    ToComputedValue,
204    ToCss,
205    ToResolvedValue,
206    ToShmem,
207    ToTyped,
208)]
209#[value_info(other_values = "none")]
210#[repr(C)]
211pub struct AnimationName(pub KeyframesName);
212
213impl AnimationName {
214    /// Get the name of the animation as an `Atom`.
215    pub fn as_atom(&self) -> Option<&Atom> {
216        if self.is_none() {
217            return None;
218        }
219        Some(self.0.as_atom())
220    }
221
222    /// Returns the `none` value.
223    pub fn none() -> Self {
224        AnimationName(KeyframesName::none())
225    }
226
227    /// Returns whether this is the none value.
228    pub fn is_none(&self) -> bool {
229        self.0.is_none()
230    }
231}
232
233impl Parse for AnimationName {
234    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
235        if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) {
236            return Ok(AnimationName(name));
237        }
238
239        input.expect_ident_matching("none")?;
240        Ok(AnimationName(KeyframesName::none()))
241    }
242}
243
244/// https://drafts.csswg.org/css-animations/#propdef-animation-direction
245#[derive(
246    Copy,
247    Clone,
248    Debug,
249    MallocSizeOf,
250    Parse,
251    PartialEq,
252    SpecifiedValueInfo,
253    ToComputedValue,
254    ToCss,
255    ToResolvedValue,
256    ToShmem,
257    ToTyped,
258)]
259#[repr(u8)]
260#[allow(missing_docs)]
261pub enum AnimationDirection {
262    Normal,
263    Reverse,
264    Alternate,
265    AlternateReverse,
266}
267
268impl AnimationDirection {
269    /// Returns true if the name matches any animation-direction keyword.
270    #[inline]
271    pub fn match_keywords(name: &AnimationName) -> bool {
272        if let Some(name) = name.as_atom() {
273            #[cfg(feature = "gecko")]
274            return name.with_str(|n| Self::from_ident(n).is_ok());
275            #[cfg(feature = "servo")]
276            return Self::from_ident(name).is_ok();
277        }
278        false
279    }
280}
281
282/// https://drafts.csswg.org/css-animations/#animation-play-state
283#[derive(
284    Copy,
285    Clone,
286    Debug,
287    MallocSizeOf,
288    Parse,
289    PartialEq,
290    SpecifiedValueInfo,
291    ToComputedValue,
292    ToCss,
293    ToResolvedValue,
294    ToShmem,
295    ToTyped,
296)]
297#[repr(u8)]
298#[allow(missing_docs)]
299pub enum AnimationPlayState {
300    Running,
301    Paused,
302}
303
304impl AnimationPlayState {
305    /// Returns true if the name matches any animation-play-state keyword.
306    #[inline]
307    pub fn match_keywords(name: &AnimationName) -> bool {
308        if let Some(name) = name.as_atom() {
309            #[cfg(feature = "gecko")]
310            return name.with_str(|n| Self::from_ident(n).is_ok());
311            #[cfg(feature = "servo")]
312            return Self::from_ident(name).is_ok();
313        }
314        false
315    }
316}
317
318/// https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode
319#[derive(
320    Copy,
321    Clone,
322    Debug,
323    MallocSizeOf,
324    Parse,
325    PartialEq,
326    SpecifiedValueInfo,
327    ToComputedValue,
328    ToCss,
329    ToResolvedValue,
330    ToShmem,
331    ToTyped,
332)]
333#[repr(u8)]
334#[allow(missing_docs)]
335pub enum AnimationFillMode {
336    None,
337    Forwards,
338    Backwards,
339    Both,
340}
341
342impl AnimationFillMode {
343    /// Returns true if the name matches any animation-fill-mode keyword.
344    /// Note: animation-name:none is its initial value, so we don't have to match none here.
345    #[inline]
346    pub fn match_keywords(name: &AnimationName) -> bool {
347        if let Some(name) = name.as_atom() {
348            #[cfg(feature = "gecko")]
349            return name.with_str(|n| Self::from_ident(n).is_ok());
350            #[cfg(feature = "servo")]
351            return Self::from_ident(name).is_ok();
352        }
353        false
354    }
355}
356
357/// https://drafts.csswg.org/css-animations-2/#animation-composition
358#[derive(
359    Copy,
360    Clone,
361    Debug,
362    MallocSizeOf,
363    Parse,
364    PartialEq,
365    SpecifiedValueInfo,
366    ToComputedValue,
367    ToCss,
368    ToResolvedValue,
369    ToShmem,
370    ToTyped,
371)]
372#[repr(u8)]
373#[allow(missing_docs)]
374pub enum AnimationComposition {
375    Replace,
376    Add,
377    Accumulate,
378}
379
380/// A value for the <Scroller> used in scroll().
381///
382/// https://drafts.csswg.org/scroll-animations-1/rewrite#typedef-scroller
383#[derive(
384    Copy,
385    Clone,
386    Debug,
387    Eq,
388    Hash,
389    MallocSizeOf,
390    Parse,
391    PartialEq,
392    SpecifiedValueInfo,
393    ToComputedValue,
394    ToCss,
395    ToResolvedValue,
396    ToShmem,
397)]
398#[repr(u8)]
399#[derive(Default)]
400pub enum Scroller {
401    /// The nearest ancestor scroll container. (Default.)
402    #[default]
403    Nearest,
404    /// The document viewport as the scroll container.
405    Root,
406    /// Specifies to use the element’s own principal box as the scroll container.
407    #[css(keyword = "self")]
408    SelfElement,
409}
410
411impl Scroller {
412    /// Returns true if it is default.
413    #[inline]
414    fn is_default(&self) -> bool {
415        matches!(*self, Self::Nearest)
416    }
417}
418
419/// A value for the <Axis> used in scroll(), or a value for {scroll|view}-timeline-axis.
420///
421/// https://drafts.csswg.org/scroll-animations-1/#typedef-axis
422/// https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-axis
423/// https://drafts.csswg.org/scroll-animations-1/#view-timeline-axis
424#[derive(
425    Copy,
426    Clone,
427    Debug,
428    Eq,
429    Hash,
430    MallocSizeOf,
431    Parse,
432    PartialEq,
433    SpecifiedValueInfo,
434    ToComputedValue,
435    ToCss,
436    ToResolvedValue,
437    ToShmem,
438    ToTyped,
439)]
440#[repr(u8)]
441#[derive(Default)]
442pub enum ScrollAxis {
443    /// The block axis of the scroll container. (Default.)
444    #[default]
445    Block = 0,
446    /// The inline axis of the scroll container.
447    Inline = 1,
448    /// The horizontal axis of the scroll container.
449    X = 2,
450    /// The vertical axis of the scroll container.
451    Y = 3,
452}
453
454impl ScrollAxis {
455    /// Returns true if it is default.
456    #[inline]
457    pub fn is_default(&self) -> bool {
458        matches!(*self, Self::Block)
459    }
460}
461
462/// The scroll() notation.
463/// https://drafts.csswg.org/scroll-animations-1/#scroll-notation
464#[derive(
465    Copy,
466    Clone,
467    Debug,
468    MallocSizeOf,
469    PartialEq,
470    SpecifiedValueInfo,
471    ToComputedValue,
472    ToCss,
473    ToResolvedValue,
474    ToShmem,
475)]
476#[css(function = "scroll")]
477#[repr(C)]
478pub struct ScrollFunction {
479    /// The scroll container element whose scroll position drives the progress of the timeline.
480    #[css(skip_if = "Scroller::is_default")]
481    pub scroller: Scroller,
482    /// The axis of scrolling that drives the progress of the timeline.
483    #[css(skip_if = "ScrollAxis::is_default")]
484    pub axis: ScrollAxis,
485}
486
487impl ScrollFunction {
488    /// Parse the inner function arguments of `scroll()`.
489    fn parse_arguments(input: &mut Parser) -> Result<Self, ParseError> {
490        // <scroll()> = scroll( [ <scroller> || <axis> ]? )
491        // https://drafts.csswg.org/scroll-animations-1/#funcdef-scroll
492        let mut scroller = None;
493        let mut axis = None;
494        loop {
495            if scroller.is_none() {
496                scroller = input.try_parse(Scroller::parse).ok();
497            }
498
499            if axis.is_none() {
500                axis = input.try_parse(ScrollAxis::parse).ok();
501                if axis.is_some() {
502                    continue;
503                }
504            }
505            break;
506        }
507
508        Ok(Self {
509            scroller: scroller.unwrap_or_default(),
510            axis: axis.unwrap_or_default(),
511        })
512    }
513}
514
515impl generics::ViewFunction<LengthPercentage> {
516    /// Parse the inner function arguments of `view()`.
517    fn parse_arguments(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
518        // <view()> = view( [ <axis> || <'view-timeline-inset'> ]? )
519        // https://drafts.csswg.org/scroll-animations-1/#funcdef-view
520        let mut axis = None;
521        let mut inset = None;
522        loop {
523            if axis.is_none() {
524                axis = input.try_parse(ScrollAxis::parse).ok();
525            }
526
527            if inset.is_none() {
528                inset = input
529                    .try_parse(|i| ViewTimelineInset::parse(context, i))
530                    .ok();
531                if inset.is_some() {
532                    continue;
533                }
534            }
535            break;
536        }
537
538        Ok(Self {
539            inset: inset.unwrap_or_default(),
540            axis: axis.unwrap_or_default(),
541        })
542    }
543}
544
545/// The typedef of scroll-timeline-name or view-timeline-name.
546///
547/// https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-name
548/// https://drafts.csswg.org/scroll-animations-1/#view-timeline-name
549pub type TimelineName = TreeScoped<TimelineIdent>;
550
551impl TimelineName {
552    /// Return the `none` value.
553    pub fn none() -> Self {
554        Self::with_default_level(TimelineIdent::none())
555    }
556}
557
558/// The identifier for a timeline name.
559#[derive(
560    Clone,
561    Debug,
562    Eq,
563    Hash,
564    MallocSizeOf,
565    PartialEq,
566    SpecifiedValueInfo,
567    ToComputedValue,
568    ToResolvedValue,
569    ToShmem,
570)]
571#[repr(C)]
572pub struct TimelineIdent(DashedIdent);
573
574impl TimelineIdent {
575    /// Returns the `none` value.
576    pub fn none() -> Self {
577        Self(DashedIdent::empty())
578    }
579
580    /// Check if this is `none` value.
581    pub fn is_none(&self) -> bool {
582        self.0.is_empty()
583    }
584}
585
586impl IsTreeScoped for TimelineIdent {
587    fn is_tree_scoped(&self) -> bool {
588        !self.is_none()
589    }
590}
591
592impl Parse for TimelineIdent {
593    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
594        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
595            return Ok(Self::none());
596        }
597
598        DashedIdent::parse(context, input).map(TimelineIdent)
599    }
600}
601
602impl ToCss for TimelineIdent {
603    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
604    where
605        W: Write,
606    {
607        if self.is_none() {
608            return dest.write_str("none");
609        }
610
611        self.0.to_css(dest)
612    }
613}
614
615impl ToTyped for TimelineName {}
616
617/// A specified value for the `animation-timeline` property.
618pub type AnimationTimeline = generics::GenericAnimationTimeline<LengthPercentage>;
619
620impl Parse for AnimationTimeline {
621    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
622        use crate::values::generics::animation::ViewFunction;
623
624        // <single-animation-timeline> = auto | none | <dashed-ident> | <scroll()> | <view()>
625        // https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline
626
627        if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
628            return Ok(Self::Auto);
629        }
630
631        // This parses none or <dashed-indent>.
632        if let Ok(name) = input.try_parse(|i| TimelineName::parse(context, i)) {
633            return Ok(AnimationTimeline::Timeline(name));
634        }
635
636        // Parse <scroll()> or <view()>.
637        let function = input.expect_function()?.clone();
638        input.parse_nested_block(move |i| {
639            match_ignore_ascii_case! { &function,
640                "scroll" => ScrollFunction::parse_arguments(i).map(Self::Scroll),
641                "view" => ViewFunction::parse_arguments(context, i).map(Self::View),
642                _ => {
643                    Err(ParseError::custom(
644                        StyleParseErrorKind::UnexpectedFunction
645                    ))
646                },
647            }
648        })
649    }
650}
651
652/// A specified value for the `view-timeline-inset` property.
653pub type ViewTimelineInset = generics::GenericViewTimelineInset<LengthPercentage>;
654
655impl Parse for ViewTimelineInset {
656    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
657        use crate::values::specified::LengthPercentageOrAuto;
658
659        let start = LengthPercentageOrAuto::parse(context, input)?;
660        let end = match input.try_parse(|input| LengthPercentageOrAuto::parse(context, input)) {
661            Ok(end) => end,
662            Err(_) => start.clone(),
663        };
664
665        Ok(Self { start, end })
666    }
667}
668
669/// The view-transition-name: `none | <custom-ident> | match-element`.
670///
671/// https://drafts.csswg.org/css-view-transitions-1/#view-transition-name-prop
672/// https://drafts.csswg.org/css-view-transitions-2/#auto-vt-name
673// TODO: auto keyword.
674#[derive(
675    Clone,
676    Debug,
677    Eq,
678    Hash,
679    PartialEq,
680    MallocSizeOf,
681    SpecifiedValueInfo,
682    ToCss,
683    ToComputedValue,
684    ToResolvedValue,
685    ToShmem,
686    ToTyped,
687)]
688#[repr(transparent)]
689#[typed(todo_derive_fields)]
690#[value_info(other_values = "none, match-element")]
691pub struct ViewTransitionNameKeyword(AtomIdent);
692
693impl ViewTransitionNameKeyword {
694    /// Returns the `none` value.
695    pub fn none() -> Self {
696        Self(AtomIdent::new(atom!("none")))
697    }
698}
699
700impl IsTreeScoped for ViewTransitionNameKeyword {
701    fn is_tree_scoped(&self) -> bool {
702        self.0.0 != atom!("none")
703    }
704}
705
706impl Parse for ViewTransitionNameKeyword {
707    fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
708        let ident = input.expect_ident()?;
709        if ident.eq_ignore_ascii_case("none") {
710            return Ok(Self::none());
711        }
712
713        if ident.eq_ignore_ascii_case("match-element") {
714            return Ok(Self(AtomIdent::new(atom!("match-element"))));
715        }
716
717        // We check none already, so don't need to exclude none here.
718        // Note: "auto" is not supported yet so we exclude it.
719        CustomIdent::from_ident(ident, &["auto"]).map(|i| Self(AtomIdent::new(i.0)))
720    }
721}
722
723/// https://drafts.csswg.org/css-view-transitions-1/#view-transition-name-prop
724pub type ViewTransitionName = TreeScoped<ViewTransitionNameKeyword>;
725
726impl ViewTransitionName {
727    /// Return the `none` value.
728    pub fn none() -> Self {
729        Self::with_default_level(ViewTransitionNameKeyword::none())
730    }
731}
732
733/// The view-transition-class: `none | <custom-ident>+`.
734///
735/// https://drafts.csswg.org/css-view-transitions-2/#view-transition-class-prop
736///
737/// Empty slice represents `none`.
738#[derive(
739    Clone,
740    Debug,
741    Default,
742    Eq,
743    Hash,
744    PartialEq,
745    MallocSizeOf,
746    SpecifiedValueInfo,
747    ToComputedValue,
748    ToCss,
749    ToResolvedValue,
750    ToShmem,
751    ToTyped,
752)]
753#[repr(C)]
754#[value_info(other_values = "none")]
755pub struct ViewTransitionClassList(
756    #[css(iterable, if_empty = "none")]
757    #[ignore_malloc_size_of = "Arc"]
758    crate::ArcSlice<CustomIdent>,
759);
760
761impl IsTreeScoped for ViewTransitionClassList {
762    fn is_tree_scoped(&self) -> bool {
763        !self.is_none()
764    }
765}
766
767impl ViewTransitionClassList {
768    /// Returns the default value, `none`. We use the default slice (i.e. empty) to represent it.
769    pub fn none() -> Self {
770        Self(Default::default())
771    }
772
773    /// Returns whether this is the `none` value.
774    pub fn is_none(&self) -> bool {
775        self.0.is_empty()
776    }
777
778    /// Iterates over the contained custom idents.
779    pub fn iter(&self) -> impl Iterator<Item = &CustomIdent> {
780        self.0.iter()
781    }
782}
783
784impl Parse for ViewTransitionClassList {
785    fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
786        use style_traits::{Separator, Space};
787
788        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
789            return Ok(Self::none());
790        }
791
792        Ok(Self(crate::ArcSlice::from_iter(
793            Space::parse(input, |i| CustomIdent::parse(i, &["none"]))?.into_iter(),
794        )))
795    }
796}
797
798/// https://drafts.csswg.org/css-view-transitions-2/#view-transition-class-prop
799pub type ViewTransitionClass = TreeScoped<ViewTransitionClassList>;
800
801impl ViewTransitionClass {
802    /// Returns the default value, `none`.
803    pub fn none() -> Self {
804        Self::with_default_level(ViewTransitionClassList::none())
805    }
806}
807
808/// The <timeline-range-name> value type, which indicates a CSS identifier representing one of the
809/// predefined named timeline ranges.
810/// https://drafts.csswg.org/scroll-animations-1/#named-ranges
811///
812/// For now, only view timeline ranges use this type.
813/// https://drafts.csswg.org/scroll-animations-1/#view-timelines-ranges
814#[derive(
815    Copy,
816    Clone,
817    Debug,
818    Eq,
819    MallocSizeOf,
820    Parse,
821    PartialEq,
822    SpecifiedValueInfo,
823    ToComputedValue,
824    ToCss,
825    ToResolvedValue,
826    ToShmem,
827    ToTyped,
828)]
829#[repr(u8)]
830pub enum TimelineRangeName {
831    /// The default normal value.
832    #[css(skip)]
833    Normal,
834    /// No timeline range name specified.
835    #[css(skip)]
836    None,
837    /// Represents the full range of the view progress timeline
838    Cover,
839    /// Represents the range during which the principal box is either fully contained by, or fully
840    /// covers, its view progress visibility range within the scrollport.
841    Contain,
842    /// Represents the range during which the principal box is entering the view progress
843    /// visibility range.
844    Entry,
845    /// Represents the range during which the principal box is exiting the view progress visibility
846    /// range.
847    Exit,
848    /// Represents the range during which the principal box crosses the end border edge.
849    EntryCrossing,
850    /// Represents the range during which the principal box crosses the start border edge.
851    ExitCrossing,
852    /// Represents the full range of the scroll container on which the view progress timeline is
853    /// defined.
854    Scroll,
855}
856
857impl TimelineRangeName {
858    /// Returns true if it is `normal`.
859    #[inline]
860    pub fn is_normal(&self) -> bool {
861        matches!(*self, Self::Normal)
862    }
863
864    /// Returns true if it is `none`.
865    #[inline]
866    pub fn is_none(&self) -> bool {
867        matches!(*self, Self::None)
868    }
869}
870
871/// The internal value for `animation-range-start` and `animation-range-end`.
872pub type AnimationRangeValue = generics::GenericAnimationRangeValue<LengthPercentage>;
873
874fn parse_animation_range(
875    context: &ParserContext,
876    input: &mut Parser,
877    default: LengthPercentage,
878) -> Result<AnimationRangeValue, ParseError> {
879    if input
880        .try_parse(|i| i.expect_ident_matching("normal"))
881        .is_ok()
882    {
883        return Ok(AnimationRangeValue::normal(default));
884    }
885
886    if let Ok(lp) = input.try_parse(|i| LengthPercentage::parse(context, i)) {
887        return Ok(AnimationRangeValue::length_percentage(lp));
888    }
889
890    let name = TimelineRangeName::parse(input)?;
891    let lp = input
892        .try_parse(|i| LengthPercentage::parse(context, i))
893        .unwrap_or(default);
894    Ok(AnimationRangeValue::new(name, lp))
895}
896
897/// A specified value for the `animation-range-start`.
898pub type AnimationRangeStart = generics::GenericAnimationRangeStart<LengthPercentage>;
899
900impl Parse for AnimationRangeStart {
901    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
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(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
911        parse_animation_range(context, input, LengthPercentage::hundred_percent()).map(Self)
912    }
913}