tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

animation.rs (21079B)


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