tor-browser

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

animated_properties.mako.rs (30870B)


      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 <%namespace name="helpers" file="/helpers.mako.rs" />
      6 
      7 <%
      8    from data import to_idl_name, SYSTEM_FONT_LONGHANDS, to_camel_case
      9    from itertools import groupby
     10 %>
     11 
     12 #[cfg(feature = "gecko")] use crate::gecko_bindings::structs::NonCustomCSSPropertyId;
     13 use crate::properties::{
     14    longhands::{
     15        self, visibility::computed_value::T as Visibility,
     16    },
     17    CSSWideKeyword, LonghandId, NonCustomPropertyIterator,
     18    PropertyDeclaration, PropertyDeclarationId,
     19 };
     20 #[cfg(feature = "gecko")] use crate::properties::{
     21    longhands::content_visibility::computed_value::T as ContentVisibility,
     22    NonCustomPropertyId,
     23 };
     24 use std::ptr;
     25 use std::mem;
     26 use rustc_hash::FxHashMap;
     27 use super::ComputedValues;
     28 use crate::derives::*;
     29 use crate::properties::OwnedPropertyDeclarationId;
     30 use crate::dom::AttributeProvider;
     31 use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
     32 use crate::values::animated::effects::AnimatedFilter;
     33 #[cfg(feature = "gecko")] use crate::values::computed::TransitionProperty;
     34 use crate::values::computed::{ClipRect, Context};
     35 use crate::values::computed::ToComputedValue;
     36 use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
     37 use crate::values::generics::effects::Filter;
     38 use void::{self, Void};
     39 use crate::properties_and_values::value::CustomAnimatedValue;
     40 use debug_unreachable::debug_unreachable;
     41 
     42 /// Convert NonCustomCSSPropertyId to TransitionProperty
     43 #[cfg(feature = "gecko")]
     44 #[allow(non_upper_case_globals)]
     45 impl From<NonCustomCSSPropertyId> for TransitionProperty {
     46    fn from(property: NonCustomCSSPropertyId) -> TransitionProperty {
     47        TransitionProperty::NonCustom(NonCustomPropertyId::from_noncustomcsspropertyid(property).unwrap())
     48    }
     49 }
     50 
     51 /// A collection of AnimationValue that were composed on an element.
     52 /// This HashMap stores the values that are the last AnimationValue to be
     53 /// composed for each TransitionProperty.
     54 pub type AnimationValueMap = FxHashMap<OwnedPropertyDeclarationId, AnimationValue>;
     55 
     56 /// An enum to represent a single computed value belonging to an animated
     57 /// property in order to be interpolated with another one. When interpolating,
     58 /// both values need to belong to the same property.
     59 #[derive(Debug, MallocSizeOf)]
     60 #[repr(u16)]
     61 pub enum AnimationValue {
     62    % for prop in data.longhands:
     63    /// `${prop.name}`
     64    % if prop.animatable and not prop.logical:
     65    ${prop.camel_case}(${prop.animated_type()}),
     66    % else:
     67    ${prop.camel_case}(Void),
     68    % endif
     69    % endfor
     70    /// A custom property.
     71    Custom(CustomAnimatedValue),
     72 }
     73 
     74 <%
     75    animated = []
     76    unanimated = []
     77    animated_with_logical = []
     78    for prop in data.longhands:
     79        if prop.animatable:
     80            animated_with_logical.append(prop)
     81        if prop.animatable and not prop.logical:
     82            animated.append(prop)
     83        else:
     84            unanimated.append(prop)
     85 %>
     86 
     87 #[repr(C)]
     88 struct AnimationValueVariantRepr<T> {
     89    tag: u16,
     90    value: T
     91 }
     92 
     93 impl Clone for AnimationValue {
     94    #[inline]
     95    fn clone(&self) -> Self {
     96        use self::AnimationValue::*;
     97 
     98        <%
     99            [copy, others] = [list(g) for _, g in groupby(animated, key=lambda x: not x.specified_is_copy())]
    100        %>
    101 
    102        let self_tag = unsafe { *(self as *const _ as *const u16) };
    103        if self_tag <= LonghandId::${copy[-1].camel_case} as u16 {
    104            #[derive(Clone, Copy)]
    105            #[repr(u16)]
    106            enum CopyVariants {
    107                % for prop in copy:
    108                _${prop.camel_case}(${prop.animated_type()}),
    109                % endfor
    110            }
    111 
    112            unsafe {
    113                let mut out = mem::MaybeUninit::uninit();
    114                ptr::write(
    115                    out.as_mut_ptr() as *mut CopyVariants,
    116                    *(self as *const _ as *const CopyVariants),
    117                );
    118                return out.assume_init();
    119            }
    120        }
    121 
    122        match *self {
    123            % for ty, props in groupby(others, key=lambda x: x.animated_type()):
    124            <% props = list(props) %>
    125            ${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => {
    126                % if len(props) == 1:
    127                ${props[0].camel_case}(value.clone())
    128                % else:
    129                unsafe {
    130                    let mut out = mem::MaybeUninit::uninit();
    131                    ptr::write(
    132                        out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
    133                        AnimationValueVariantRepr {
    134                            tag: *(self as *const _ as *const u16),
    135                            value: value.clone(),
    136                        },
    137                    );
    138                    out.assume_init()
    139                }
    140                % endif
    141            }
    142            % endfor
    143            Custom(ref animated_value) => Custom(animated_value.clone()),
    144            _ => unsafe { debug_unreachable!() }
    145        }
    146    }
    147 }
    148 
    149 impl PartialEq for AnimationValue {
    150    #[inline]
    151    fn eq(&self, other: &Self) -> bool {
    152        use self::AnimationValue::*;
    153 
    154        unsafe {
    155            let this_tag = *(self as *const _ as *const u16);
    156            let other_tag = *(other as *const _ as *const u16);
    157            if this_tag != other_tag {
    158                return false;
    159            }
    160 
    161            match *self {
    162                % for ty, props in groupby(animated, key=lambda x: x.animated_type()):
    163                ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
    164                    let other_repr =
    165                        &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
    166                    *this == other_repr.value
    167                }
    168                % endfor
    169                ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
    170                    void::unreachable(void)
    171                },
    172                AnimationValue::Custom(ref this) => {
    173                    let other_repr =
    174                        &*(other as *const _ as *const AnimationValueVariantRepr<CustomAnimatedValue>);
    175                    *this == other_repr.value
    176                },
    177            }
    178        }
    179    }
    180 }
    181 
    182 impl AnimationValue {
    183    /// Returns the longhand id this animated value corresponds to.
    184    #[inline]
    185    pub fn id(&self) -> PropertyDeclarationId<'_> {
    186        if let AnimationValue::Custom(animated_value) = self {
    187            return PropertyDeclarationId::Custom(&animated_value.name);
    188        }
    189 
    190        let id = unsafe { *(self as *const _ as *const LonghandId) };
    191        debug_assert_eq!(id, match *self {
    192            % for prop in data.longhands:
    193            % if prop.animatable and not prop.logical:
    194            AnimationValue::${prop.camel_case}(..) => LonghandId::${prop.camel_case},
    195            % else:
    196            AnimationValue::${prop.camel_case}(void) => void::unreachable(void),
    197            % endif
    198            % endfor
    199            AnimationValue::Custom(..) => unsafe { debug_unreachable!() },
    200        });
    201        PropertyDeclarationId::Longhand(id)
    202    }
    203 
    204    /// Returns whether this value is interpolable with another one.
    205    pub fn interpolable_with(&self, other: &Self) -> bool {
    206        self.animate(other, Procedure::Interpolate { progress: 0.5 }).is_ok()
    207    }
    208 
    209    /// "Uncompute" this animation value in order to be used inside the CSS
    210    /// cascade.
    211    pub fn uncompute(&self) -> PropertyDeclaration {
    212        use crate::properties::longhands;
    213        use self::AnimationValue::*;
    214 
    215        use super::PropertyDeclarationVariantRepr;
    216 
    217        match *self {
    218            <% keyfunc = lambda x: (x.base_type(), x.specified_type(), x.boxed, x.animation_type != "discrete") %>
    219            % for (ty, specified, boxed, to_animated), props in groupby(animated, key=keyfunc):
    220            <% props = list(props) %>
    221            ${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => {
    222                % if to_animated:
    223                let value = ToAnimatedValue::from_animated_value(value.clone());
    224                % endif
    225                let value = ${ty}::from_computed_value(&value);
    226                % if boxed:
    227                let value = Box::new(value);
    228                % endif
    229                % if len(props) == 1:
    230                PropertyDeclaration::${props[0].camel_case}(value)
    231                % else:
    232                unsafe {
    233                    let mut out = mem::MaybeUninit::uninit();
    234                    ptr::write(
    235                        out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${specified}>,
    236                        PropertyDeclarationVariantRepr {
    237                            tag: *(self as *const _ as *const u16),
    238                            value,
    239                        },
    240                    );
    241                    out.assume_init()
    242                }
    243                % endif
    244            }
    245            % endfor
    246            ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
    247                void::unreachable(void)
    248            },
    249            Custom(ref animated_value) => animated_value.to_declaration(),
    250        }
    251    }
    252 
    253    /// Construct an AnimationValue from a property declaration.
    254    pub fn from_declaration(
    255        decl: &PropertyDeclaration,
    256        context: &mut Context,
    257        style: &ComputedValues,
    258        initial: &ComputedValues,
    259        attr_provider: &dyn AttributeProvider,
    260    ) -> Option<Self> {
    261        use super::PropertyDeclarationVariantRepr;
    262 
    263        <%
    264            keyfunc = lambda x: (
    265                x.specified_type(),
    266                x.animated_type(),
    267                x.boxed,
    268                x.animation_type not in ["discrete", "none"],
    269                x.style_struct.inherited,
    270                x.ident in SYSTEM_FONT_LONGHANDS and engine == "gecko",
    271            )
    272        %>
    273 
    274        let animatable = match *decl {
    275            % for (specified_ty, ty, boxed, to_animated, inherit, system), props in groupby(animated_with_logical, key=keyfunc):
    276            ${" |\n".join("PropertyDeclaration::{}(ref value)".format(prop.camel_case) for prop in props)} => {
    277                let decl_repr = unsafe {
    278                    &*(decl as *const _ as *const PropertyDeclarationVariantRepr<${specified_ty}>)
    279                };
    280                let longhand_id = unsafe {
    281                    *(&decl_repr.tag as *const u16 as *const LonghandId)
    282                };
    283                context.for_non_inherited_property = ${"false" if inherit else "true"};
    284                % if system:
    285                if let Some(sf) = value.get_system() {
    286                    longhands::system_font::resolve_system_font(sf, context)
    287                }
    288                % endif
    289                % if boxed:
    290                let value = (**value).to_computed_value(context);
    291                % else:
    292                let value = value.to_computed_value(context);
    293                % endif
    294                % if to_animated:
    295                let value = value.to_animated_value(&crate::values::animated::Context { style });
    296                % endif
    297 
    298                unsafe {
    299                    let mut out = mem::MaybeUninit::uninit();
    300                    ptr::write(
    301                        out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
    302                        AnimationValueVariantRepr {
    303                            tag: longhand_id.to_physical(context.builder.writing_mode) as u16,
    304                            value,
    305                        },
    306                    );
    307                    out.assume_init()
    308                }
    309            }
    310            % endfor
    311            PropertyDeclaration::CSSWideKeyword(ref declaration) => {
    312                match declaration.id.to_physical(context.builder.writing_mode) {
    313                    // We put all the animatable properties first in the hopes
    314                    // that it might increase match locality.
    315                    % for prop in data.longhands:
    316                    % if prop.animatable and not prop.logical:
    317                    LonghandId::${prop.camel_case} => {
    318                        // FIXME(emilio, bug 1533327): I think revert (and
    319                        // revert-layer) handling is not fine here, but what to
    320                        // do instead?
    321                        //
    322                        // Seems we'd need the computed value as if it was
    323                        // revert, somehow. Treating it as `unset` seems fine
    324                        // for now...
    325                        let style_struct = match declaration.keyword {
    326                            % if not prop.style_struct.inherited:
    327                            CSSWideKeyword::Revert |
    328                            CSSWideKeyword::RevertLayer |
    329                            CSSWideKeyword::Unset |
    330                            % endif
    331                            CSSWideKeyword::Initial => {
    332                                initial.get_${prop.style_struct.name_lower}()
    333                            },
    334                            % if prop.style_struct.inherited:
    335                            CSSWideKeyword::Revert |
    336                            CSSWideKeyword::RevertLayer |
    337                            CSSWideKeyword::Unset |
    338                            % endif
    339                            CSSWideKeyword::Inherit => {
    340                                context.builder
    341                                       .get_parent_${prop.style_struct.name_lower}()
    342                            },
    343                        };
    344                        let computed = style_struct
    345                        % if prop.logical:
    346                            .clone_${prop.ident}(context.builder.writing_mode);
    347                        % else:
    348                            .clone_${prop.ident}();
    349                        % endif
    350 
    351                        % if prop.animation_type != "discrete":
    352                        let computed = computed.to_animated_value(&crate::values::animated::Context {
    353                            style
    354                        });
    355                        % endif
    356                        AnimationValue::${prop.camel_case}(computed)
    357                    },
    358                    % endif
    359                    % endfor
    360                    % for prop in data.longhands:
    361                    % if not prop.animatable or prop.logical:
    362                    LonghandId::${prop.camel_case} => return None,
    363                    % endif
    364                    % endfor
    365                }
    366            },
    367            PropertyDeclaration::WithVariables(ref declaration) => {
    368                let mut cache = Default::default();
    369                let substituted = {
    370                    let custom_properties = &context.style().custom_properties();
    371 
    372                    debug_assert!(
    373                        context.builder.stylist.is_some(),
    374                        "Need a Stylist to substitute variables!"
    375                    );
    376                    declaration.value.substitute_variables(
    377                        declaration.id,
    378                        custom_properties,
    379                        context.builder.stylist.unwrap(),
    380                        context,
    381                        &mut cache,
    382                        attr_provider,
    383                    )
    384                };
    385                return AnimationValue::from_declaration(
    386                    &substituted,
    387                    context,
    388                    style,
    389                    initial,
    390                    attr_provider,
    391                )
    392            },
    393            PropertyDeclaration::Custom(ref declaration) => {
    394                AnimationValue::Custom(CustomAnimatedValue::from_declaration(
    395                    declaration,
    396                    context,
    397                    initial,
    398                    attr_provider
    399                )?)
    400            },
    401            _ => return None // non animatable properties will get included because of shorthands. ignore.
    402        };
    403        Some(animatable)
    404    }
    405 
    406    /// Get an AnimationValue for an declaration id from a given computed values.
    407    pub fn from_computed_values(
    408        property: PropertyDeclarationId,
    409        style: &ComputedValues,
    410    ) -> Option<Self> {
    411        let property = match property {
    412            PropertyDeclarationId::Longhand(id) => id,
    413            PropertyDeclarationId::Custom(ref name) => {
    414                // FIXME(bug 1869476): This should use a stylist to determine whether the name
    415                // corresponds to an inherited custom property and then choose the
    416                // inherited/non_inherited map accordingly.
    417                let p = &style.custom_properties();
    418                let value = p.inherited.get(*name).or_else(|| p.non_inherited.get(*name))?;
    419                return Some(AnimationValue::Custom(CustomAnimatedValue::from_computed(name, value)))
    420            }
    421        };
    422 
    423        Some(match property {
    424            % for prop in data.longhands:
    425            % if prop.animatable and not prop.logical:
    426            LonghandId::${prop.camel_case} => {
    427                let computed = style.clone_${prop.ident}();
    428                AnimationValue::${prop.camel_case}(
    429                % if prop.animation_type == "discrete":
    430                    computed
    431                % else:
    432                    computed.to_animated_value(&crate::values::animated::Context { style })
    433                % endif
    434                )
    435            }
    436            % endif
    437            % endfor
    438            _ => return None,
    439        })
    440    }
    441 
    442    /// Update `style` with the value of this `AnimationValue`.
    443    ///
    444    /// SERVO ONLY: This doesn't properly handle things like updating 'em' units
    445    /// when animated font-size.
    446    #[cfg(feature = "servo")]
    447    pub fn set_in_style_for_servo(&self, style: &mut ComputedValues) {
    448        match self {
    449            % for prop in data.longhands:
    450            % if prop.animatable and not prop.logical:
    451            AnimationValue::${prop.camel_case}(ref value) => {
    452                let value: longhands::${prop.ident}::computed_value::T =
    453                % if prop.animation_type != "discrete":
    454                    ToAnimatedValue::from_animated_value(value.clone());
    455                % else:
    456                    value.clone();
    457                % endif
    458                style.mutate_${prop.style_struct.name_lower}().set_${prop.ident}(value);
    459            }
    460            % else:
    461            AnimationValue::${prop.camel_case}(..) => unreachable!(),
    462            % endif
    463            % endfor
    464            AnimationValue::Custom(..) => unreachable!(),
    465        }
    466    }
    467 
    468    /// As above, but a stub for Gecko.
    469    #[cfg(feature = "gecko")]
    470    pub fn set_in_style_for_servo(&self, _: &mut ComputedValues) {
    471    }
    472 }
    473 
    474 fn animate_discrete<T: Clone>(this: &T, other: &T, procedure: Procedure) -> Result<T, ()> {
    475    if let Procedure::Interpolate { progress } = procedure {
    476        Ok(if progress < 0.5 { this.clone() } else { other.clone() })
    477    } else {
    478        Err(())
    479    }
    480 }
    481 
    482 impl Animate for AnimationValue {
    483    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
    484        Ok(unsafe {
    485            use self::AnimationValue::*;
    486 
    487            let this_tag = *(self as *const _ as *const u16);
    488            let other_tag = *(other as *const _ as *const u16);
    489            if this_tag != other_tag {
    490                panic!("Unexpected AnimationValue::animate call");
    491            }
    492 
    493            match *self {
    494                <% keyfunc = lambda x: (x.animated_type(), x.animation_type == "discrete") %>
    495                % for (ty, discrete), props in groupby(animated, key=keyfunc):
    496                ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
    497                    let other_repr =
    498                        &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
    499                    % if discrete:
    500                    let value = animate_discrete(this, &other_repr.value, procedure)?;
    501                    % else:
    502                    let value = this.animate(&other_repr.value, procedure)?;
    503                    % endif
    504 
    505                    let mut out = mem::MaybeUninit::uninit();
    506                    ptr::write(
    507                        out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
    508                        AnimationValueVariantRepr {
    509                            tag: this_tag,
    510                            value,
    511                        },
    512                    );
    513                    out.assume_init()
    514                },
    515                % endfor
    516                ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
    517                    void::unreachable(void)
    518                },
    519                Custom(ref self_value) => {
    520                    let Custom(ref other_value) = *other else { unreachable!() };
    521                    Custom(self_value.animate(other_value, procedure)?)
    522                },
    523            }
    524        })
    525    }
    526 }
    527 
    528 <%
    529    nondiscrete = []
    530    for prop in animated:
    531        if prop.animation_type != "discrete":
    532            nondiscrete.append(prop)
    533 %>
    534 
    535 impl ComputeSquaredDistance for AnimationValue {
    536    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
    537        unsafe {
    538            use self::AnimationValue::*;
    539 
    540            let this_tag = *(self as *const _ as *const u16);
    541            let other_tag = *(other as *const _ as *const u16);
    542            if this_tag != other_tag {
    543                panic!("Unexpected AnimationValue::compute_squared_distance call");
    544            }
    545 
    546            match *self {
    547                % for ty, props in groupby(nondiscrete, key=lambda x: x.animated_type()):
    548                ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
    549                    let other_repr =
    550                        &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
    551 
    552                    this.compute_squared_distance(&other_repr.value)
    553                }
    554                % endfor
    555                _ => Err(()),
    556            }
    557        }
    558    }
    559 }
    560 
    561 impl ToAnimatedZero for AnimationValue {
    562    #[inline]
    563    fn to_animated_zero(&self) -> Result<Self, ()> {
    564        match *self {
    565            % for prop in data.longhands:
    566            % if prop.animatable and not prop.logical and prop.animation_type != "discrete":
    567            AnimationValue::${prop.camel_case}(ref base) => {
    568                Ok(AnimationValue::${prop.camel_case}(base.to_animated_zero()?))
    569            },
    570            % endif
    571            % endfor
    572            AnimationValue::Custom(..) => {
    573                // TODO(bug 1869185): For some non-universal registered custom properties, it may make sense to implement this.
    574                Err(())
    575            },
    576            _ => Err(()),
    577        }
    578    }
    579 }
    580 
    581 /// <https://drafts.csswg.org/web-animations-1/#animating-visibility>
    582 impl Animate for Visibility {
    583    #[inline]
    584    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
    585        match procedure {
    586            Procedure::Interpolate { .. } => {
    587                let (this_weight, other_weight) = procedure.weights();
    588                match (*self, *other) {
    589                    (Visibility::Visible, _) => {
    590                        Ok(if this_weight > 0.0 { *self } else { *other })
    591                    },
    592                    (_, Visibility::Visible) => {
    593                        Ok(if other_weight > 0.0 { *other } else { *self })
    594                    },
    595                    _ => Err(()),
    596                }
    597            },
    598            _ => Err(()),
    599        }
    600    }
    601 }
    602 
    603 impl ComputeSquaredDistance for Visibility {
    604    #[inline]
    605    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
    606        Ok(SquaredDistance::from_sqrt(if *self == *other { 0. } else { 1. }))
    607    }
    608 }
    609 
    610 impl ToAnimatedZero for Visibility {
    611    #[inline]
    612    fn to_animated_zero(&self) -> Result<Self, ()> {
    613        Err(())
    614    }
    615 }
    616 
    617 /// <https://drafts.csswg.org/css-contain-3/#content-visibility-animation>
    618 #[cfg(feature = "gecko")]
    619 impl Animate for ContentVisibility {
    620    #[inline]
    621    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
    622        match procedure {
    623            Procedure::Interpolate { .. } => {
    624                let (this_weight, other_weight) = procedure.weights();
    625                match (*self, *other) {
    626                    (ContentVisibility::Hidden, _) => {
    627                        Ok(if other_weight > 0.0 { *other } else { *self })
    628                    },
    629                    (_, ContentVisibility::Hidden) => {
    630                        Ok(if this_weight > 0.0 { *self } else { *other })
    631                    },
    632                    _ => Err(()),
    633                }
    634            },
    635            _ => Err(()),
    636        }
    637    }
    638 }
    639 
    640 #[cfg(feature = "gecko")]
    641 impl ComputeSquaredDistance for ContentVisibility {
    642    #[inline]
    643    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
    644        Ok(SquaredDistance::from_sqrt(if *self == *other { 0. } else { 1. }))
    645    }
    646 }
    647 
    648 #[cfg(feature = "gecko")]
    649 impl ToAnimatedZero for ContentVisibility {
    650    #[inline]
    651    fn to_animated_zero(&self) -> Result<Self, ()> {
    652        Err(())
    653    }
    654 }
    655 
    656 /// <https://drafts.csswg.org/css-transitions/#animtype-rect>
    657 impl Animate for ClipRect {
    658    #[inline]
    659    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
    660        use crate::values::computed::LengthOrAuto;
    661        let animate_component = |this: &LengthOrAuto, other: &LengthOrAuto| {
    662            let result = this.animate(other, procedure)?;
    663            if let Procedure::Interpolate { .. } = procedure {
    664                return Ok(result);
    665            }
    666            if result.is_auto() {
    667                // FIXME(emilio): Why? A couple SMIL tests fail without this,
    668                // but it seems extremely fishy.
    669                return Err(());
    670            }
    671            Ok(result)
    672        };
    673 
    674        Ok(ClipRect {
    675            top: animate_component(&self.top, &other.top)?,
    676            right: animate_component(&self.right, &other.right)?,
    677            bottom: animate_component(&self.bottom, &other.bottom)?,
    678            left: animate_component(&self.left, &other.left)?,
    679        })
    680    }
    681 }
    682 
    683 <%
    684    FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale',
    685                         'HueRotate', 'Invert', 'Opacity', 'Saturate',
    686                         'Sepia' ]
    687 %>
    688 
    689 /// <https://drafts.fxtf.org/filters/#animation-of-filters>
    690 impl Animate for AnimatedFilter {
    691    fn animate(
    692        &self,
    693        other: &Self,
    694        procedure: Procedure,
    695    ) -> Result<Self, ()> {
    696        use crate::values::animated::animate_multiplicative_factor;
    697        match (self, other) {
    698            % for func in ['Blur', 'DropShadow', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']:
    699            (&Filter::${func}(ref this), &Filter::${func}(ref other)) => {
    700                Ok(Filter::${func}(this.animate(other, procedure)?))
    701            },
    702            % endfor
    703            % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']:
    704            (&Filter::${func}(this), &Filter::${func}(other)) => {
    705                Ok(Filter::${func}(animate_multiplicative_factor(this.0, other.0, procedure)?.into()))
    706            },
    707            % endfor
    708            _ => Err(()),
    709        }
    710    }
    711 }
    712 
    713 /// <http://dev.w3.org/csswg/css-transforms/#none-transform-animation>
    714 impl ToAnimatedZero for AnimatedFilter {
    715    fn to_animated_zero(&self) -> Result<Self, ()> {
    716        match *self {
    717            % for func in ['Blur', 'DropShadow', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']:
    718            Filter::${func}(ref this) => Ok(Filter::${func}(this.to_animated_zero()?)),
    719            % endfor
    720            % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']:
    721            Filter::${func}(_) => Ok(Filter::${func}(1.0.into())),
    722            % endfor
    723            _ => Err(()),
    724        }
    725    }
    726 }
    727 
    728 /// An iterator over all the properties that transition on a given style.
    729 pub struct TransitionPropertyIterator<'a> {
    730    style: &'a ComputedValues,
    731    index_range: core::ops::Range<usize>,
    732    longhand_iterator: Option<NonCustomPropertyIterator<LonghandId>>,
    733 }
    734 
    735 impl<'a> TransitionPropertyIterator<'a> {
    736    /// Create a `TransitionPropertyIterator` for the given style.
    737    pub fn from_style(style: &'a ComputedValues) -> Self {
    738        Self {
    739            style,
    740            index_range: 0..style.get_ui().transition_property_count(),
    741            longhand_iterator: None,
    742        }
    743    }
    744 }
    745 
    746 /// A single iteration of the TransitionPropertyIterator.
    747 pub struct TransitionPropertyIteration {
    748    /// The id of the longhand for this property.
    749    pub property: OwnedPropertyDeclarationId,
    750 
    751    /// The index of this property in the list of transition properties for this
    752    /// iterator's style.
    753    pub index: usize,
    754 }
    755 
    756 impl<'a> Iterator for TransitionPropertyIterator<'a> {
    757    type Item = TransitionPropertyIteration;
    758 
    759    fn next(&mut self) -> Option<Self::Item> {
    760        use crate::values::computed::TransitionProperty;
    761        loop {
    762            if let Some(ref mut longhand_iterator) = self.longhand_iterator {
    763                if let Some(longhand_id) = longhand_iterator.next() {
    764                    return Some(TransitionPropertyIteration {
    765                        property: OwnedPropertyDeclarationId::Longhand(longhand_id),
    766                        index: self.index_range.start - 1,
    767                    });
    768                }
    769                self.longhand_iterator = None;
    770            }
    771 
    772            let index = self.index_range.next()?;
    773            match self.style.get_ui().transition_property_at(index) {
    774                TransitionProperty::NonCustom(id) => {
    775                    match id.longhand_or_shorthand() {
    776                        Ok(longhand_id) => {
    777                            return Some(TransitionPropertyIteration {
    778                                property: OwnedPropertyDeclarationId::Longhand(longhand_id),
    779                                index,
    780                            });
    781                        },
    782                        Err(shorthand_id) => {
    783                            // In the other cases, we set up our state so that we are ready to
    784                            // compute the next value of the iterator and then loop (equivalent
    785                            // to calling self.next()).
    786                            self.longhand_iterator = Some(shorthand_id.longhands());
    787                        },
    788                    }
    789                }
    790                TransitionProperty::Custom(name) => {
    791                    return Some(TransitionPropertyIteration {
    792                        property: OwnedPropertyDeclarationId::Custom(name),
    793                        index,
    794                    })
    795                },
    796                TransitionProperty::Unsupported(..) => {},
    797            }
    798        }
    799    }
    800 }