Bug 1332633 - Part 1: Implement ComputeDistance trait. draft
authorBoris Chiou <boris.chiou@gmail.com>
Sat, 18 Mar 2017 19:47:04 +0800
changeset 565608 714b42fafd94a104a7b262d4a47caa6262d26847
parent 565119 897c9110de182154dbcdb3bd3804002c05457cc8
child 565609 6465253a72d37dc1dcc79fbda0692f39471eedc5
push id54918
push userbmo:boris.chiou@gmail.com
push dateThu, 20 Apr 2017 03:16:15 +0000
bugs1332633
milestone55.0a1
Bug 1332633 - Part 1: Implement ComputeDistance trait. Introduce ComputeDistance trait, which implement compute_distance and compute_squared_distance. For vector, compute_squared_distance is necessary because we use Euclidean distance as the distance between two values. The easier way to implement compute_squared_distance is to square the result from compute_distance, but for some property values, they may have many components, e.g. (v1, v2, v3). If we just square the result from compute_distance, the computation is (sqrt(v1^2 + v2^2 + v3^2))^2. There are two redundant operators: "square-root" and then "square". In order to avoid this, we should implement compute_squared_distance separately for these types. MozReview-Commit-ID: LmmrUXYlDb6
servo/components/style/properties/helpers.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/background.mako.rs
servo/components/style/properties/longhand/box.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/components/style/properties/longhand/inherited_text.mako.rs
servo/components/style/properties/longhand/svg.mako.rs
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -90,16 +90,29 @@
 
                 % if delegate_animate:
                     use properties::animated_properties::Interpolate;
                     impl Interpolate for T {
                         fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
                             self.0.interpolate(&other.0, progress).map(T)
                         }
                     }
+
+                    use properties::animated_properties::ComputeDistance;
+                    impl ComputeDistance for T {
+                        #[inline]
+                        fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+                            self.0.compute_distance(&other.0)
+                        }
+
+                        #[inline]
+                        fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
+                            self.0.compute_squared_distance(&other.0)
+                        }
+                    }
                 % endif
             }
 
             impl ToCss for computed_value::T {
                 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
                     where W: fmt::Write,
                 {
                     let mut iter = self.0.iter();
@@ -798,8 +811,50 @@
                 },
                 (&T(None), &T(None)) => {
                     Ok(T(None))
                 },
             }
         }
     }
 </%def>
+
+/// Macro for defining ComputeDistance trait for tuple struct which has Option<T>,
+/// e.g. struct T(pub Option<Au>).
+<%def name="impl_compute_distance_for_option_tuple(value_for_none)">
+    impl ComputeDistance for T {
+        #[inline]
+        fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+            match (self, other) {
+                (&T(Some(ref this)), &T(Some(ref other))) => {
+                    this.compute_distance(other)
+                },
+                (&T(Some(ref this)), &T(None)) => {
+                    this.compute_distance(&${value_for_none})
+                },
+                (&T(None), &T(Some(ref other))) => {
+                    ${value_for_none}.compute_distance(other)
+                },
+                (&T(None), &T(None)) => {
+                    Ok(0.0)
+                },
+            }
+        }
+
+        #[inline]
+        fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
+            match (self, other) {
+                (&T(Some(ref this)), &T(Some(ref other))) => {
+                    this.compute_squared_distance(other)
+                },
+                (&T(Some(ref this)), &T(None)) => {
+                    this.compute_squared_distance(&${value_for_none})
+                },
+                (&T(None), &T(Some(ref other))) => {
+                    ${value_for_none}.compute_squared_distance(other)
+                },
+                (&T(None), &T(None)) => {
+                    Ok(0.0)
+                },
+            }
+        }
+    }
+</%def>
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -5,17 +5,17 @@
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 use app_units::Au;
 use cssparser::{Color as CSSParserColor, Parser, RGBA};
 use euclid::{Point2D, Size2D};
 #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID;
 use properties::{CSSWideKeyword, PropertyDeclaration};
 use properties::longhands;
