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::nsCSSPropertyID;
13 use itertools::{EitherOrBoth, Itertools};
14 use crate::properties::{CSSWideKeyword, PropertyDeclaration, NonCustomPropertyIterator};
15 use crate::properties::longhands;
16 use crate::properties::longhands::visibility::computed_value::T as Visibility;
17 use crate::properties::LonghandId;
18 use servo_arc::Arc;
19 use smallvec::SmallVec;
20 use std::ptr;
21 use std::mem;
22 use crate::hash::FxHashMap;
23 use super::ComputedValues;
24 use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
25 use crate::values::animated::effects::AnimatedFilter;
26 #[cfg(feature = "gecko")] use crate::values::computed::TransitionProperty;
27 use crate::values::computed::{ClipRect, Context};
28 use crate::values::computed::ToComputedValue;
29 use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
30 use crate::values::generics::effects::Filter;
31 use void::{self, Void};
32 
33 /// Convert nsCSSPropertyID to TransitionProperty
34 #[cfg(feature = "gecko")]
35 #[allow(non_upper_case_globals)]
36 impl From<nsCSSPropertyID> for TransitionProperty {
from(property: nsCSSPropertyID) -> TransitionProperty37     fn from(property: nsCSSPropertyID) -> TransitionProperty {
38         use properties::ShorthandId;
39         match property {
40             % for prop in data.longhands:
41             ${prop.nscsspropertyid()} => {
42                 TransitionProperty::Longhand(LonghandId::${prop.camel_case})
43             }
44             % endfor
45             % for prop in data.shorthands_except_all():
46             ${prop.nscsspropertyid()} => {
47                 TransitionProperty::Shorthand(ShorthandId::${prop.camel_case})
48             }
49             % endfor
50             nsCSSPropertyID::eCSSPropertyExtra_all_properties => {
51                 TransitionProperty::Shorthand(ShorthandId::All)
52             }
53             _ => {
54                 panic!("non-convertible nsCSSPropertyID")
55             }
56         }
57     }
58 }
59 
60 /// An animated property interpolation between two computed values for that
61 /// property.
62 #[derive(Clone, Debug, PartialEq)]
63 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
64 pub enum AnimatedProperty {
65     % for prop in data.longhands:
66         % if prop.animatable and not prop.logical:
67             <%
68                 value_type = "longhands::{}::computed_value::T".format(prop.ident)
69                 if not prop.is_animatable_with_computed_value:
70                     value_type = "<{} as ToAnimatedValue>::AnimatedValue".format(value_type)
71             %>
72             /// ${prop.name}
73             ${prop.camel_case}(${value_type}, ${value_type}),
74         % endif
75     % endfor
76 }
77 
78 impl AnimatedProperty {
79     /// Get the id of the property we're animating.
id(&self) -> LonghandId80     pub fn id(&self) -> LonghandId {
81         match *self {
82             % for prop in data.longhands:
83             % if prop.animatable and not prop.logical:
84             AnimatedProperty::${prop.camel_case}(..) => LonghandId::${prop.camel_case},
85             % endif
86             % endfor
87         }
88     }
89 
90     /// Get the name of this property.
name(&self) -> &'static str91     pub fn name(&self) -> &'static str {
92         self.id().name()
93     }
94 
95     /// Whether this interpolation does animate, that is, whether the start and
96     /// end values are different.
does_animate(&self) -> bool97     pub fn does_animate(&self) -> bool {
98         match *self {
99             % for prop in data.longhands:
100                 % if prop.animatable and not prop.logical:
101                     AnimatedProperty::${prop.camel_case}(ref from, ref to) => from != to,
102                 % endif
103             % endfor
104         }
105     }
106 
107     /// Whether an animated property has the same end value as another.
has_the_same_end_value_as(&self, other: &Self) -> bool108     pub fn has_the_same_end_value_as(&self, other: &Self) -> bool {
109         match (self, other) {
110             % for prop in data.longhands:
111                 % if prop.animatable and not prop.logical:
112                     (&AnimatedProperty::${prop.camel_case}(_, ref this_end_value),
113                      &AnimatedProperty::${prop.camel_case}(_, ref other_end_value)) => {
114                         this_end_value == other_end_value
115                     }
116                 % endif
117             % endfor
118             _ => false,
119         }
120     }
121 
122     /// Update `style` with the proper computed style corresponding to this
123     /// animation at `progress`.
124     #[cfg_attr(feature = "gecko", allow(unused))]
update(&self, style: &mut ComputedValues, progress: f64)125     pub fn update(&self, style: &mut ComputedValues, progress: f64) {
126         #[cfg(feature = "servo")]
127         {
128             match *self {
129                 % for prop in data.longhands:
130                 % if prop.animatable and not prop.logical:
131                     AnimatedProperty::${prop.camel_case}(ref from, ref to) => {
132                         // https://drafts.csswg.org/web-animations/#discrete-animation-type
133                         % if prop.animation_value_type == "discrete":
134                             let value = if progress < 0.5 { from.clone() } else { to.clone() };
135                         % else:
136                             let value = match from.animate(to, Procedure::Interpolate { progress }) {
137                                 Ok(value) => value,
138                                 Err(()) => return,
139                             };
140                         % endif
141                         % if not prop.is_animatable_with_computed_value:
142                             let value: longhands::${prop.ident}::computed_value::T =
143                                 ToAnimatedValue::from_animated_value(value);
144                         % endif
145                         style.mutate_${prop.style_struct.name_lower}().set_${prop.ident}(value);
146                     }
147                 % endif
148                 % endfor
149             }
150         }
151     }
152 
153     /// Get an animatable value from a transition-property, an old style, and a
154     /// new style.
from_longhand( property: LonghandId, old_style: &ComputedValues, new_style: &ComputedValues, ) -> Option<AnimatedProperty>155     pub fn from_longhand(
156         property: LonghandId,
157         old_style: &ComputedValues,
158         new_style: &ComputedValues,
159     ) -> Option<AnimatedProperty> {
160         // FIXME(emilio): Handle the case where old_style and new_style's
161         // writing mode differ.
162         let property = property.to_physical(new_style.writing_mode);
163         Some(match property {
164             % for prop in data.longhands:
165             % if prop.animatable and not prop.logical:
166                 LonghandId::${prop.camel_case} => {
167                     let old_computed = old_style.clone_${prop.ident}();
168                     let new_computed = new_style.clone_${prop.ident}();
169                     AnimatedProperty::${prop.camel_case}(
170                     % if prop.is_animatable_with_computed_value:
171                         old_computed,
172                         new_computed,
173                     % else:
174                         old_computed.to_animated_value(),
175                         new_computed.to_animated_value(),
176                     % endif
177                     )
178                 }
179             % endif
180             % endfor
181             _ => return None,
182         })
183     }
184 }
185 
186 /// A collection of AnimationValue that were composed on an element.
187 /// This HashMap stores the values that are the last AnimationValue to be
188 /// composed for each TransitionProperty.
189 pub type AnimationValueMap = FxHashMap<LonghandId, AnimationValue>;
190 
191 /// An enum to represent a single computed value belonging to an animated
192 /// property in order to be interpolated with another one. When interpolating,
193 /// both values need to belong to the same property.
194 ///
195 /// This is different to AnimatedProperty in the sense that AnimatedProperty
196 /// also knows the final value to be used during the animation.
197 ///
198 /// This is to be used in Gecko integration code.
199 ///
200 /// FIXME: We need to add a path for custom properties, but that's trivial after
201 /// this (is a similar path to that of PropertyDeclaration).
202 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
203 #[derive(Debug)]
204 #[repr(u16)]
205 pub enum AnimationValue {
206     % for prop in data.longhands:
207     /// `${prop.name}`
208     % if prop.animatable and not prop.logical:
209     ${prop.camel_case}(${prop.animated_type()}),
210     % else:
211     ${prop.camel_case}(Void),
212     % endif
213     % endfor
214 }
215 
216 <%
217     animated = []
218     unanimated = []
219     animated_with_logical = []
220     for prop in data.longhands:
221         if prop.animatable:
222             animated_with_logical.append(prop)
223         if prop.animatable and not prop.logical:
224             animated.append(prop)
225         else:
226             unanimated.append(prop)
227 %>
228 
229 #[repr(C)]
230 struct AnimationValueVariantRepr<T> {
231     tag: u16,
232     value: T
233 }
234 
235 impl Clone for AnimationValue {
236     #[inline]
clone(&self) -> Self237     fn clone(&self) -> Self {
238         use self::AnimationValue::*;
239 
240         <%
241             [copy, others] = [list(g) for _, g in groupby(animated, key=lambda x: not x.specified_is_copy())]
242         %>
243 
244         let self_tag = unsafe { *(self as *const _ as *const u16) };
245         if self_tag <= LonghandId::${copy[-1].camel_case} as u16 {
246             #[derive(Clone, Copy)]
247             #[repr(u16)]
248             enum CopyVariants {
249                 % for prop in copy:
250                 _${prop.camel_case}(${prop.animated_type()}),
251                 % endfor
252             }
253 
254             unsafe {
255                 let mut out = mem::MaybeUninit::uninit();
256                 ptr::write(
257                     out.as_mut_ptr() as *mut CopyVariants,
258                     *(self as *const _ as *const CopyVariants),
259                 );
260                 return out.assume_init();
261             }
262         }
263 
264         match *self {
265             % for ty, props in groupby(others, key=lambda x: x.animated_type()):
266             <% props = list(props) %>
267             ${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => {
268                 % if len(props) == 1:
269                 ${props[0].camel_case}(value.clone())
270                 % else:
271                 unsafe {
272                     let mut out = mem::MaybeUninit::uninit();
273                     ptr::write(
274                         out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
275                         AnimationValueVariantRepr {
276                             tag: *(self as *const _ as *const u16),
277                             value: value.clone(),
278                         },
279                     );
280                     out.assume_init()
281                 }
282                 % endif
283             }
284             % endfor
285             _ => unsafe { debug_unreachable!() }
286         }
287     }
288 }
289 
290 impl PartialEq for AnimationValue {
291     #[inline]
eq(&self, other: &Self) -> bool292     fn eq(&self, other: &Self) -> bool {
293         use self::AnimationValue::*;
294 
295         unsafe {
296             let this_tag = *(self as *const _ as *const u16);
297             let other_tag = *(other as *const _ as *const u16);
298             if this_tag != other_tag {
299                 return false;
300             }
301 
302             match *self {
303                 % for ty, props in groupby(animated, key=lambda x: x.animated_type()):
304                 ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
305                     let other_repr =
306                         &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
307                     *this == other_repr.value
308                 }
309                 % endfor
310                 ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
311                     void::unreachable(void)
312                 }
313             }
314         }
315     }
316 }
317 
318 impl AnimationValue {
319     /// Returns the longhand id this animated value corresponds to.
320     #[inline]
id(&self) -> LonghandId321     pub fn id(&self) -> LonghandId {
322         let id = unsafe { *(self as *const _ as *const LonghandId) };
323         debug_assert_eq!(id, match *self {
324             % for prop in data.longhands:
325             % if prop.animatable and not prop.logical:
326             AnimationValue::${prop.camel_case}(..) => LonghandId::${prop.camel_case},
327             % else:
328             AnimationValue::${prop.camel_case}(void) => void::unreachable(void),
329             % endif
330             % endfor
331         });
332         id
333     }
334 
335     /// "Uncompute" this animation value in order to be used inside the CSS
336     /// cascade.
uncompute(&self) -> PropertyDeclaration337     pub fn uncompute(&self) -> PropertyDeclaration {
338         use crate::properties::longhands;
339         use self::AnimationValue::*;
340 
341         use super::PropertyDeclarationVariantRepr;
342 
343         match *self {
344             <% keyfunc = lambda x: (x.base_type(), x.specified_type(), x.boxed, x.is_animatable_with_computed_value) %>
345             % for (ty, specified, boxed, computed), props in groupby(animated, key=keyfunc):
346             <% props = list(props) %>
347             ${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => {
348                 % if not computed:
349                 let ref value = ToAnimatedValue::from_animated_value(value.clone());
350                 % endif
351                 let value = ${ty}::from_computed_value(&value);
352                 % if boxed:
353                 let value = Box::new(value);
354                 % endif
355                 % if len(props) == 1:
356                 PropertyDeclaration::${props[0].camel_case}(value)
357                 % else:
358                 unsafe {
359                     let mut out = mem::MaybeUninit::uninit();
360                     ptr::write(
361                         out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${specified}>,
362                         PropertyDeclarationVariantRepr {
363                             tag: *(self as *const _ as *const u16),
364                             value,
365                         },
366                     );
367                     out.assume_init()
368                 }
369                 % endif
370             }
371             % endfor
372             ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
373                 void::unreachable(void)
374             }
375         }
376     }
377 
378     /// Construct an AnimationValue from a property declaration.
from_declaration( decl: &PropertyDeclaration, context: &mut Context, extra_custom_properties: Option<<&Arc<crate::custom_properties::CustomPropertiesMap>>, initial: &ComputedValues ) -> Option<Self>379     pub fn from_declaration(
380         decl: &PropertyDeclaration,
381         context: &mut Context,
382         extra_custom_properties: Option<<&Arc<crate::custom_properties::CustomPropertiesMap>>,
383         initial: &ComputedValues
384     ) -> Option<Self> {
385         use super::PropertyDeclarationVariantRepr;
386 
387         <%
388             keyfunc = lambda x: (
389                 x.specified_type(),
390                 x.animated_type(),
391                 x.boxed,
392                 not x.is_animatable_with_computed_value,
393                 x.style_struct.inherited,
394                 x.ident in SYSTEM_FONT_LONGHANDS and engine == "gecko",
395             )
396         %>
397 
398         let animatable = match *decl {
399             % for (specified_ty, ty, boxed, to_animated, inherit, system), props in groupby(animated_with_logical, key=keyfunc):
400             ${" |\n".join("PropertyDeclaration::{}(ref value)".format(prop.camel_case) for prop in props)} => {
401                 let decl_repr = unsafe {
402                     &*(decl as *const _ as *const PropertyDeclarationVariantRepr<${specified_ty}>)
403                 };
404                 let longhand_id = unsafe {
405                     *(&decl_repr.tag as *const u16 as *const LonghandId)
406                 };
407                 % if inherit:
408                 context.for_non_inherited_property = None;
409                 % else:
410                 context.for_non_inherited_property = Some(longhand_id);
411                 % endif
412                 % if system:
413                 if let Some(sf) = value.get_system() {
414                     longhands::system_font::resolve_system_font(sf, context)
415                 }
416                 % endif
417                 % if boxed:
418                 let value = (**value).to_computed_value(context);
419                 % else:
420                 let value = value.to_computed_value(context);
421                 % endif
422                 % if to_animated:
423                 let value = value.to_animated_value();
424                 % endif
425 
426                 unsafe {
427                     let mut out = mem::MaybeUninit::uninit();
428                     ptr::write(
429                         out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
430                         AnimationValueVariantRepr {
431                             tag: longhand_id.to_physical(context.builder.writing_mode) as u16,
432                             value,
433                         },
434                     );
435                     out.assume_init()
436                 }
437             }
438             % endfor
439             PropertyDeclaration::CSSWideKeyword(ref declaration) => {
440                 match declaration.id {
441                     // We put all the animatable properties first in the hopes
442                     // that it might increase match locality.
443                     % for prop in data.longhands:
444                     % if prop.animatable:
445                     LonghandId::${prop.camel_case} => {
446                         // FIXME(emilio, bug 1533327): I think
447                         // CSSWideKeyword::Revert handling is not fine here, but
448                         // what to do instead?
449                         //
450                         // Seems we'd need the computed value as if it was
451                         // revert, somehow. Treating it as `unset` seems fine
452                         // for now...
453                         let style_struct = match declaration.keyword {
454                             % if not prop.style_struct.inherited:
455                             CSSWideKeyword::Revert |
456                             CSSWideKeyword::Unset |
457                             % endif
458                             CSSWideKeyword::Initial => {
459                                 initial.get_${prop.style_struct.name_lower}()
460                             },
461                             % if prop.style_struct.inherited:
462                             CSSWideKeyword::Revert |
463                             CSSWideKeyword::Unset |
464                             % endif
465                             CSSWideKeyword::Inherit => {
466                                 context.builder
467                                        .get_parent_${prop.style_struct.name_lower}()
468                             },
469                         };
470                         let computed = style_struct
471                         % if prop.logical:
472                             .clone_${prop.ident}(context.builder.writing_mode);
473                         % else:
474                             .clone_${prop.ident}();
475                         % endif
476 
477                         % if not prop.is_animatable_with_computed_value:
478                         let computed = computed.to_animated_value();
479                         % endif
480 
481                         % if prop.logical:
482                         let wm = context.builder.writing_mode;
483                         <%helpers:logical_setter_helper name="${prop.name}">
484                         <%def name="inner(physical_ident)">
485                             AnimationValue::${to_camel_case(physical_ident)}(computed)
486                         </%def>
487                         </%helpers:logical_setter_helper>
488                         % else:
489                             AnimationValue::${prop.camel_case}(computed)
490                         % endif
491                     },
492                     % endif
493                     % endfor
494                     % for prop in data.longhands:
495                     % if not prop.animatable:
496                     LonghandId::${prop.camel_case} => return None,
497                     % endif
498                     % endfor
499                 }
500             },
501             PropertyDeclaration::WithVariables(ref declaration) => {
502                 let substituted = {
503                     let custom_properties =
504                         extra_custom_properties.or_else(|| context.style().custom_properties());
505 
506                     declaration.value.substitute_variables(
507                         declaration.id,
508                         custom_properties,
509                         context.quirks_mode,
510                         context.device(),
511                     )
512                 };
513                 return AnimationValue::from_declaration(
514                     &substituted,
515                     context,
516                     extra_custom_properties,
517                     initial,
518                 )
519             },
520             _ => return None // non animatable properties will get included because of shorthands. ignore.
521         };
522         Some(animatable)
523     }
524 
525     /// Get an AnimationValue for an AnimatableLonghand from a given computed values.
from_computed_values( property: LonghandId, style: &ComputedValues, ) -> Option<Self>526     pub fn from_computed_values(
527         property: LonghandId,
528         style: &ComputedValues,
529     ) -> Option<Self> {
530         let property = property.to_physical(style.writing_mode);
531         Some(match property {
532             % for prop in data.longhands:
533             % if prop.animatable and not prop.logical:
534             LonghandId::${prop.camel_case} => {
535                 let computed = style.clone_${prop.ident}();
536                 AnimationValue::${prop.camel_case}(
537                 % if prop.is_animatable_with_computed_value:
538                     computed
539                 % else:
540                     computed.to_animated_value()
541                 % endif
542                 )
543             }
544             % endif
545             % endfor
546             _ => return None,
547         })
548     }
549 }
550 
animate_discrete<T: Clone>(this: &T, other: &T, procedure: Procedure) -> Result<T, ()>551 fn animate_discrete<T: Clone>(this: &T, other: &T, procedure: Procedure) -> Result<T, ()> {
552     if let Procedure::Interpolate { progress } = procedure {
553         Ok(if progress < 0.5 { this.clone() } else { other.clone() })
554     } else {
555         Err(())
556     }
557 }
558 
559 impl Animate for AnimationValue {
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>560     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
561         Ok(unsafe {
562             use self::AnimationValue::*;
563 
564             let this_tag = *(self as *const _ as *const u16);
565             let other_tag = *(other as *const _ as *const u16);
566             if this_tag != other_tag {
567                 panic!("Unexpected AnimationValue::animate call");
568             }
569 
570             match *self {
571                 <% keyfunc = lambda x: (x.animated_type(), x.animation_value_type == "discrete") %>
572                 % for (ty, discrete), props in groupby(animated, key=keyfunc):
573                 ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
574                     let other_repr =
575                         &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
576                     % if discrete:
577                     let value = animate_discrete(this, &other_repr.value, procedure)?;
578                     % else:
579                     let value = this.animate(&other_repr.value, procedure)?;
580                     % endif
581 
582                     let mut out = mem::MaybeUninit::uninit();
583                     ptr::write(
584                         out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
585                         AnimationValueVariantRepr {
586                             tag: this_tag,
587                             value,
588                         },
589                     );
590                     out.assume_init()
591                 }
592                 % endfor
593                 ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
594                     void::unreachable(void)
595                 }
596             }
597         })
598     }
599 }
600 
601 <%
602     nondiscrete = []
603     for prop in animated:
604         if prop.animation_value_type != "discrete":
605             nondiscrete.append(prop)
606 %>
607 
608 impl ComputeSquaredDistance for AnimationValue {
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>609     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
610         unsafe {
611             use self::AnimationValue::*;
612 
613             let this_tag = *(self as *const _ as *const u16);
614             let other_tag = *(other as *const _ as *const u16);
615             if this_tag != other_tag {
616                 panic!("Unexpected AnimationValue::compute_squared_distance call");
617             }
618 
619             match *self {
620                 % for ty, props in groupby(nondiscrete, key=lambda x: x.animated_type()):
621                 ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
622                     let other_repr =
623                         &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
624 
625                     this.compute_squared_distance(&other_repr.value)
626                 }
627                 % endfor
628                 _ => Err(()),
629             }
630         }
631     }
632 }
633 
634 impl ToAnimatedZero for AnimationValue {
635     #[inline]
to_animated_zero(&self) -> Result<Self, ()>636     fn to_animated_zero(&self) -> Result<Self, ()> {
637         match *self {
638             % for prop in data.longhands:
639             % if prop.animatable and not prop.logical and prop.animation_value_type != "discrete":
640             AnimationValue::${prop.camel_case}(ref base) => {
641                 Ok(AnimationValue::${prop.camel_case}(base.to_animated_zero()?))
642             },
643             % endif
644             % endfor
645             _ => Err(()),
646         }
647     }
648 }
649 
650 /// A trait to abstract away the different kind of animations over a list that
651 /// there may be.
652 pub trait ListAnimation<T> : Sized {
653     /// <https://drafts.csswg.org/css-transitions/#animtype-repeatable-list>
animate_repeatable_list(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> where T: Animate654     fn animate_repeatable_list(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>
655     where
656         T: Animate;
657 
658     /// <https://drafts.csswg.org/css-transitions/#animtype-repeatable-list>
squared_distance_repeatable_list(&self, other: &Self) -> Result<SquaredDistance, ()> where T: ComputeSquaredDistance659     fn squared_distance_repeatable_list(&self, other: &Self) -> Result<SquaredDistance, ()>
660     where
661         T: ComputeSquaredDistance;
662 
663     /// This is the animation used for some of the types like shadows and
664     /// filters, where the interpolation happens with the zero value if one of
665     /// the sides is not present.
animate_with_zero(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> where T: Animate + Clone + ToAnimatedZero666     fn animate_with_zero(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>
667     where
668         T: Animate + Clone + ToAnimatedZero;
669 
670     /// This is the animation used for some of the types like shadows and
671     /// filters, where the interpolation happens with the zero value if one of
672     /// the sides is not present.
squared_distance_with_zero(&self, other: &Self) -> Result<SquaredDistance, ()> where T: ToAnimatedZero + ComputeSquaredDistance673     fn squared_distance_with_zero(&self, other: &Self) -> Result<SquaredDistance, ()>
674     where
675         T: ToAnimatedZero + ComputeSquaredDistance;
676 }
677 
678 macro_rules! animated_list_impl {
679     (<$t:ident> for $ty:ty) => {
680         impl<$t> ListAnimation<$t> for $ty {
681             fn animate_repeatable_list(
682                 &self,
683                 other: &Self,
684                 procedure: Procedure,
685             ) -> Result<Self, ()>
686             where
687                 T: Animate,
688             {
689                 // If the length of either list is zero, the least common multiple is undefined.
690                 if self.is_empty() || other.is_empty() {
691                     return Err(());
692                 }
693                 use num_integer::lcm;
694                 let len = lcm(self.len(), other.len());
695                 self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(this, other)| {
696                     this.animate(other, procedure)
697                 }).collect()
698             }
699 
700             fn squared_distance_repeatable_list(
701                 &self,
702                 other: &Self,
703             ) -> Result<SquaredDistance, ()>
704             where
705                 T: ComputeSquaredDistance,
706             {
707                 if self.is_empty() || other.is_empty() {
708                     return Err(());
709                 }
710                 use num_integer::lcm;
711                 let len = lcm(self.len(), other.len());
712                 self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(this, other)| {
713                     this.compute_squared_distance(other)
714                 }).sum()
715             }
716 
717             fn animate_with_zero(
718                 &self,
719                 other: &Self,
720                 procedure: Procedure,
721             ) -> Result<Self, ()>
722             where
723                 T: Animate + Clone + ToAnimatedZero
724             {
725                 if procedure == Procedure::Add {
726                     return Ok(
727                         self.iter().chain(other.iter()).cloned().collect()
728                     );
729                 }
730                 self.iter().zip_longest(other.iter()).map(|it| {
731                     match it {
732                         EitherOrBoth::Both(this, other) => {
733                             this.animate(other, procedure)
734                         },
735                         EitherOrBoth::Left(this) => {
736                             this.animate(&this.to_animated_zero()?, procedure)
737                         },
738                         EitherOrBoth::Right(other) => {
739                             other.to_animated_zero()?.animate(other, procedure)
740                         }
741                     }
742                 }).collect()
743             }
744 
745             fn squared_distance_with_zero(
746                 &self,
747                 other: &Self,
748             ) -> Result<SquaredDistance, ()>
749             where
750                 T: ToAnimatedZero + ComputeSquaredDistance
751             {
752                 self.iter().zip_longest(other.iter()).map(|it| {
753                     match it {
754                         EitherOrBoth::Both(this, other) => {
755                             this.compute_squared_distance(other)
756                         },
757                         EitherOrBoth::Left(list) | EitherOrBoth::Right(list) => {
758                             list.to_animated_zero()?.compute_squared_distance(list)
759                         },
760                     }
761                 }).sum()
762             }
763         }
764     }
765 }
766 
767 animated_list_impl!(<T> for crate::OwnedSlice<T>);
768 animated_list_impl!(<T> for SmallVec<[T; 1]>);
769 animated_list_impl!(<T> for Vec<T>);
770 
771 /// <https://drafts.csswg.org/web-animations-1/#animating-visibility>
772 impl Animate for Visibility {
773     #[inline]
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>774     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
775         match procedure {
776             Procedure::Interpolate { .. } => {
777                 let (this_weight, other_weight) = procedure.weights();
778                 match (*self, *other) {
779                     (Visibility::Visible, _) => {
780                         Ok(if this_weight > 0.0 { *self } else { *other })
781                     },
782                     (_, Visibility::Visible) => {
783                         Ok(if other_weight > 0.0 { *other } else { *self })
784                     },
785                     _ => Err(()),
786                 }
787             },
788             _ => Err(()),
789         }
790     }
791 }
792 
793 impl ComputeSquaredDistance for Visibility {
794     #[inline]
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>795     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
796         Ok(SquaredDistance::from_sqrt(if *self == *other { 0. } else { 1. }))
797     }
798 }
799 
800 impl ToAnimatedZero for Visibility {
801     #[inline]
to_animated_zero(&self) -> Result<Self, ()>802     fn to_animated_zero(&self) -> Result<Self, ()> {
803         Err(())
804     }
805 }
806 
807 /// <https://drafts.csswg.org/css-transitions/#animtype-rect>
808 impl Animate for ClipRect {
809     #[inline]
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>810     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
811         use crate::values::computed::LengthOrAuto;
812         let animate_component = |this: &LengthOrAuto, other: &LengthOrAuto| {
813             let result = this.animate(other, procedure)?;
814             if let Procedure::Interpolate { .. } = procedure {
815                 return Ok(result);
816             }
817             if result.is_auto() {
818                 // FIXME(emilio): Why? A couple SMIL tests fail without this,
819                 // but it seems extremely fishy.
820                 return Err(());
821             }
822             Ok(result)
823         };
824 
825         Ok(ClipRect {
826             top: animate_component(&self.top, &other.top)?,
827             right: animate_component(&self.right, &other.right)?,
828             bottom: animate_component(&self.bottom, &other.bottom)?,
829             left: animate_component(&self.left, &other.left)?,
830         })
831     }
832 }
833 
834 <%
835     FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale',
836                          'HueRotate', 'Invert', 'Opacity', 'Saturate',
837                          'Sepia' ]
838 %>
839 
840 /// <https://drafts.fxtf.org/filters/#animation-of-filters>
841 impl Animate for AnimatedFilter {
animate( &self, other: &Self, procedure: Procedure, ) -> Result<Self, ()>842     fn animate(
843         &self,
844         other: &Self,
845         procedure: Procedure,
846     ) -> Result<Self, ()> {
847         use crate::values::animated::animate_multiplicative_factor;
848         match (self, other) {
849             % for func in ['Blur', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']:
850             (&Filter::${func}(ref this), &Filter::${func}(ref other)) => {
851                 Ok(Filter::${func}(this.animate(other, procedure)?))
852             },
853             % endfor
854             % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']:
855             (&Filter::${func}(this), &Filter::${func}(other)) => {
856                 Ok(Filter::${func}(animate_multiplicative_factor(this, other, procedure)?))
857             },
858             % endfor
859             % if engine == "gecko":
860             (&Filter::DropShadow(ref this), &Filter::DropShadow(ref other)) => {
861                 Ok(Filter::DropShadow(this.animate(other, procedure)?))
862             },
863             % endif
864             _ => Err(()),
865         }
866     }
867 }
868 
869 /// <http://dev.w3.org/csswg/css-transforms/#none-transform-animation>
870 impl ToAnimatedZero for AnimatedFilter {
to_animated_zero(&self) -> Result<Self, ()>871     fn to_animated_zero(&self) -> Result<Self, ()> {
872         match *self {
873             % for func in ['Blur', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']:
874             Filter::${func}(ref this) => Ok(Filter::${func}(this.to_animated_zero()?)),
875             % endfor
876             % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']:
877             Filter::${func}(_) => Ok(Filter::${func}(1.)),
878             % endfor
879             % if engine == "gecko":
880             Filter::DropShadow(ref this) => Ok(Filter::DropShadow(this.to_animated_zero()?)),
881             % endif
882             _ => Err(()),
883         }
884     }
885 }
886 
887 /// An iterator over all the properties that transition on a given style.
888 pub struct TransitionPropertyIterator<'a> {
889     style: &'a ComputedValues,
890     index_range: core::ops::Range<usize>,
891     longhand_iterator: Option<NonCustomPropertyIterator<LonghandId>>,
892 }
893 
894 impl<'a> TransitionPropertyIterator<'a> {
895     /// Create a `TransitionPropertyIterator` for the given style.
from_style(style: &'a ComputedValues) -> Self896     pub fn from_style(style: &'a ComputedValues) -> Self {
897         Self {
898             style,
899             index_range: 0..style.get_box().transition_property_count(),
900             longhand_iterator: None,
901         }
902     }
903 }
904 
905 /// A single iteration of the TransitionPropertyIterator.
906 pub struct TransitionPropertyIteration {
907     /// The id of the longhand for this property.
908     pub longhand_id: LonghandId,
909 
910     /// The index of this property in the list of transition properties for this
911     /// iterator's style.
912     pub index: usize,
913 }
914 
915 impl<'a> Iterator for TransitionPropertyIterator<'a> {
916     type Item = TransitionPropertyIteration;
917 
next(&mut self) -> Option<Self::Item>918     fn next(&mut self) -> Option<Self::Item> {
919         use crate::values::computed::TransitionProperty;
920         loop {
921             if let Some(ref mut longhand_iterator) = self.longhand_iterator {
922                 if let Some(longhand_id) = longhand_iterator.next() {
923                     return Some(TransitionPropertyIteration {
924                         longhand_id,
925                         index: self.index_range.start - 1,
926                     });
927                 }
928                 self.longhand_iterator = None;
929             }
930 
931             let index = self.index_range.next()?;
932             match self.style.get_box().transition_property_at(index) {
933                 TransitionProperty::Longhand(longhand_id) => {
934                     return Some(TransitionPropertyIteration {
935                         longhand_id,
936                         index,
937                     })
938                 }
939                 // In the other cases, we set up our state so that we are ready to
940                 // compute the next value of the iterator and then loop (equivalent
941                 // to calling self.next()).
942                 TransitionProperty::Shorthand(ref shorthand_id) =>
943                     self.longhand_iterator = Some(shorthand_id.longhands()),
944                 TransitionProperty::Custom(..) | TransitionProperty::Unsupported(..) => {}
945             }
946         }
947     }
948 }
949