servo: Merge #17162 - Support additive SMIL animation (from birtles:additive-smil); r=hiro
authorBrian Birtles <birtles@gmail.com>
Sun, 04 Jun 2017 21:41:06 -0700
changeset 412749 2839baa84dca69d6691fa3c81b450431c01f05c9
parent 412712 7d5df2dead2c9edaa41688a82d9d9dcc86529ef2
child 412750 38ae6e3d3a114ba48fa13ae98cb81d943c5b6230
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershiro
bugs1355349
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #17162 - Support additive SMIL animation (from birtles:additive-smil); r=hiro These are the Servo-side changes for [Bug 1355349](https://bugzilla.mozilla.org/show_bug.cgi?id=1355349). They have been reviewed by @hiikezoe. Source-Repo: https://github.com/servo/servo Source-Revision: d767e6046b5d970bc94818bc750afe5acccbdb31
servo/components/style/gecko/generated/bindings.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -2181,16 +2181,32 @@ extern "C" {
 extern "C" {
     pub fn Servo_AnimationValues_IsInterpolable(from:
                                                     RawServoAnimationValueBorrowed,
                                                 to:
                                                     RawServoAnimationValueBorrowed)
      -> bool;
 }
 extern "C" {
+    pub fn Servo_AnimationValues_Add(a: RawServoAnimationValueBorrowed,
+                                     b: RawServoAnimationValueBorrowed)
+     -> RawServoAnimationValueStrong;
+}
+extern "C" {
+    pub fn Servo_AnimationValues_Accumulate(a: RawServoAnimationValueBorrowed,
+                                            b: RawServoAnimationValueBorrowed,
+                                            count: u64)
+     -> RawServoAnimationValueStrong;
+}
+extern "C" {
+    pub fn Servo_AnimationValues_GetZeroValue(value_to_match:
+                                                  RawServoAnimationValueBorrowed)
+     -> RawServoAnimationValueStrong;
+}
+extern "C" {
     pub fn Servo_AnimationValues_ComputeDistance(from:
                                                      RawServoAnimationValueBorrowed,
                                                  to:
                                                      RawServoAnimationValueBorrowed)
      -> f64;
 }
 extern "C" {
     pub fn Servo_AnimationValue_Serialize(value:
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -636,16 +636,34 @@ impl Animatable for AnimationValue {
             % endfor
             _ => {
                 panic!("Expected weighted addition of computed values of the same \
                         property, got: {:?}, {:?}", self, other);
             }
         }
     }
 
+    fn get_zero_value(&self) -> Option<Self> {
+        match self {
+            % for prop in data.longhands:
+                % if prop.animatable:
+                    % if prop.animation_value_type == "discrete":
+                        &AnimationValue::${prop.camel_case}(_) => {
+                            None
+                        }
+                    % else:
+                        &AnimationValue::${prop.camel_case}(ref base) => {
+                            base.get_zero_value().map(AnimationValue::${prop.camel_case})
+                        }
+                    % endif
+                % endif
+            % endfor
+        }
+    }
+
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         match (self, other) {
             % for prop in data.longhands:
                 % if prop.animatable:
                     % if prop.animation_value_type != "discrete":
                         (&AnimationValue::${prop.camel_case}(ref from),
                          &AnimationValue::${prop.camel_case}(ref to)) => {
                             from.compute_distance(to)
@@ -692,16 +710,27 @@ pub trait Animatable: Sized {
     /// accumulates |other| onto the result.
     /// If |count| is zero, the result will be |other|.
     ///
     /// [animation-accumulation]: https://w3c.github.io/web-animations/#animation-accumulation
     fn accumulate(&self, other: &Self, count: u64) -> Result<Self, ()> {
         self.add_weighted(other, count as f64, 1.0)
     }
 
+    /// Returns a value that, when added with an underlying value, will produce the underlying
+    /// value. This is used for SMIL animation's "by-animation" where SMIL first interpolates from
+    /// the zero value to the 'by' value, and then adds the result to the underlying value.
+    ///
+    /// This is not the necessarily the same as the initial value of a property. For example, the
+    /// initial value of 'stroke-width' is 1, but the zero value is 0, since adding 1 to the
+    /// underlying value will not produce the underlying value.
+    fn get_zero_value(&self) -> Option<Self> {
+        None
+    }
+
     /// Compute distance between a value and another for a given property.
     fn compute_distance(&self, _other: &Self) -> Result<f64, ()>  { Err(()) }
 
     /// In order to compute the Euclidean distance of a list or property value with multiple
     /// components, we need to compute squared distance for each element, so the vector can sum it
     /// and then get its squared root as the distance.
     fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
         self.compute_distance(other).map(|d| d * d)
@@ -742,16 +771,19 @@ impl<T: RepeatableListAnimatable> Animat
 /// https://drafts.csswg.org/css-transitions/#animtype-number
 impl Animatable for Au {
     #[inline]
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok(Au((self.0 as f64 * self_portion + other.0 as f64 * other_portion).round() as i32))
     }
 
     #[inline]
+    fn get_zero_value(&self) -> Option<Self> { Some(Au(0)) }
+
+    #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         self.0.compute_distance(&other.0)
     }
 }
 
 impl <T> Animatable for Option<T>
     where T: Animatable,
 {
@@ -791,42 +823,51 @@ impl <T> Animatable for Option<T>
 /// https://drafts.csswg.org/css-transitions/#animtype-number
 impl Animatable for f32 {
     #[inline]
     fn add_weighted(&self, other: &f32, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok((*self as f64 * self_portion + *other as f64 * other_portion) as f32)
     }
 
     #[inline]
+    fn get_zero_value(&self) -> Option<Self> { Some(0.) }
+
+    #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         Ok((*self - *other).abs() as f64)
     }
 }
 
 /// https://drafts.csswg.org/css-transitions/#animtype-number
 impl Animatable for f64 {
     #[inline]
     fn add_weighted(&self, other: &f64, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok(*self * self_portion + *other * other_portion)
     }
 
     #[inline]
+    fn get_zero_value(&self) -> Option<Self> { Some(0.) }
+
+    #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         Ok((*self - *other).abs())
     }
 }
 
 /// https://drafts.csswg.org/css-transitions/#animtype-integer
 impl Animatable for i32 {
     #[inline]
     fn add_weighted(&self, other: &i32, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok((*self as f64 * self_portion + *other as f64 * other_portion).round() as i32)
     }
 
     #[inline]
+    fn get_zero_value(&self) -> Option<Self> { Some(0) }
+
+    #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         Ok((*self - *other).abs() as f64)
     }
 }
 
 /// https://drafts.csswg.org/css-transitions/#animtype-number
 impl Animatable for Angle {
     #[inline]
@@ -994,25 +1035,35 @@ impl Animatable for LengthOrPercentage {
                     .map(LengthOrPercentage::Length)
             }
             (LengthOrPercentage::Percentage(ref this),
              LengthOrPercentage::Percentage(ref other)) => {
                 this.add_weighted(other, self_portion, other_portion)
                     .map(LengthOrPercentage::Percentage)
             }
             (this, other) => {
+                // Special handling for zero values since these should not require calc().
+                if this.is_definitely_zero() {
+                    return other.add_weighted(&other, 0., other_portion)
+                } else if other.is_definitely_zero() {
+                    return this.add_weighted(self, self_portion, 0.)
+                }
+
                 let this: CalcLengthOrPercentage = From::from(this);
                 let other: CalcLengthOrPercentage = From::from(other);
                 this.add_weighted(&other, self_portion, other_portion)
                     .map(LengthOrPercentage::Calc)
             }
         }
     }
 
     #[inline]