-use properties::longhands::background_size::computed_value::T as BackgroundSize;
+use properties::longhands::background_size::computed_value::T as BackgroundSizeList;
 use properties::longhands::font_weight::computed_value::T as FontWeight;
 use properties::longhands::line_height::computed_value::T as LineHeight;
 use properties::longhands::text_shadow::computed_value::T as TextShadowList;
 use properties::longhands::text_shadow::computed_value::TextShadow;
 use properties::longhands::box_shadow::computed_value::T as BoxShadowList;
 use properties::longhands::box_shadow::single_value::computed_value::T as BoxShadow;
 use properties::longhands::transform::computed_value::ComputedMatrix;
 use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation;
@@ -639,24 +639,24 @@ impl Interpolate for VerticalAlign {
                 this.interpolate(other, progress).map(|value| {
                     VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(value))
                 })
             }
             _ => Err(()),
         }
     }
 }
-impl Interpolate for BackgroundSize {
+
+impl Interpolate for BackgroundSizeList {
     #[inline]
     fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
-        self.0.interpolate(&other.0, progress).map(BackgroundSize)
+        self.0.interpolate(&other.0, progress).map(BackgroundSizeList)
     }
 }
 
-
 /// https://drafts.csswg.org/css-transitions/#animtype-color
 impl Interpolate for RGBA {
     #[inline]
     fn interpolate(&self, other: &RGBA, progress: f64) -> Result<Self, ()> {
         fn clamp(val: f32) -> f32 {
             val.max(0.).min(1.)
         }
 
@@ -1968,8 +1968,624 @@ impl<T, U> Interpolate for Either<T, U>
             },
             _ => {
                 let interpolated = if progress < 0.5 { *self } else { *other };
                 Ok(interpolated)
             }
         }
     }
 }
