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