+    fn get_zero_value(&self) -> Option<Self> { Some(LengthOrPercentage::zero()) }
+
+    #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         match (*self, *other) {
             (LengthOrPercentage::Length(ref this),
              LengthOrPercentage::Length(ref other)) => {
                 this.compute_distance(other)
             },
             (LengthOrPercentage::Percentage(ref this),
              LengthOrPercentage::Percentage(ref other)) => {
@@ -1075,16 +1126,28 @@ impl Animatable for LengthOrPercentageOr
                     Ok(Some(result)) => Ok(LengthOrPercentageOrAuto::Calc(result)),
                     _ => Err(()),
                 }
             }
         }
     }
 
     #[inline]
+    fn get_zero_value(&self) -> Option<Self> {
+        match *self {
+            LengthOrPercentageOrAuto::Length(_) |
+            LengthOrPercentageOrAuto::Percentage(_) |
+            LengthOrPercentageOrAuto::Calc(_) => {
+                Some(LengthOrPercentageOrAuto::Length(Au(0)))
+            },
+            LengthOrPercentageOrAuto::Auto => { None },
+        }
+    }
+
+    #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         match (*self, *other) {
             (LengthOrPercentageOrAuto::Length(ref this),
              LengthOrPercentageOrAuto::Length(ref other)) => {
                 this.compute_distance(other)
             },
             (LengthOrPercentageOrAuto::Percentage(ref this),
              LengthOrPercentageOrAuto::Percentage(ref other)) => {
@@ -1145,16 +1208,28 @@ impl Animatable for LengthOrPercentageOr
             (LengthOrPercentageOrNone::None, LengthOrPercentageOrNone::None) => {
                 Ok(LengthOrPercentageOrNone::None)
             }
             _ => Err(())
         }
     }
 
     #[inline]