+
+
+/// We support ComputeDistance for an API in gecko to test the transition per property.
+impl ComputeDistance for AnimationValue {
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        match (self, other) {
+            % for prop in data.longhands:
+                % if prop.animatable:
+                    % if prop.animation_type == "normal":
+                        (&AnimationValue::${prop.camel_case}(ref from),
+                         &AnimationValue::${prop.camel_case}(ref to)) => {
+                            from.compute_distance(to)
+                        },
+                    % else:
+                        (&AnimationValue::${prop.camel_case}(ref _from),
+                         &AnimationValue::${prop.camel_case}(ref _to)) => {
+                            Err(())
+                        },
+                    % endif
+                % endif
+            % endfor
+            _ => {
+                panic!("Expected compute_distance of computed values of the same \
+                        property, got: {:?}, {:?}", self, other);
+            }
+        }
+    }
+}
+
+/// A trait used to implement [compute_distance].
+/// In order to compute the Euclidean distance of a list, we need to compute squared distance
+/// for each element, so the vector can sum it and then get its squared root as the distance.
+pub trait ComputeDistance: Sized {
+    /// Compute distance between a value and another for a given property.
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()>;
+
+    /// Compute squared distance between a value and another for a given property.
+    /// This is used for list or if there are many components in a property value.
+    fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
+        self.compute_distance(other).map(|d| d * d)
+    }
+}
+
+impl<T: ComputeDistance> ComputeDistance for Vec<T> {
+    #[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, ()> {
+        if self.len() != other.len() {
+            return Err(());
+        }
+
+        let mut squared_dist = 0.0f64;
+        for (this, other) in self.iter().zip(other) {
+            let diff = try!(this.compute_squared_distance(other));
+            squared_dist += diff;
+        }
+        Ok(squared_dist)
+    }
+}
+
+impl ComputeDistance for Au {
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        self.0.compute_distance(&other.0)
+    }
+}
+
+impl ComputeDistance for Auto {
+    #[inline]
+    fn compute_distance(&self, _other: &Self) -> Result<f64, ()> {
+        Err(())
+    }
+}
+
+impl ComputeDistance for Normal {
+    #[inline]
+    fn compute_distance(&self, _other: &Self) -> Result<f64, ()> {
+        Err(())
+    }
+}
+
+impl <T> ComputeDistance for Option<T>
+    where T: ComputeDistance,
+{
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        match (self, other) {
+            (&Some(ref this), &Some(ref other)) => {
+                this.compute_distance(other)
+            },
+            _ => Err(()),
+        }
+    }
+
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
+        match (self, other) {
+            (&Some(ref this), &Some(ref other)) => {
+                this.compute_squared_distance(other)
+            },
+            _ => Err(()),
+        }
+    }
+}
+
+impl ComputeDistance for f32 {
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        Ok((*self - *other).abs() as f64)
+    }
+}
+
+impl ComputeDistance for f64 {
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        Ok((*self - *other).abs())
+    }
+}
+
+impl ComputeDistance for i32 {
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        Ok((*self - *other).abs() as f64)
+    }
+}
+
+impl ComputeDistance for Visibility {
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        if *self == *other {
+            Ok(0.0)
+        } else {
+            Ok(1.0)
+        }
+    }
+}
+
+/// https://www.w3.org/TR/smil-animation/#animateColorElement says we should use Euclidean RGB-cube distance.
+impl ComputeDistance for RGBA {
+    #[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, ()> {
+        fn clamp(val: f32) -> f32 {
+            val.max(0.).min(1.)
+        }
+
+        let start_a = clamp(self.alpha_f32());
+        let end_a = clamp(other.alpha_f32());
+        let start = [ start_a,
+                      self.red_f32() * start_a,
+                      self.green_f32() * start_a,
+                      self.blue_f32() * start_a ];
+        let end = [ end_a,
+                    other.red_f32() * end_a,
+                    other.green_f32() * end_a,
+                    other.blue_f32() * end_a ];
+        let diff = start.iter().zip(&end)
+                               .fold(0.0f64, |n, (&a, &b)| {
+                                   let diff = (a - b) as f64;
+                                   n + diff * diff
+                               });
+        Ok(diff)
+    }
+}
+
+impl ComputeDistance for CSSParserColor {
+    #[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, ()> {
+        match (*self, *other) {
+            (CSSParserColor::RGBA(ref this), CSSParserColor::RGBA(ref other)) => {
+                this.compute_squared_distance(other)
+            },
+            _ => Ok(0.0),
+        }
+    }
+}
+
+impl ComputeDistance for CalcLengthOrPercentage {
+    #[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 length_diff = (self.length().0 - other.length().0) as f64;
+        let percentage_diff = (self.percentage() - other.percentage()) as f64;
+        Ok(length_diff * length_diff + percentage_diff * percentage_diff)
+    }
+}
+
+impl ComputeDistance for LengthOrPercentage {
+    #[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)) => {
+                this.compute_distance(other)
+            },
+            (this, other) => {
+                let this: CalcLengthOrPercentage = From::from(this);
+                let other: CalcLengthOrPercentage = From::from(other);
+                this.compute_distance(&other)
+            }
+        }
+    }
+
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
+        match (*self, *other) {
+            (LengthOrPercentage::Length(ref this),
+             LengthOrPercentage::Length(ref other)) => {
+                let diff = (this.0 - other.0) as f64;
+                Ok(diff * diff)
+            },
+            (LengthOrPercentage::Percentage(ref this),
+             LengthOrPercentage::Percentage(ref other)) => {
+                let diff = (this - other) as f64;
+                Ok(diff * diff)
+            },
+            (this, other) => {
+                let this: CalcLengthOrPercentage = From::from(this);
+                let other: CalcLengthOrPercentage = From::from(other);
+                let length_diff = (this.length().0 - other.length().0) as f64;
+                let percentage_diff = (this.percentage() - other.percentage()) as f64;
+                Ok(length_diff * length_diff + percentage_diff * percentage_diff)
+            }
+        }
+    }
+}
+
+impl ComputeDistance for LengthOrPercentageOrAuto {
+    #[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)) => {
+                this.compute_distance(other)
+            },
+            (this, other) => {
+                // If one of the element is Auto, Option<> will be None, and the returned distance is Err(())
+                let this: Option<CalcLengthOrPercentage> = From::from(this);
+                let other: Option<CalcLengthOrPercentage> = From::from(other);
+                this.compute_distance(&other)
+            }
+        }
+    }
+
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
+        match (*self, *other) {
+            (LengthOrPercentageOrAuto::Length(ref this),
+             LengthOrPercentageOrAuto::Length(ref other)) => {
+                let diff = (this.0 - other.0) as f64;
+                Ok(diff * diff)
+            },
+            (LengthOrPercentageOrAuto::Percentage(ref this),
+             LengthOrPercentageOrAuto::Percentage(ref other)) => {
+                let diff = (this - other) as f64;
+                Ok(diff * diff)
+            },
+            (this, other) => {
+                let this: Option<CalcLengthOrPercentage> = From::from(this);
+                let other: Option<CalcLengthOrPercentage> = From::from(other);
+                if this.is_none() || other.is_none() {
+                    Err(())
+                } else {
+                    let length_diff = (this.unwrap().length().0 - other.unwrap().length().0) as f64;
+                    let percentage_diff = (this.unwrap().percentage() - other.unwrap().percentage()) as f64;
+                    Ok(length_diff * length_diff + percentage_diff * percentage_diff)
+                }
+            }
+        }
+    }
+}
+
+impl ComputeDistance for LengthOrPercentageOrNone {
+    #[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)) => {
+                this.compute_distance(other)
+            },
+            _ => Err(())
+        }
+    }
+}
+
+impl ComputeDistance for LengthOrNone {
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        match (*self, *other) {
+            (Either::First(ref length), Either::First(ref other)) => {
+                length.compute_distance(other)
+            },
+            _ => Err(()),
+        }
+    }
+}
+
+impl ComputeDistance for MinLength {
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        match (*self, *other) {
+            (MinLength::LengthOrPercentage(ref this),
+             MinLength::LengthOrPercentage(ref other)) => {
+                this.compute_distance(other)
+            },
+            _ => Err(()),
+        }
+    }
+}
+
+impl ComputeDistance for MaxLength {
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        match (*self, *other) {
+            (MaxLength::LengthOrPercentage(ref this),
+             MaxLength::LengthOrPercentage(ref other)) => {
+                this.compute_distance(other)
+            },
+            _ => Err(()),
+        }
+    }
+}
+
+impl ComputeDistance for VerticalAlign {
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        match (*self, *other) {
+            (VerticalAlign::LengthOrPercentage(ref this),
+             VerticalAlign::LengthOrPercentage(ref other)) => {
+                this.compute_distance(other)
+            },
+            _ => Err(()),
+        }
+    }
+}
+
+impl ComputeDistance for BorderRadiusSize {
+    #[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.0.width.compute_squared_distance(&other.0.width)) +
+           try!(self.0.height.compute_squared_distance(&other.0.height)))
+    }
+}
+
+impl ComputeDistance for BackgroundSizeList {
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        self.0.compute_distance(&other.0)
+    }
+
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
+        self.0.compute_squared_distance(&other.0)
+    }
+}
+
+impl ComputeDistance for LineHeight {
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        match (*self, *other) {
+            (LineHeight::Length(ref this),
+             LineHeight::Length(ref other)) => {
+                this.compute_distance(other)
+            },
+            (LineHeight::Number(ref this),
+             LineHeight::Number(ref other)) => {
+                this.compute_distance(other)
+            },
+            _ => Err(()),
+        }
+    }
+}
+
+impl ComputeDistance for FontWeight {
+    #[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)
+    }
+}
+
+impl ComputeDistance for Position {
+    #[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)))
+    }
+}
+
+impl ComputeDistance for HorizontalPosition {
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        self.0.compute_distance(&other.0)
+    }
+
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
+        self.0.compute_squared_distance(&other.0)
+    }
+}
+
+impl ComputeDistance for VerticalPosition {
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        self.0.compute_distance(&other.0)
+    }
+
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
+        self.0.compute_squared_distance(&other.0)
+    }
+}
+
+impl ComputeDistance for ClipRect {
+    #[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, ()> {
+        let list = [ try!(self.top.compute_distance(&other.top)),
+                     try!(self.right.compute_distance(&other.right)),
+                     try!(self.bottom.compute_distance(&other.bottom)),
+                     try!(self.left.compute_distance(&other.left)) ];
+        Ok(list.iter().fold(0.0f64, |sum, diff| sum + diff * diff))
+    }
+}
+
+impl ComputeDistance for TextShadow {
+    #[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, ()> {
+        let list = [ try!(self.offset_x.compute_distance(&other.offset_x)),
+                     try!(self.offset_y.compute_distance(&other.offset_y)),
+                     try!(self.blur_radius.compute_distance(&other.blur_radius)),
+                     try!(self.color.compute_distance(&other.color)) ];
+        Ok(list.iter().fold(0.0f64, |sum, diff| sum + diff * diff))
+    }
+}
+
+impl ComputeDistance for TextShadowList {
+    #[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, ()> {
+        let zero = TextShadow {
+            offset_x: Au(0),
+            offset_y: Au(0),
+            blur_radius: Au(0),
+            color: CSSParserColor::RGBA(RGBA::transparent()),
+        };
+
+        let max_len = cmp::max(self.0.len(), other.0.len());
+        let mut diff_squared = 0.0f64;
+        for i in 0..max_len {
+            diff_squared += match (self.0.get(i), other.0.get(i)) {
+                (Some(shadow), Some(other)) => {
+                    try!(shadow.compute_squared_distance(other))
+                },
+                (Some(shadow), None) => {
+                    try!(shadow.compute_squared_distance(&zero))
+                },
+                (None, Some(shadow)) => {
+                    try!(zero.compute_squared_distance(shadow))
+                }
+                (None, None) => unreachable!(),
+            };
+        }
+        Ok(diff_squared)
+    }
+}
+
+impl ComputeDistance for BoxShadow {
+    #[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, ()> {
+        if self.inset != other.inset {
+            return Err(());
+        }
+        let list = [ try!(self.offset_x.compute_distance(&other.offset_x)),
+                     try!(self.offset_y.compute_distance(&other.offset_y)),
+                     try!(self.color.compute_distance(&other.color)),
+                     try!(self.spread_radius.compute_distance(&other.spread_radius)),
+                     try!(self.blur_radius.compute_distance(&other.blur_radius)) ];
+        Ok(list.iter().fold(0.0f64, |sum, diff| sum + diff * diff))
+    }
+}
+
+impl ComputeDistance for BoxShadowList {
+    #[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, ()> {
+        // The inset value must change
+        let mut zero = BoxShadow {
+            offset_x: Au(0),
+            offset_y: Au(0),
+            spread_radius: Au(0),
+            blur_radius: Au(0),
+            color: CSSParserColor::RGBA(RGBA::transparent()),
+            inset: false,
+        };
+
+        let max_len = cmp::max(self.0.len(), other.0.len());
+        let mut diff_squared = 0.0f64;
+        for i in 0..max_len {
+            diff_squared += match (self.0.get(i), other.0.get(i)) {
+                (Some(shadow), Some(other)) => {
+                    try!(shadow.compute_squared_distance(other))
+                },
+                (Some(shadow), None) => {
+                    zero.inset = shadow.inset;
+                    try!(shadow.compute_squared_distance(&zero))
+                }
+                (None, Some(shadow)) => {
+                    zero.inset = shadow.inset;
+                    try!(zero.compute_squared_distance(shadow))
+                }
+                (None, None) => unreachable!(),
+            };
+        }
+        Ok(diff_squared)
+    }
+}
+
+impl ComputeDistance for TransformList {
+    #[inline]
+    fn compute_distance(&self, _other: &Self) -> Result<f64, ()> {
+        Err(())
+    }
+}
+
+impl<T, U> ComputeDistance for Either<T, U>
+    where T: ComputeDistance, U: ComputeDistance
+{
+    #[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)
+            },
+            _ => Err(())
+        }
+    }
+
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
+        match (self, other) {
+            (&Either::First(ref this), &Either::First(ref other)) => {
+                this.compute_squared_distance(other)
+            },
+            (&Either::Second(ref this), &Either::Second(ref other)) => {
+                this.compute_squared_distance(other)
+            },
+            _ => Err(())
+        }
+    }
+}
--- a/servo/components/style/properties/longhand/background.mako.rs
+++ b/servo/components/style/properties/longhand/background.mako.rs
@@ -329,17 +329,17 @@
     use std::ascii::AsciiExt;
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
 
     #[allow(missing_docs)]
     pub mod computed_value {
         use values::computed::LengthOrPercentageOrAuto;
-        use properties::animated_properties::{Interpolate, RepeatableListInterpolate};
+        use properties::animated_properties::{ComputeDistance, Interpolate, RepeatableListInterpolate};
 
         #[derive(PartialEq, Clone, Debug)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct ExplicitSize {
             pub width: LengthOrPercentageOrAuto,
             pub height: LengthOrPercentageOrAuto,
         }
 
