summaryrefslogtreecommitdiffstats
path: root/servo/components/style/properties/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/properties/helpers')
-rw-r--r--servo/components/style/properties/helpers/animated_properties.mako.rs728
1 files changed, 728 insertions, 0 deletions
diff --git a/servo/components/style/properties/helpers/animated_properties.mako.rs b/servo/components/style/properties/helpers/animated_properties.mako.rs
new file mode 100644
index 0000000000..f1159ac4ec
--- /dev/null
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -0,0 +1,728 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+<%namespace name="helpers" file="/helpers.mako.rs" />
+
+<%
+ from data import to_idl_name, SYSTEM_FONT_LONGHANDS, to_camel_case
+ from itertools import groupby
+%>
+
+#[cfg(feature = "gecko")] use crate::gecko_bindings::structs::nsCSSPropertyID;
+use crate::properties::{CSSWideKeyword, PropertyDeclaration, NonCustomPropertyIterator};
+use crate::properties::longhands;
+use crate::properties::longhands::visibility::computed_value::T as Visibility;
+use crate::properties::LonghandId;
+use servo_arc::Arc;
+use std::ptr;
+use std::mem;
+use fxhash::FxHashMap;
+use super::ComputedValues;
+use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
+use crate::values::animated::effects::AnimatedFilter;
+#[cfg(feature = "gecko")] use crate::values::computed::TransitionProperty;
+use crate::values::computed::{ClipRect, Context};
+use crate::values::computed::ToComputedValue;
+use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
+use crate::values::generics::effects::Filter;
+use void::{self, Void};
+
+/// Convert nsCSSPropertyID to TransitionProperty
+#[cfg(feature = "gecko")]
+#[allow(non_upper_case_globals)]
+impl From<nsCSSPropertyID> for TransitionProperty {
+ fn from(property: nsCSSPropertyID) -> TransitionProperty {
+ use crate::properties::ShorthandId;
+ match property {
+ % for prop in data.longhands:
+ ${prop.nscsspropertyid()} => {
+ TransitionProperty::Longhand(LonghandId::${prop.camel_case})
+ }
+ % endfor
+ % for prop in data.shorthands_except_all():
+ ${prop.nscsspropertyid()} => {
+ TransitionProperty::Shorthand(ShorthandId::${prop.camel_case})
+ }
+ % endfor
+ nsCSSPropertyID::eCSSPropertyExtra_all_properties => {
+ TransitionProperty::Shorthand(ShorthandId::All)
+ }
+ _ => {
+ panic!("non-convertible nsCSSPropertyID")
+ }
+ }
+ }
+}
+
+/// A collection of AnimationValue that were composed on an element.
+/// This HashMap stores the values that are the last AnimationValue to be
+/// composed for each TransitionProperty.
+pub type AnimationValueMap = FxHashMap<LonghandId, AnimationValue>;
+
+/// An enum to represent a single computed value belonging to an animated
+/// property in order to be interpolated with another one. When interpolating,
+/// both values need to belong to the same property.
+///
+/// FIXME: We need to add a path for custom properties, but that's trivial after
+/// this (is a similar path to that of PropertyDeclaration).
+#[derive(Debug, MallocSizeOf)]
+#[repr(u16)]
+pub enum AnimationValue {
+ % for prop in data.longhands:
+ /// `${prop.name}`
+ % if prop.animatable and not prop.logical:
+ ${prop.camel_case}(${prop.animated_type()}),
+ % else:
+ ${prop.camel_case}(Void),
+ % endif
+ % endfor
+}
+
+<%
+ animated = []
+ unanimated = []
+ animated_with_logical = []
+ for prop in data.longhands:
+ if prop.animatable:
+ animated_with_logical.append(prop)
+ if prop.animatable and not prop.logical:
+ animated.append(prop)
+ else:
+ unanimated.append(prop)
+%>
+
+#[repr(C)]
+struct AnimationValueVariantRepr<T> {
+ tag: u16,
+ value: T
+}
+
+impl Clone for AnimationValue {
+ #[inline]
+ fn clone(&self) -> Self {
+ use self::AnimationValue::*;
+
+ <%
+ [copy, others] = [list(g) for _, g in groupby(animated, key=lambda x: not x.specified_is_copy())]
+ %>
+
+ let self_tag = unsafe { *(self as *const _ as *const u16) };
+ if self_tag <= LonghandId::${copy[-1].camel_case} as u16 {
+ #[derive(Clone, Copy)]
+ #[repr(u16)]
+ enum CopyVariants {
+ % for prop in copy:
+ _${prop.camel_case}(${prop.animated_type()}),
+ % endfor
+ }
+
+ unsafe {
+ let mut out = mem::MaybeUninit::uninit();
+ ptr::write(
+ out.as_mut_ptr() as *mut CopyVariants,
+ *(self as *const _ as *const CopyVariants),
+ );
+ return out.assume_init();
+ }
+ }
+
+ match *self {
+ % for ty, props in groupby(others, key=lambda x: x.animated_type()):
+ <% props = list(props) %>
+ ${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => {
+ % if len(props) == 1:
+ ${props[0].camel_case}(value.clone())
+ % else:
+ unsafe {
+ let mut out = mem::MaybeUninit::uninit();
+ ptr::write(
+ out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
+ AnimationValueVariantRepr {
+ tag: *(self as *const _ as *const u16),
+ value: value.clone(),
+ },
+ );
+ out.assume_init()
+ }
+ % endif
+ }
+ % endfor
+ _ => unsafe { debug_unreachable!() }
+ }
+ }
+}
+
+impl PartialEq for AnimationValue {
+ #[inline]
+ fn eq(&self, other: &Self) -> bool {
+ use self::AnimationValue::*;
+
+ unsafe {
+ let this_tag = *(self as *const _ as *const u16);
+ let other_tag = *(other as *const _ as *const u16);
+ if this_tag != other_tag {
+ return false;
+ }
+
+ match *self {
+ % for ty, props in groupby(animated, key=lambda x: x.animated_type()):
+ ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
+ let other_repr =
+ &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
+ *this == other_repr.value
+ }
+ % endfor
+ ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
+ void::unreachable(void)
+ }
+ }
+ }
+ }
+}
+
+impl AnimationValue {
+ /// Returns the longhand id this animated value corresponds to.
+ #[inline]
+ pub fn id(&self) -> LonghandId {
+ let id = unsafe { *(self as *const _ as *const LonghandId) };
+ debug_assert_eq!(id, match *self {
+ % for prop in data.longhands:
+ % if prop.animatable and not prop.logical:
+ AnimationValue::${prop.camel_case}(..) => LonghandId::${prop.camel_case},
+ % else:
+ AnimationValue::${prop.camel_case}(void) => void::unreachable(void),
+ % endif
+ % endfor
+ });
+ id
+ }
+
+ /// "Uncompute" this animation value in order to be used inside the CSS
+ /// cascade.
+ pub fn uncompute(&self) -> PropertyDeclaration {
+ use crate::properties::longhands;
+ use self::AnimationValue::*;
+
+ use super::PropertyDeclarationVariantRepr;
+
+ match *self {
+ <% keyfunc = lambda x: (x.base_type(), x.specified_type(), x.boxed, x.is_animatable_with_computed_value) %>
+ % for (ty, specified, boxed, computed), props in groupby(animated, key=keyfunc):
+ <% props = list(props) %>
+ ${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => {
+ % if not computed:
+ let ref value = ToAnimatedValue::from_animated_value(value.clone());
+ % endif
+ let value = ${ty}::from_computed_value(&value);
+ % if boxed:
+ let value = Box::new(value);
+ % endif
+ % if len(props) == 1:
+ PropertyDeclaration::${props[0].camel_case}(value)
+ % else:
+ unsafe {
+ let mut out = mem::MaybeUninit::uninit();
+ ptr::write(
+ out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${specified}>,
+ PropertyDeclarationVariantRepr {
+ tag: *(self as *const _ as *const u16),
+ value,
+ },
+ );
+ out.assume_init()
+ }
+ % endif
+ }
+ % endfor
+ ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
+ void::unreachable(void)
+ }
+ }
+ }
+
+ /// Construct an AnimationValue from a property declaration.
+ pub fn from_declaration(
+ decl: &PropertyDeclaration,
+ context: &mut Context,
+ extra_custom_properties: Option<<&Arc<crate::custom_properties::CustomPropertiesMap>>,
+ initial: &ComputedValues,
+ ) -> Option<Self> {
+ use super::PropertyDeclarationVariantRepr;
+
+ <%
+ keyfunc = lambda x: (
+ x.specified_type(),
+ x.animated_type(),
+ x.boxed,
+ not x.is_animatable_with_computed_value,
+ x.style_struct.inherited,
+ x.ident in SYSTEM_FONT_LONGHANDS and engine == "gecko",
+ )
+ %>
+
+ let animatable = match *decl {
+ % for (specified_ty, ty, boxed, to_animated, inherit, system), props in groupby(animated_with_logical, key=keyfunc):
+ ${" |\n".join("PropertyDeclaration::{}(ref value)".format(prop.camel_case) for prop in props)} => {
+ let decl_repr = unsafe {
+ &*(decl as *const _ as *const PropertyDeclarationVariantRepr<${specified_ty}>)
+ };
+ let longhand_id = unsafe {
+ *(&decl_repr.tag as *const u16 as *const LonghandId)
+ };
+ % if inherit:
+ context.for_non_inherited_property = None;
+ % else:
+ context.for_non_inherited_property = Some(longhand_id);
+ % endif
+ % if system:
+ if let Some(sf) = value.get_system() {
+ longhands::system_font::resolve_system_font(sf, context)
+ }
+ % endif
+ % if boxed:
+ let value = (**value).to_computed_value(context);
+ % else:
+ let value = value.to_computed_value(context);
+ % endif
+ % if to_animated:
+ let value = value.to_animated_value();
+ % endif
+
+ unsafe {
+ let mut out = mem::MaybeUninit::uninit();
+ ptr::write(
+ out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
+ AnimationValueVariantRepr {
+ tag: longhand_id.to_physical(context.builder.writing_mode) as u16,
+ value,
+ },
+ );
+ out.assume_init()
+ }
+ }
+ % endfor
+ PropertyDeclaration::CSSWideKeyword(ref declaration) => {
+ match declaration.id {
+ // We put all the animatable properties first in the hopes
+ // that it might increase match locality.
+ % for prop in data.longhands:
+ % if prop.animatable:
+ LonghandId::${prop.camel_case} => {
+ // FIXME(emilio, bug 1533327): I think revert (and
+ // revert-layer) handling is not fine here, but what to
+ // do instead?
+ //
+ // Seems we'd need the computed value as if it was
+ // revert, somehow. Treating it as `unset` seems fine
+ // for now...
+ let style_struct = match declaration.keyword {
+ % if not prop.style_struct.inherited:
+ CSSWideKeyword::Revert |
+ CSSWideKeyword::RevertLayer |
+ CSSWideKeyword::Unset |
+ % endif
+ CSSWideKeyword::Initial => {
+ initial.get_${prop.style_struct.name_lower}()
+ },
+ % if prop.style_struct.inherited:
+ CSSWideKeyword::Revert |
+ CSSWideKeyword::RevertLayer |
+ CSSWideKeyword::Unset |
+ % endif
+ CSSWideKeyword::Inherit => {
+ context.builder
+ .get_parent_${prop.style_struct.name_lower}()
+ },
+ };
+ let computed = style_struct
+ % if prop.logical:
+ .clone_${prop.ident}(context.builder.writing_mode);
+ % else:
+ .clone_${prop.ident}();
+ % endif
+
+ % if not prop.is_animatable_with_computed_value:
+ let computed = computed.to_animated_value();
+ % endif
+
+ % if prop.logical:
+ let wm = context.builder.writing_mode;
+ <%helpers:logical_setter_helper name="${prop.name}">
+ <%def name="inner(physical_ident)">
+ AnimationValue::${to_camel_case(physical_ident)}(computed)
+ </%def>
+ </%helpers:logical_setter_helper>
+ % else:
+ AnimationValue::${prop.camel_case}(computed)
+ % endif
+ },
+ % endif
+ % endfor
+ % for prop in data.longhands:
+ % if not prop.animatable:
+ LonghandId::${prop.camel_case} => return None,
+ % endif
+ % endfor
+ }
+ },
+ PropertyDeclaration::WithVariables(ref declaration) => {
+ let mut cache = Default::default();
+ let substituted = {
+ let custom_properties =
+ extra_custom_properties.or_else(|| context.style().custom_properties());
+
+ declaration.value.substitute_variables(
+ declaration.id,
+ context.builder.writing_mode,
+ custom_properties,
+ context.quirks_mode,
+ context.device(),
+ &mut cache,
+ )
+ };
+ return AnimationValue::from_declaration(
+ &substituted,
+ context,
+ extra_custom_properties,
+ initial,
+ )
+ },
+ _ => return None // non animatable properties will get included because of shorthands. ignore.
+ };
+ Some(animatable)
+ }
+
+ /// Get an AnimationValue for an AnimatableLonghand from a given computed values.
+ pub fn from_computed_values(
+ property: LonghandId,
+ style: &ComputedValues,
+ ) -> Option<Self> {
+ let property = property.to_physical(style.writing_mode);
+ Some(match property {
+ % for prop in data.longhands:
+ % if prop.animatable and not prop.logical:
+ LonghandId::${prop.camel_case} => {
+ let computed = style.clone_${prop.ident}();
+ AnimationValue::${prop.camel_case}(
+ % if prop.is_animatable_with_computed_value:
+ computed
+ % else:
+ computed.to_animated_value()
+ % endif
+ )
+ }
+ % endif
+ % endfor
+ _ => return None,
+ })
+ }
+
+ /// Update `style` with the value of this `AnimationValue`.
+ ///
+ /// SERVO ONLY: This doesn't properly handle things like updating 'em' units
+ /// when animated font-size.
+ #[cfg(feature = "servo")]
+ pub fn set_in_style_for_servo(&self, style: &mut ComputedValues) {
+ match self {
+ % for prop in data.longhands:
+ % if prop.animatable and not prop.logical:
+ AnimationValue::${prop.camel_case}(ref value) => {
+ % if not prop.is_animatable_with_computed_value:
+ let value: longhands::${prop.ident}::computed_value::T =
+ ToAnimatedValue::from_animated_value(value.clone());
+ style.mutate_${prop.style_struct.name_lower}().set_${prop.ident}(value);
+ % else:
+ style.mutate_${prop.style_struct.name_lower}().set_${prop.ident}(value.clone());
+ % endif
+ }
+ % else:
+ AnimationValue::${prop.camel_case}(..) => unreachable!(),
+ % endif
+ % endfor
+ }
+ }
+
+ /// As above, but a stub for Gecko.
+ #[cfg(feature = "gecko")]
+ pub fn set_in_style_for_servo(&self, _: &mut ComputedValues) {
+ }
+}
+
+fn animate_discrete<T: Clone>(this: &T, other: &T, procedure: Procedure) -> Result<T, ()> {
+ if let Procedure::Interpolate { progress } = procedure {
+ Ok(if progress < 0.5 { this.clone() } else { other.clone() })
+ } else {
+ Err(())
+ }
+}
+
+impl Animate for AnimationValue {
+ fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
+ Ok(unsafe {
+ use self::AnimationValue::*;
+
+ let this_tag = *(self as *const _ as *const u16);
+ let other_tag = *(other as *const _ as *const u16);
+ if this_tag != other_tag {
+ panic!("Unexpected AnimationValue::animate call");
+ }
+
+ match *self {
+ <% keyfunc = lambda x: (x.animated_type(), x.animation_value_type == "discrete") %>
+ % for (ty, discrete), props in groupby(animated, key=keyfunc):
+ ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
+ let other_repr =
+ &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
+ % if discrete:
+ let value = animate_discrete(this, &other_repr.value, procedure)?;
+ % else:
+ let value = this.animate(&other_repr.value, procedure)?;
+ % endif
+
+ let mut out = mem::MaybeUninit::uninit();
+ ptr::write(
+ out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
+ AnimationValueVariantRepr {
+ tag: this_tag,
+ value,
+ },
+ );
+ out.assume_init()
+ }
+ % endfor
+ ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
+ void::unreachable(void)
+ }
+ }
+ })
+ }
+}
+
+<%
+ nondiscrete = []
+ for prop in animated:
+ if prop.animation_value_type != "discrete":
+ nondiscrete.append(prop)
+%>
+
+impl ComputeSquaredDistance for AnimationValue {
+ fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
+ unsafe {
+ use self::AnimationValue::*;
+
+ let this_tag = *(self as *const _ as *const u16);
+ let other_tag = *(other as *const _ as *const u16);
+ if this_tag != other_tag {
+ panic!("Unexpected AnimationValue::compute_squared_distance call");
+ }
+
+ match *self {
+ % for ty, props in groupby(nondiscrete, key=lambda x: x.animated_type()):
+ ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
+ let other_repr =
+ &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
+
+ this.compute_squared_distance(&other_repr.value)
+ }
+ % endfor
+ _ => Err(()),
+ }
+ }
+ }
+}
+
+impl ToAnimatedZero for AnimationValue {
+ #[inline]
+ fn to_animated_zero(&self) -> Result<Self, ()> {
+ match *self {
+ % for prop in data.longhands:
+ % if prop.animatable and not prop.logical and prop.animation_value_type != "discrete":
+ AnimationValue::${prop.camel_case}(ref base) => {
+ Ok(AnimationValue::${prop.camel_case}(base.to_animated_zero()?))
+ },
+ % endif
+ % endfor
+ _ => Err(()),
+ }
+ }
+}
+
+/// <https://drafts.csswg.org/web-animations-1/#animating-visibility>
+impl Animate for Visibility {
+ #[inline]
+ fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
+ match procedure {
+ Procedure::Interpolate { .. } => {
+ let (this_weight, other_weight) = procedure.weights();
+ match (*self, *other) {
+ (Visibility::Visible, _) => {
+ Ok(if this_weight > 0.0 { *self } else { *other })
+ },
+ (_, Visibility::Visible) => {
+ Ok(if other_weight > 0.0 { *other } else { *self })
+ },
+ _ => Err(()),
+ }
+ },
+ _ => Err(()),
+ }
+ }
+}
+
+impl ComputeSquaredDistance for Visibility {
+ #[inline]
+ fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
+ Ok(SquaredDistance::from_sqrt(if *self == *other { 0. } else { 1. }))
+ }
+}
+
+impl ToAnimatedZero for Visibility {
+ #[inline]
+ fn to_animated_zero(&self) -> Result<Self, ()> {
+ Err(())
+ }
+}
+
+/// <https://drafts.csswg.org/css-transitions/#animtype-rect>
+impl Animate for ClipRect {
+ #[inline]
+ fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
+ use crate::values::computed::LengthOrAuto;
+ let animate_component = |this: &LengthOrAuto, other: &LengthOrAuto| {
+ let result = this.animate(other, procedure)?;
+ if let Procedure::Interpolate { .. } = procedure {
+ return Ok(result);
+ }
+ if result.is_auto() {
+ // FIXME(emilio): Why? A couple SMIL tests fail without this,
+ // but it seems extremely fishy.
+ return Err(());
+ }
+ Ok(result)
+ };
+
+ Ok(ClipRect {
+ top: animate_component(&self.top, &other.top)?,
+ right: animate_component(&self.right, &other.right)?,
+ bottom: animate_component(&self.bottom, &other.bottom)?,
+ left: animate_component(&self.left, &other.left)?,
+ })
+ }
+}
+
+<%
+ FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale',
+ 'HueRotate', 'Invert', 'Opacity', 'Saturate',
+ 'Sepia' ]
+%>
+
+/// <https://drafts.fxtf.org/filters/#animation-of-filters>
+impl Animate for AnimatedFilter {
+ fn animate(
+ &self,
+ other: &Self,
+ procedure: Procedure,
+ ) -> Result<Self, ()> {
+ use crate::values::animated::animate_multiplicative_factor;
+ match (self, other) {
+ % for func in ['Blur', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']:
+ (&Filter::${func}(ref this), &Filter::${func}(ref other)) => {
+ Ok(Filter::${func}(this.animate(other, procedure)?))
+ },
+ % endfor
+ % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']:
+ (&Filter::${func}(this), &Filter::${func}(other)) => {
+ Ok(Filter::${func}(animate_multiplicative_factor(this, other, procedure)?))
+ },
+ % endfor
+ % if engine == "gecko":
+ (&Filter::DropShadow(ref this), &Filter::DropShadow(ref other)) => {
+ Ok(Filter::DropShadow(this.animate(other, procedure)?))
+ },
+ % endif
+ _ => Err(()),
+ }
+ }
+}
+
+/// <http://dev.w3.org/csswg/css-transforms/#none-transform-animation>
+impl ToAnimatedZero for AnimatedFilter {
+ fn to_animated_zero(&self) -> Result<Self, ()> {
+ match *self {
+ % for func in ['Blur', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']:
+ Filter::${func}(ref this) => Ok(Filter::${func}(this.to_animated_zero()?)),
+ % endfor
+ % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']:
+ Filter::${func}(_) => Ok(Filter::${func}(1.)),
+ % endfor
+ % if engine == "gecko":
+ Filter::DropShadow(ref this) => Ok(Filter::DropShadow(this.to_animated_zero()?)),
+ % endif
+ _ => Err(()),
+ }
+ }
+}
+
+/// An iterator over all the properties that transition on a given style.
+pub struct TransitionPropertyIterator<'a> {
+ style: &'a ComputedValues,
+ index_range: core::ops::Range<usize>,
+ longhand_iterator: Option<NonCustomPropertyIterator<LonghandId>>,
+}
+
+impl<'a> TransitionPropertyIterator<'a> {
+ /// Create a `TransitionPropertyIterator` for the given style.
+ pub fn from_style(style: &'a ComputedValues) -> Self {
+ Self {
+ style,
+ index_range: 0..style.get_ui().transition_property_count(),
+ longhand_iterator: None,
+ }
+ }
+}
+
+/// A single iteration of the TransitionPropertyIterator.
+pub struct TransitionPropertyIteration {
+ /// The id of the longhand for this property.
+ pub longhand_id: LonghandId,
+
+ /// The index of this property in the list of transition properties for this
+ /// iterator's style.
+ pub index: usize,
+}
+
+impl<'a> Iterator for TransitionPropertyIterator<'a> {
+ type Item = TransitionPropertyIteration;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ use crate::values::computed::TransitionProperty;
+ loop {
+ if let Some(ref mut longhand_iterator) = self.longhand_iterator {
+ if let Some(longhand_id) = longhand_iterator.next() {
+ return Some(TransitionPropertyIteration {
+ longhand_id,
+ index: self.index_range.start - 1,
+ });
+ }
+ self.longhand_iterator = None;
+ }
+
+ let index = self.index_range.next()?;
+ match self.style.get_ui().transition_property_at(index) {
+ TransitionProperty::Longhand(longhand_id) => {
+ return Some(TransitionPropertyIteration {
+ longhand_id,
+ index,
+ })
+ }
+ // In the other cases, we set up our state so that we are ready to
+ // compute the next value of the iterator and then loop (equivalent
+ // to calling self.next()).
+ TransitionProperty::Shorthand(ref shorthand_id) =>
+ self.longhand_iterator = Some(shorthand_id.longhands()),
+ TransitionProperty::Custom(..) | TransitionProperty::Unsupported(..) => {}
+ }
+ }
+ }
+}