+    fn get_zero_value(&self) -> Option<Self> {
+        match *self {
+            LengthOrPercentageOrNone::Length(_) |
+            LengthOrPercentageOrNone::Percentage(_) |
+            LengthOrPercentageOrNone::Calc(_) => {
+                Some(LengthOrPercentageOrNone::Length(Au(0)))
+            },
+            LengthOrPercentageOrNone::None => { None },
+        }
+    }
+
+    #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         match (*self, *other) {
             (LengthOrPercentageOrNone::Length(ref this),
              LengthOrPercentageOrNone::Length(ref other)) => {
                 this.compute_distance(other)
             },
             (LengthOrPercentageOrNone::Percentage(ref this),
              LengthOrPercentageOrNone::Percentage(ref other)) => {
@@ -1218,17 +1293,18 @@ impl Animatable for MaxLength {
 }
 
 /// http://dev.w3.org/csswg/css-transitions/#animtype-font-weight
 impl Animatable for FontWeight {
     #[inline]
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         let a = (*self as u32) as f64;
         let b = (*other as u32) as f64;
-        let weight = a * self_portion + b * other_portion;
+        const NORMAL: f64 = 400.;
+        let weight = (a - NORMAL) * self_portion + (b - NORMAL) * other_portion + NORMAL;
         Ok(if weight < 150. {
             FontWeight::Weight100
         } else if weight < 250. {
             FontWeight::Weight200
         } else if weight < 350. {
             FontWeight::Weight300
         } else if weight < 450. {
             FontWeight::Weight400
@@ -1241,16 +1317,19 @@ impl Animatable for FontWeight {
         } else if weight < 850. {
             FontWeight::Weight800
         } else {
             FontWeight::Weight900
         })
     }
 
     #[inline]
+    fn get_zero_value(&self) -> Option<Self> { Some(FontWeight::Weight400) }
+
+    #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         let a = (*self as u32) as f64;
         let b = (*other as u32) as f64;
         a.compute_distance(&b)
     }
 }
 
 /// https://drafts.csswg.org/css-fonts/#font-stretch-prop
@@ -1301,29 +1380,42 @@ impl Into<FontStretch> for f64 {
         let index = (self + 0.5).floor().min(9.0).max(1.0);
         static FONT_STRETCH_ENUM_MAP: [FontStretch; 9] =
             [ ultra_condensed, extra_condensed, condensed, semi_condensed, normal,
               semi_expanded, expanded, extra_expanded, ultra_expanded ];
         FONT_STRETCH_ENUM_MAP[(index - 1.0) as usize]
     }
 }
 
+// Like std::macros::try!, but for Option<>.
+macro_rules! option_try {
+    ($e:expr) => (match $e { Some(e) => e, None => return None })
+}
+
 /// https://drafts.csswg.org/css-transitions/#animtype-simple-list
 impl<H: Animatable, V: Animatable> Animatable for generic_position::Position<H, V> {
     #[inline]
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok(generic_position::Position {
             horizontal: try!(self.horizontal.add_weighted(&other.horizontal,
                                                           self_portion, other_portion)),
             vertical: try!(self.vertical.add_weighted(&other.vertical,
                                                       self_portion, other_portion)),
         })
     }
 
     #[inline]
+    fn get_zero_value(&self) -> Option<Self> {
+        Some(generic_position::Position {
+            horizontal: option_try!(self.horizontal.get_zero_value()),
+            vertical: option_try!(self.vertical.get_zero_value()),
+        })
+    }
+
+    #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         self.compute_squared_distance(other).map(|sd| sd.sqrt())
     }
 
     #[inline]
     fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
         Ok(try!(self.horizontal.compute_squared_distance(&other.horizontal)) +
            try!(self.vertical.compute_squared_distance(&other.vertical)))