@@ -362,16 +362,34 @@
                             width: try!(me.width.interpolate(&other.width, time)),
                             height: try!(me.height.interpolate(&other.height, time)),
                         }))
                     }
                     _ => Err(()),
                 }
             }
         }
+
+        impl ComputeDistance for T {
+            #[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, ()> {
+                match (self, other) {
+                    (&T::Explicit(ref me), &T::Explicit(ref other)) => {
+                        Ok(try!(me.width.compute_squared_distance(&other.width)) +
+                           try!(me.height.compute_squared_distance(&other.height)))
+                    },
+                    _ => Err(())
+                }
+            }
+        }
     }
 
     impl ToCss for computed_value::T {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
                 computed_value::T::Explicit(ref size) => size.to_css(dest),
                 computed_value::T::Cover => dest.write_str("cover"),
                 computed_value::T::Contain => dest.write_str("contain"),
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -1971,36 +1971,51 @@
                    spec="https://drafts.csswg.org/css-transforms/#transform-origin-property">
     use app_units::Au;
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
     use values::specified::{NoCalcLength, LengthOrPercentage, Percentage};
 
     pub mod computed_value {
-        use properties::animated_properties::Interpolate;
+        use properties::animated_properties::{ComputeDistance, Interpolate};
         use values::computed::{Length, LengthOrPercentage};
 
         #[derive(Clone, Copy, Debug, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct T {
             pub horizontal: LengthOrPercentage,
             pub vertical: LengthOrPercentage,
             pub depth: Length,
         }
 
         impl Interpolate for T {
+            #[inline]
             fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
                 Ok(T {
                     horizontal: try!(self.horizontal.interpolate(&other.horizontal, time)),
                     vertical: try!(self.vertical.interpolate(&other.vertical, time)),
                     depth: try!(self.depth.interpolate(&other.depth, time)),
                 })
             }
         }