@@ -2488,16 +2580,19 @@ impl Animatable for TransformList {
             (&None, &Some(_)) => {
                 Ok(other.clone())
             }
             _ => {
                 Ok(TransformList(None))
             }
         }
     }
+
+    #[inline]
+    fn get_zero_value(&self) -> Option<Self> { Some(TransformList(None)) }
 }
 
 impl<T, U> Animatable for Either<T, U>
         where T: Animatable + Copy, U: Animatable + Copy,
 {
     #[inline]
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         match (*self, *other) {
@@ -2510,16 +2605,24 @@ impl<T, U> Animatable for Either<T, U>
             _ => {
                 let result = if self_portion > other_portion {*self} else {*other};
                 Ok(result)
             }
         }
     }
 
     #[inline]
+    fn get_zero_value(&self) -> Option<Self> {
+        match *self {
+            Either::First(ref this) => { this.get_zero_value().map(Either::First) },
+            Either::Second(ref this) => { this.get_zero_value().map(Either::Second) },
+        }
+    }
+
+    #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         match (self, other) {
             (&Either::First(ref this), &Either::First(ref other)) => {
                 this.compute_distance(other)
             },
             (&Either::Second(ref this), &Either::Second(ref other)) => {
                 this.compute_distance(other)
             },
@@ -2612,16 +2715,21 @@ impl Animatable for IntermediateRGBA {
                              .add_weighted(&(other.blue * other.alpha),
                                            self_portion, other_portion))
                              * 1. / alpha;
             Ok(IntermediateRGBA::new(red, green, blue, alpha))
         }
     }
 
     #[inline]
+    fn get_zero_value(&self) -> Option<Self> {
+        Some(IntermediateRGBA::new(0., 0., 0., 1.))
+    }
+
+    #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         self.compute_squared_distance(other).map(|sq| sq.sqrt())
     }
 
     #[inline]
     fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
         let start = [ self.alpha,
                       self.red * self.alpha,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -309,16 +309,58 @@ pub extern "C" fn Servo_AnimationValues_
                                                        to: RawServoAnimationValueBorrowed)
                                                        -> bool {
     let from_value = AnimationValue::as_arc(&from);
     let to_value = AnimationValue::as_arc(&to);
     from_value.interpolate(to_value, 0.5).is_ok()
 }
 
 #[no_mangle]
+pub extern "C" fn Servo_AnimationValues_Add(a: RawServoAnimationValueBorrowed,
+                                            b: RawServoAnimationValueBorrowed)
+     -> RawServoAnimationValueStrong
+{
+    let a_value = AnimationValue::as_arc(&a);
+    let b_value = AnimationValue::as_arc(&b);
+    if let Ok(value) = a_value.add(b_value) {
+        Arc::new(value).into_strong()
+    } else {
+        RawServoAnimationValueStrong::null()
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn Servo_AnimationValues_Accumulate(a: RawServoAnimationValueBorrowed,
+                                                   b: RawServoAnimationValueBorrowed,
+                                                   count: u64)
+     -> RawServoAnimationValueStrong
+{
+    let a_value = AnimationValue::as_arc(&a);
+    let b_value = AnimationValue::as_arc(&b);
+    if let Ok(value) = a_value.accumulate(b_value, count) {
+        Arc::new(value).into_strong()
+    } else {
+        RawServoAnimationValueStrong::null()
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn Servo_AnimationValues_GetZeroValue(
+    value_to_match: RawServoAnimationValueBorrowed)
+    -> RawServoAnimationValueStrong
+{
+    let value_to_match = AnimationValue::as_arc(&value_to_match);
+    if let Some(zero_value) = value_to_match.get_zero_value() {
+        Arc::new(zero_value).into_strong()
+    } else {
+        RawServoAnimationValueStrong::null()
+    }
+}
+
+#[no_mangle]
 pub extern "C" fn Servo_AnimationValues_ComputeDistance(from: RawServoAnimationValueBorrowed,
                                                         to: RawServoAnimationValueBorrowed)
                                                         -> f64 {
     let from_value = AnimationValue::as_arc(&from);
     let to_value = AnimationValue::as_arc(&to);
     from_value.compute_distance(to_value).unwrap_or(0.0)
 }