+
+        impl ComputeDistance for T {
+            #[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)) +
+                   try!(self.depth.compute_squared_distance(&other.depth)))
+            }
+        }
     }
 
     impl HasViewportPercentage for SpecifiedValue {
         fn has_viewport_percentage(&self) -> bool {
             self.horizontal.has_viewport_percentage() ||
             self.vertical.has_viewport_percentage() ||
             self.depth.has_viewport_percentage()
         }
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -745,17 +745,17 @@
             match *computed {
                 computed_value::T::None => SpecifiedValue::None,
                 computed_value::T::Number(ref v) => SpecifiedValue::Number(specified::Number::from_computed_value(v)),
             }
         }
     }
 
     pub mod computed_value {
-        use properties::animated_properties::Interpolate;
+        use properties::animated_properties::{ComputeDistance, Interpolate};
         use std::fmt;
         use style_traits::ToCss;
         use values::CSSFloat;
 
         #[derive(Copy, Clone, Debug, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub enum T {
             None,
@@ -777,16 +777,27 @@
             fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
                 match (*self, *other) {
                     (T::Number(ref number), T::Number(ref other)) =>
                         Ok(T::Number(try!(number.interpolate(other, time)))),
                     _ => Err(()),
                 }
             }
         }
+
+        impl ComputeDistance for T {
+            #[inline]
+            fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+                match (*self, *other) {
+                    (T::Number(ref number), T::Number(ref other)) =>
+                        number.compute_distance(other),
+                    _ => Err(()),
+                }
+            }
+        }
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T::None
     }
 
     #[inline]
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -434,23 +434,24 @@
                 SpecifiedValue::Normal => dest.write_str("normal"),
                 SpecifiedValue::Specified(ref l) => l.to_css(dest),
             }
         }
     }
 
     pub mod computed_value {
         use app_units::Au;
-        use properties::animated_properties::Interpolate;
+        use properties::animated_properties::{ComputeDistance, Interpolate};
 
         #[derive(Debug, Clone, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct T(pub Option<Au>);
 
         ${helpers.impl_interpolate_for_option_tuple('Au(0)')}
+        ${helpers.impl_compute_distance_for_option_tuple('Au(0)')}
     }
 
     impl ToCss for computed_value::T {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match self.0 {
                 None => dest.write_str("normal"),
                 Some(l) => l.to_css(dest),
             }
@@ -518,23 +519,24 @@
             match *self {
                 SpecifiedValue::Normal => dest.write_str("normal"),
                 SpecifiedValue::Specified(ref l) => l.to_css(dest),
             }
         }
     }
 
     pub mod computed_value {
-        use properties::animated_properties::Interpolate;
+        use properties::animated_properties::{ComputeDistance, Interpolate};
         use values::computed::LengthOrPercentage;
         #[derive(Debug, Clone, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct T(pub Option<LengthOrPercentage>);
 
         ${helpers.impl_interpolate_for_option_tuple('LengthOrPercentage::zero()')}
+        ${helpers.impl_compute_distance_for_option_tuple('LengthOrPercentage::zero()')}
     }
 
     impl ToCss for computed_value::T {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match self.0 {
                 None => dest.write_str("normal"),
                 Some(l) => l.to_css(dest),
             }
--- a/servo/components/style/properties/longhand/svg.mako.rs
+++ b/servo/components/style/properties/longhand/svg.mako.rs
@@ -116,48 +116,62 @@
 <%helpers:vector_longhand name="mask-position-x" products="gecko" animation_type="normal" extra_prefixes="webkit"
                           spec="https://drafts.fxtf.org/css-masking/#propdef-mask-position">
     pub use properties::longhands::background_position_x::single_value::get_initial_value;
     pub use properties::longhands::background_position_x::single_value::get_initial_position_value;
     pub use properties::longhands::background_position_x::single_value::get_initial_specified_value;
     pub use properties::longhands::background_position_x::single_value::parse;
     pub use properties::longhands::background_position_x::single_value::SpecifiedValue;
     pub use properties::longhands::background_position_x::single_value::computed_value;
-    use properties::animated_properties::{Interpolate, RepeatableListInterpolate};
+    use properties::animated_properties::{ComputeDistance, Interpolate, RepeatableListInterpolate};
     use properties::longhands::mask_position_x::computed_value::T as MaskPositionX;
 
     impl Interpolate for MaskPositionX {
         #[inline]
         fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
             Ok(MaskPositionX(try!(self.0.interpolate(&other.0, progress))))
         }
     }
 
     impl RepeatableListInterpolate for MaskPositionX {}
+
+    impl ComputeDistance for MaskPositionX {
+        #[inline]
+        fn compute_distance(&self, _other: &Self) -> Result<f64, ()> {
+            Err(())
+        }
+    }
 </%helpers:vector_longhand>
 
 <%helpers:vector_longhand name="mask-position-y" products="gecko" animation_type="normal" extra_prefixes="webkit"
                           spec="https://drafts.fxtf.org/css-masking/#propdef-mask-position">
     pub use properties::longhands::background_position_y::single_value::get_initial_value;
     pub use properties::longhands::background_position_y::single_value::get_initial_position_value;
     pub use properties::longhands::background_position_y::single_value::get_initial_specified_value;
     pub use properties::longhands::background_position_y::single_value::parse;
     pub use properties::longhands::background_position_y::single_value::SpecifiedValue;
     pub use properties::longhands::background_position_y::single_value::computed_value;
-    use properties::animated_properties::{Interpolate, RepeatableListInterpolate};
+    use properties::animated_properties::{ComputeDistance, Interpolate, RepeatableListInterpolate};
     use properties::longhands::mask_position_y::computed_value::T as MaskPositionY;
 
     impl Interpolate for MaskPositionY {
         #[inline]
         fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
             Ok(MaskPositionY(try!(self.0.interpolate(&other.0, progress))))
         }
     }
 
     impl RepeatableListInterpolate for MaskPositionY {}
+
+    impl ComputeDistance for MaskPositionY {
+        #[inline]
+        fn compute_distance(&self, _other: &Self) -> Result<f64, ()> {
+            Err(())
+        }
+    }
 </%helpers:vector_longhand>
 
 ${helpers.single_keyword("mask-clip",
                          "border-box content-box padding-box",
                          extra_gecko_values="fill-box stroke-box view-box no-clip",
                          vector=True,
                          products="gecko",
                          extra_prefixes="webkit",