servo: Merge #13163 - Use conditional compilation for stylo properties; output unimplemented logs for all properties (from Manishearth:conditional-compilation); r=bholley
authorManish Goregaokar <manishsmail@gmail.com>
Fri, 02 Sep 2016 21:45:08 -0500
changeset 339639 9730f03d46420b9abaf6c7a1b74c13b5723d163b
parent 339638 91e1fd2a1b42746de21413d2587a7e1689a16cec
child 339640 3786e54afb9d0895bbd7ae64d8423985d2d15afe
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
servo: Merge #13163 - Use conditional compilation for stylo properties; output unimplemented logs for all properties (from Manishearth:conditional-compilation); r=bholley Till now we were only emitting unimplemented property logs for properties which servo implements but stylo doesn't. This list is getting smaller, and we really should be emitting this for any unexpected property we encounter. I also made it so that longhands which stylo does not implement will not be compiled in stylo builds; instead of what we currently do, which is to parse them and then basically ignore the result. There are still a few exceptions -- we generate stubs for properties that are parts of shorthands because otherwise we'd have to add fiddly conditional compilation to the shorthand code. r? @bholley cc @emilio Source-Repo: https://github.com/servo/servo Source-Revision: 58205f1a787a69adaeac0c875a6ed98d3a6640cb
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/column.mako.rs
servo/components/style/properties/longhand/counters.mako.rs
servo/components/style/properties/longhand/effects.mako.rs
servo/components/style/properties/longhand/inherited_box.mako.rs
servo/components/style/properties/longhand/inherited_table.mako.rs
servo/components/style/properties/longhand/inherited_text.mako.rs
servo/components/style/properties/longhand/list.mako.rs
servo/components/style/properties/longhand/outline.mako.rs
servo/components/style/properties/longhand/position.mako.rs
servo/components/style/properties/properties.mako.rs
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -463,49 +463,80 @@ impl Debug for ${style_struct.gecko_stru
     // FIXME(bholley): Generate this.
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.gecko.fmt(f) }
 }
 %endif
 </%def>
 
 <%def name="raw_impl_trait(style_struct, skip_longhands='', skip_additionals='')">
 <%
-   longhands = [x for x in style_struct.longhands
+    longhands = [x for x in style_struct.longhands
                 if not (skip_longhands == "*" or x.name in skip_longhands.split())]
 
-   #
-   # Make a list of types we can't auto-generate.
-   #
-   force_stub = [];
-   # These are currently being shuffled to a different style struct on the gecko side.
-   force_stub += ["backface-visibility", "transform-box", "transform-style"]
-   # These live in an nsFont member in Gecko. Should be straightforward to do manually.
-   force_stub += ["font-kerning", "font-stretch", "font-variant"]
-   # These have unusual representations in gecko.
-   force_stub += ["list-style-type", "text-overflow"]
-   # In a nsTArray, have to be done manually, but probably not too much work
-   # (the "filling them", not the "making them work")
-   force_stub += ["animation-name", "animation-duration",
+    #
+    # Make a list of types we can't auto-generate.
+    #
+    force_stub = [];
+    # These are currently being shuffled to a different style struct on the gecko side.
+    force_stub += ["backface-visibility", "transform-box", "transform-style"]
+    # These live in an nsFont member in Gecko. Should be straightforward to do manually.
+    force_stub += ["font-kerning", "font-stretch", "font-variant"]
+    # These have unusual representations in gecko.
+    force_stub += ["list-style-type", "text-overflow"]
+    # In a nsTArray, have to be done manually, but probably not too much work
+    # (the "filling them", not the "making them work")
+    force_stub += ["animation-name", "animation-duration",
                   "animation-timing-function", "animation-iteration-count",
                   "animation-direction", "animation-play-state",
                   "animation-fill-mode", "animation-delay"]
 
-   # Types used with predefined_type()-defined properties that we can auto-generate.
-   predefined_types = {
+    # These are part of shorthands so we must include them in stylo builds,
+    # but we haven't implemented the stylo glue for the longhand
+    # so we generate a stub
+    force_stub += ["list-style-image", # box
+                   "flex-basis", # position
+
+                   # transition
+                   "transition-duration", "transition-timing-function",
+                   "transition-property", "transition-delay",
+
+                   "background-size", # background
+                   "column-count", # column
+                   ]
+
+    # Types used with predefined_type()-defined properties that we can auto-generate.
+    predefined_types = {
        "LengthOrPercentage": impl_style_coord,
        "LengthOrPercentageOrAuto": impl_style_coord,
        "LengthOrPercentageOrNone": impl_style_coord,
        "Number": impl_simple,
        "Opacity": impl_simple,
-   }
+    }
+
+    keyword_longhands = [x for x in longhands if x.keyword and not x.name in force_stub]
+    predefined_longhands = [x for x in longhands
+                           if x.predefined_type in predefined_types and not x.name in force_stub]
+    stub_longhands = [x for x in longhands if x not in keyword_longhands + predefined_longhands]
 
-   keyword_longhands = [x for x in longhands if x.keyword and not x.name in force_stub]
-   predefined_longhands = [x for x in longhands
-                           if x.predefined_type in predefined_types and not x.name in force_stub]
-   stub_longhands = [x for x in longhands if x not in keyword_longhands + predefined_longhands]
+    # If one of the longhands is not handled
+    # by either:
+    # - being a keyword
+    # - being a predefined longhand
+    # - being a longhand with manual glue code (i.e. in skip_longhands)
+    # - being generated as a stub
+    #
+    # then we raise an error here.
+    #
+    # If you hit this error, please add `product="servo"` to the longhand.
+    # In case the longhand is used in a shorthand, add it to the force_stub
+    # list above.
+    for stub in stub_longhands:
+       if stub.name not in force_stub:
+           raise Exception("Don't know what to do with longhand %s in style struct %s"
+                           % (stub.name,style_struct. gecko_struct_name))
 %>
 impl ${style_struct.gecko_struct_name} {
     /*
      * Manually-Implemented Methods.
      */
     ${caller.body().strip()}
 
     /*
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -4,39 +4,35 @@
 
 use app_units::Au;
 use cssparser::{Color as CSSParserColor, Parser, RGBA, ToCss};
 use euclid::{Point2D, Size2D};
 use properties::PropertyDeclaration;
 use properties::longhands;
 use properties::longhands::background_position::computed_value::T as BackgroundPosition;
 use properties::longhands::background_size::computed_value::T as BackgroundSize;
-use properties::longhands::border_spacing::computed_value::T as BorderSpacing;
-use properties::longhands::clip::computed_value::ClipRect;
 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;
-use properties::longhands::transform::computed_value::T as TransformList;
-use properties::longhands::transform_origin::computed_value::T as TransformOrigin;
 use properties::longhands::vertical_align::computed_value::T as VerticalAlign;
 use properties::longhands::visibility::computed_value::T as Visibility;
 use properties::longhands::z_index::computed_value::T as ZIndex;
 use std::cmp;
 use std::fmt;
 use super::ComputedValues;
 use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone};
 use values::computed::{BorderRadiusSize, LengthOrNone};
 use values::computed::{CalcLengthOrPercentage, LengthOrPercentage};
 use values::computed::position::Position;
 
+
+
 // NB: This needs to be here because it needs all the longhands generated
 // beforehand.
 #[derive(Copy, Clone, Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum TransitionProperty {
     All,
     % for prop in data.longhands:
         % if prop.animatable:
@@ -291,28 +287,16 @@ impl Interpolate for VerticalAlign {
                 this.interpolate(other, time).map(|value| {
                     VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(value))
                 })
             }
             _ => Err(()),
         }
     }
 }
-
-/// https://drafts.csswg.org/css-transitions/#animtype-simple-list
-impl Interpolate for BorderSpacing {
-    #[inline]
-    fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
-        Ok(BorderSpacing {
-            horizontal: try!(self.horizontal.interpolate(&other.horizontal, time)),
-            vertical: try!(self.vertical.interpolate(&other.vertical, time)),
-        })
-    }
-}
-
 impl Interpolate for BackgroundSize {
     #[inline]
     fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
         self.0.interpolate(&other.0, time).map(BackgroundSize)
     }
 }
 
 
@@ -487,29 +471,16 @@ impl Interpolate for FontWeight {
         } else if weight < 850. {
             FontWeight::Weight800
         } else {
             FontWeight::Weight900
         })
     }
 }
 
-/// https://drafts.csswg.org/css-transitions/#animtype-rect
-impl Interpolate for ClipRect {
-    #[inline]
-    fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
-        Ok(ClipRect {
-            top: try!(self.top.interpolate(&other.top, time)),
-            right: try!(self.right.interpolate(&other.right, time)),
-            bottom: try!(self.bottom.interpolate(&other.bottom, time)),
-            left: try!(self.left.interpolate(&other.left, time)),
-        })
-    }
-}
-
 /// https://drafts.csswg.org/css-transitions/#animtype-simple-list
 impl Interpolate for Position {
     #[inline]
     fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
         Ok(Position {
             horizontal: try!(self.horizontal.interpolate(&other.horizontal, time)),
             vertical: try!(self.vertical.interpolate(&other.vertical, time)),
         })
@@ -568,148 +539,16 @@ impl Interpolate for TextShadowList {
             };
             result.push(shadow);
         }
 
         Ok(TextShadowList(result))
     }
 }
 
-/// Check if it's possible to do a direct numerical interpolation
-/// between these two transform lists.
-/// http://dev.w3.org/csswg/css-transforms/#transform-transform-animation
-fn can_interpolate_list(from_list: &[TransformOperation],
-                        to_list: &[TransformOperation]) -> bool {
-    // Lists must be equal length
-    if from_list.len() != to_list.len() {
-        return false;
-    }
-
-    // Each transform operation must match primitive type in other list
-    for (from, to) in from_list.iter().zip(to_list) {
-        match (from, to) {
-            (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) |
-            (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) |
-            (&TransformOperation::Translate(..), &TransformOperation::Translate(..)) |
-            (&TransformOperation::Scale(..), &TransformOperation::Scale(..)) |
-            (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) |
-            (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => {}
-            _ => {
-                return false;
-            }
-        }
-    }
-
-    true
-}
-
-/// Interpolate two transform lists.
-/// http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms
-fn interpolate_transform_list(from_list: &[TransformOperation],
-                              to_list: &[TransformOperation],
-                              time: f64) -> TransformList {
-    let mut result = vec![];
-
-    if can_interpolate_list(from_list, to_list) {
-        for (from, to) in from_list.iter().zip(to_list) {
-            match (from, to) {
-                (&TransformOperation::Matrix(from),
-                 &TransformOperation::Matrix(_to)) => {
-                    // TODO(gw): Implement matrix decomposition and interpolation
-                    result.push(TransformOperation::Matrix(from));
-                }
-                (&TransformOperation::Skew(fx, fy),
-                 &TransformOperation::Skew(tx, ty)) => {
-                    let ix = fx.interpolate(&tx, time).unwrap();
-                    let iy = fy.interpolate(&ty, time).unwrap();
-                    result.push(TransformOperation::Skew(ix, iy));
-                }
-                (&TransformOperation::Translate(fx, fy, fz),
-                 &TransformOperation::Translate(tx, ty, tz)) => {
-                    let ix = fx.interpolate(&tx, time).unwrap();
-                    let iy = fy.interpolate(&ty, time).unwrap();
-                    let iz = fz.interpolate(&tz, time).unwrap();
-                    result.push(TransformOperation::Translate(ix, iy, iz));
-                }
-                (&TransformOperation::Scale(fx, fy, fz),
-                 &TransformOperation::Scale(tx, ty, tz)) => {
-                    let ix = fx.interpolate(&tx, time).unwrap();
-                    let iy = fy.interpolate(&ty, time).unwrap();
-                    let iz = fz.interpolate(&tz, time).unwrap();
-                    result.push(TransformOperation::Scale(ix, iy, iz));
-                }
-                (&TransformOperation::Rotate(fx, fy, fz, fa),
-                 &TransformOperation::Rotate(tx, ty, tz, ta)) => {
-                    let norm_f = ((fx * fx) + (fy * fy) + (fz * fz)).sqrt();
-                    let norm_t = ((tx * tx) + (ty * ty) + (tz * tz)).sqrt();
-                    let (fx, fy, fz) = (fx / norm_f, fy / norm_f, fz / norm_f);
-                    let (tx, ty, tz) = (tx / norm_t, ty / norm_t, tz / norm_t);
-                    if fx == tx && fy == ty && fz == tz {
-                        let ia = fa.interpolate(&ta, time).unwrap();
-                        result.push(TransformOperation::Rotate(fx, fy, fz, ia));
-                    } else {
-                        // TODO(gw): Implement matrix decomposition and interpolation
-                        result.push(TransformOperation::Rotate(fx, fy, fz, fa));
-                    }
-                }
-                (&TransformOperation::Perspective(fd),
-                 &TransformOperation::Perspective(_td)) => {
-                    // TODO(gw): Implement matrix decomposition and interpolation
-                    result.push(TransformOperation::Perspective(fd));
-                }
-                _ => {
-                    // This should be unreachable due to the can_interpolate_list() call.
-                    unreachable!();
-                }
-            }
-        }
-    } else {
-        // TODO(gw): Implement matrix decomposition and interpolation
-        result.extend_from_slice(from_list);
-    }
-
-    TransformList(Some(result))
-}
-
-/// Build an equivalent 'identity transform function list' based
-/// on an existing transform list.
-/// http://dev.w3.org/csswg/css-transforms/#none-transform-animation
-fn build_identity_transform_list(list: &[TransformOperation]) -> Vec<TransformOperation> {
-    let mut result = vec!();
-
-    for operation in list {
-        match *operation {
-            TransformOperation::Matrix(..) => {
-                let identity = ComputedMatrix::identity();
-                result.push(TransformOperation::Matrix(identity));
-            }
-            TransformOperation::Skew(..) => {
-                result.push(TransformOperation::Skew(Angle(0.0), Angle(0.0)));
-            }
-            TransformOperation::Translate(..) => {
-                result.push(TransformOperation::Translate(LengthOrPercentage::zero(),
-                                                          LengthOrPercentage::zero(),
-                                                          Au(0)));
-            }
-            TransformOperation::Scale(..) => {
-                result.push(TransformOperation::Scale(1.0, 1.0, 1.0));
-            }
-            TransformOperation::Rotate(..) => {
-                result.push(TransformOperation::Rotate(0.0, 0.0, 1.0, Angle(0.0)));
-            }
-            TransformOperation::Perspective(..) => {
-                // http://dev.w3.org/csswg/css-transforms/#identity-transform-function
-                let identity = ComputedMatrix::identity();
-                result.push(TransformOperation::Matrix(identity));
-            }
-        }
-    }
-
-    result
-}
 
 impl Interpolate for BoxShadowList {
     #[inline]
     fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
         // The inset value must change
         let mut zero = BoxShadow {
             offset_x: Au(0),
             offset_y: Au(0),
@@ -775,47 +614,178 @@ impl Interpolate for LengthOrNone {
         match (*self, *other) {
             (LengthOrNone::Length(ref len), LengthOrNone::Length(ref other)) =>
                 len.interpolate(&other, time).map(LengthOrNone::Length),
             _ => Err(()),
         }
     }
 }
 
-impl Interpolate for TransformOrigin {
-    fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
-        Ok(TransformOrigin {
-            horizontal: try!(self.horizontal.interpolate(&other.horizontal, time)),
-            vertical: try!(self.vertical.interpolate(&other.vertical, time)),
-            depth: try!(self.depth.interpolate(&other.depth, time)),
-        })
-    }
-}
+% if product == "servo":
+    use properties::longhands::transform::computed_value::ComputedMatrix;
+    use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation;
+    use properties::longhands::transform::computed_value::T as TransformList;
+
+    /// Check if it's possible to do a direct numerical interpolation
+    /// between these two transform lists.
+    /// http://dev.w3.org/csswg/css-transforms/#transform-transform-animation
+    fn can_interpolate_list(from_list: &[TransformOperation],
+                            to_list: &[TransformOperation]) -> bool {
+        // Lists must be equal length
+        if from_list.len() != to_list.len() {
+            return false;
+        }
+
+        // Each transform operation must match primitive type in other list
+        for (from, to) in from_list.iter().zip(to_list) {
+            match (from, to) {
+                (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) |
+                (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) |
+                (&TransformOperation::Translate(..), &TransformOperation::Translate(..)) |
+                (&TransformOperation::Scale(..), &TransformOperation::Scale(..)) |
+                (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) |
+                (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => {}
+                _ => {
+                    return false;
+                }
+            }
+        }
 
-/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms
-impl Interpolate for TransformList {
-    #[inline]
-    fn interpolate(&self, other: &TransformList, time: f64) -> Result<Self, ()> {
-        // http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms
-        let result = match (&self.0, &other.0) {
-            (&Some(ref from_list), &Some(ref to_list)) => {
-                // Two lists of transforms
-                interpolate_transform_list(from_list, &to_list, time)
+        true
+    }
+
+    /// Build an equivalent 'identity transform function list' based
+    /// on an existing transform list.
+    /// http://dev.w3.org/csswg/css-transforms/#none-transform-animation
+    fn build_identity_transform_list(list: &[TransformOperation]) -> Vec<TransformOperation> {
+        let mut result = vec!();
+
+        for operation in list {
+            match *operation {
+                TransformOperation::Matrix(..) => {
+                    let identity = ComputedMatrix::identity();
+                    result.push(TransformOperation::Matrix(identity));
+                }
+                TransformOperation::Skew(..) => {
+                    result.push(TransformOperation::Skew(Angle(0.0), Angle(0.0)));
+                }
+                TransformOperation::Translate(..) => {
+                    result.push(TransformOperation::Translate(LengthOrPercentage::zero(),
+                                                              LengthOrPercentage::zero(),
+                                                              Au(0)));
+                }
+                TransformOperation::Scale(..) => {
+                    result.push(TransformOperation::Scale(1.0, 1.0, 1.0));
+                }
+                TransformOperation::Rotate(..) => {
+                    result.push(TransformOperation::Rotate(0.0, 0.0, 1.0, Angle(0.0)));
+                }
+                TransformOperation::Perspective(..) => {
+                    // http://dev.w3.org/csswg/css-transforms/#identity-transform-function
+                    let identity = ComputedMatrix::identity();
+                    result.push(TransformOperation::Matrix(identity));
+                }
             }
-            (&Some(ref from_list), &None) => {
-                // http://dev.w3.org/csswg/css-transforms/#none-transform-animation
-                let to_list = build_identity_transform_list(from_list);
-                interpolate_transform_list(from_list, &to_list, time)
-            }
-            (&None, &Some(ref to_list)) => {
-                // http://dev.w3.org/csswg/css-transforms/#none-transform-animation
-                let from_list = build_identity_transform_list(to_list);
-                interpolate_transform_list(&from_list, to_list, time)
+        }
+
+        result
+    }
+
+    /// Interpolate two transform lists.
+    /// http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms
+    fn interpolate_transform_list(from_list: &[TransformOperation],
+                                  to_list: &[TransformOperation],
+                                  time: f64) -> TransformList {
+        let mut result = vec![];
+
+        if can_interpolate_list(from_list, to_list) {
+            for (from, to) in from_list.iter().zip(to_list) {
+                match (from, to) {
+                    (&TransformOperation::Matrix(from),
+                     &TransformOperation::Matrix(_to)) => {
+                        // TODO(gw): Implement matrix decomposition and interpolation
+                        result.push(TransformOperation::Matrix(from));
+                    }
+                    (&TransformOperation::Skew(fx, fy),
+                     &TransformOperation::Skew(tx, ty)) => {
+                        let ix = fx.interpolate(&tx, time).unwrap();
+                        let iy = fy.interpolate(&ty, time).unwrap();
+                        result.push(TransformOperation::Skew(ix, iy));
+                    }
+                    (&TransformOperation::Translate(fx, fy, fz),
+                     &TransformOperation::Translate(tx, ty, tz)) => {
+                        let ix = fx.interpolate(&tx, time).unwrap();
+                        let iy = fy.interpolate(&ty, time).unwrap();
+                        let iz = fz.interpolate(&tz, time).unwrap();
+                        result.push(TransformOperation::Translate(ix, iy, iz));
+                    }
+                    (&TransformOperation::Scale(fx, fy, fz),
+                     &TransformOperation::Scale(tx, ty, tz)) => {
+                        let ix = fx.interpolate(&tx, time).unwrap();
+                        let iy = fy.interpolate(&ty, time).unwrap();
+                        let iz = fz.interpolate(&tz, time).unwrap();
+                        result.push(TransformOperation::Scale(ix, iy, iz));
+                    }
+                    (&TransformOperation::Rotate(fx, fy, fz, fa),
+                     &TransformOperation::Rotate(tx, ty, tz, ta)) => {
+                        let norm_f = ((fx * fx) + (fy * fy) + (fz * fz)).sqrt();
+                        let norm_t = ((tx * tx) + (ty * ty) + (tz * tz)).sqrt();
+                        let (fx, fy, fz) = (fx / norm_f, fy / norm_f, fz / norm_f);
+                        let (tx, ty, tz) = (tx / norm_t, ty / norm_t, tz / norm_t);
+                        if fx == tx && fy == ty && fz == tz {
+                            let ia = fa.interpolate(&ta, time).unwrap();
+                            result.push(TransformOperation::Rotate(fx, fy, fz, ia));
+                        } else {
+                            // TODO(gw): Implement matrix decomposition and interpolation
+                            result.push(TransformOperation::Rotate(fx, fy, fz, fa));
+                        }
+                    }
+                    (&TransformOperation::Perspective(fd),
+                     &TransformOperation::Perspective(_td)) => {
+                        // TODO(gw): Implement matrix decomposition and interpolation
+                        result.push(TransformOperation::Perspective(fd));
+                    }
+                    _ => {
+                        // This should be unreachable due to the can_interpolate_list() call.
+                        unreachable!();
+                    }
+                }
             }
-            _ => {
-                // http://dev.w3.org/csswg/css-transforms/#none-none-animation
-                TransformList(None)
-            }
-        };
+        } else {
+            // TODO(gw): Implement matrix decomposition and interpolation
+            result.extend_from_slice(from_list);
+        }
+
+        TransformList(Some(result))
+    }
 
-        Ok(result)
+    /// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms
+    impl Interpolate for TransformList {
+        #[inline]
+        fn interpolate(&self, other: &TransformList, time: f64) -> Result<Self, ()> {
+            // http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms
+            let result = match (&self.0, &other.0) {
+                (&Some(ref from_list), &Some(ref to_list)) => {
+                    // Two lists of transforms
+                    interpolate_transform_list(from_list, &to_list, time)
+                }
+                (&Some(ref from_list), &None) => {
+                    // http://dev.w3.org/csswg/css-transforms/#none-transform-animation
+                    let to_list = build_identity_transform_list(from_list);
+                    interpolate_transform_list(from_list, &to_list, time)
+                }
+                (&None, &Some(ref to_list)) => {
+                    // http://dev.w3.org/csswg/css-transforms/#none-transform-animation
+                    let from_list = build_identity_transform_list(to_list);
+                    interpolate_transform_list(&from_list, to_list, time)
+                }
+                _ => {
+                    // http://dev.w3.org/csswg/css-transforms/#none-none-animation
+                    TransformList(None)
+                }
+            };
+
+            Ok(result)
+        }
     }
-}
+% endif
+
+
--- a/servo/components/style/properties/longhand/column.mako.rs
+++ b/servo/components/style/properties/longhand/column.mako.rs
@@ -148,17 +148,17 @@
                 return Err(())
             }
             Ok(SpecifiedValue::Specified(count as u32))
         }
     }
 </%helpers:longhand>
 
 // FIXME: This prop should be animatable.
-<%helpers:longhand name="column-gap" experimental="True" animatable="False">
+<%helpers:longhand name="column-gap" experimental="True" products="servo" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
     use values::HasViewportPercentage;
 
     impl HasViewportPercentage for SpecifiedValue {
         fn has_viewport_percentage(&self) -> bool {
             match *self {
--- a/servo/components/style/properties/longhand/counters.mako.rs
+++ b/servo/components/style/properties/longhand/counters.mako.rs
@@ -168,17 +168,17 @@
         if !content.is_empty() {
             Ok(SpecifiedValue::Content(content))
         } else {
             Err(())
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="counter-increment" animatable="False">
+<%helpers:longhand name="counter-increment" products="servo" animatable="False">
     use std::fmt;
     use super::content;
     use values::NoViewportPercentage;
     use values::computed::ComputedValueAsSpecified;
 
     use cssparser::{ToCss, Token, serialize_identifier};
     use std::borrow::{Cow, ToOwned};
 
@@ -240,16 +240,16 @@
         if !counters.is_empty() {
             Ok(SpecifiedValue(counters))
         } else {
             Err(())
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="counter-reset" animatable="False">
+<%helpers:longhand name="counter-reset" products="servo" animatable="False">
     pub use super::counter_increment::{SpecifiedValue, computed_value, get_initial_value};
     use super::counter_increment::{parse_common};
 
     pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
         parse_common(0, input)
     }
 </%helpers:longhand>
--- a/servo/components/style/properties/longhand/effects.mako.rs
+++ b/servo/components/style/properties/longhand/effects.mako.rs
@@ -170,39 +170,54 @@
             spread_radius: lengths[3],
             color: color,
             inset: inset,
         })
     }
 </%helpers:vector_longhand>
 
 // FIXME: This prop should be animatable
-<%helpers:longhand name="clip" animatable="False">
+<%helpers:longhand name="clip" products="servo" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
     use values::HasViewportPercentage;
 
     // NB: `top` and `left` are 0 if `auto` per CSS 2.1 11.1.2.
 
     pub mod computed_value {
         use app_units::Au;
+        use properties::animated_properties::Interpolate;
 
         #[derive(Clone, PartialEq, Eq, Copy, Debug)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct ClipRect {
             pub top: Au,
             pub right: Option<Au>,
             pub bottom: Option<Au>,
             pub left: Au,
         }
 
         #[derive(Debug, Clone, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct T(pub Option<ClipRect>);
+
+
+        /// https://drafts.csswg.org/css-transitions/#animtype-rect
+        impl Interpolate for ClipRect {
+            #[inline]
+            fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
+                Ok(ClipRect {
+                    top: try!(self.top.interpolate(&other.top, time)),
+                    right: try!(self.right.interpolate(&other.right, time)),
+                    bottom: try!(self.bottom.interpolate(&other.bottom, time)),
+                    left: try!(self.left.interpolate(&other.left, time)),
+                })
+            }
+        }
     }
 
     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("auto"),
                 Some(rect) => {
                     try!(dest.write_str("rect("));
@@ -359,17 +374,17 @@
                 bottom: bottom,
                 left: left.unwrap_or(Length::Absolute(Au(0))),
             })))
         })
     }
 </%helpers:longhand>
 
 // FIXME: This prop should be animatable
-<%helpers:longhand name="filter" animatable="False">
+<%helpers:longhand name="filter" products="servo" animatable="False">
     //pub use self::computed_value::T as SpecifiedValue;
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
     use values::CSSFloat;
     use values::HasViewportPercentage;
     use values::specified::{Angle, Length};
 
@@ -612,17 +627,17 @@
                     SpecifiedFilter::Saturate(factor) => computed_value::Filter::Saturate(factor),
                     SpecifiedFilter::Sepia(factor) => computed_value::Filter::Sepia(factor),
                 }
             }).collect() }
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="transform" animatable="True">
+<%helpers:longhand name="transform" products="servo" animatable="True">
     use app_units::Au;
     use values::CSSFloat;
     use values::HasViewportPercentage;
 
     use cssparser::ToCss;
     use std::fmt;
 
     pub mod computed_value {
@@ -1191,35 +1206,46 @@ pub fn parse_origin(_: &ParserContext, i
                          "border-box fill-box view-box",
                          products="gecko",
                          animatable=False)}
 
 ${helpers.single_keyword("transform-style",
                          "auto flat preserve-3d",
                          animatable=False)}
 
-<%helpers:longhand name="transform-origin" animatable="True">
+<%helpers:longhand name="transform-origin" products="servo" animatable="True">
     use app_units::Au;
     use values::LocalToCss;
     use values::HasViewportPercentage;
     use values::specified::{Length, LengthOrPercentage, Percentage};
 
     use cssparser::ToCss;
     use std::fmt;
 
     pub mod computed_value {
+        use properties::animated_properties::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 {
+            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 HasViewportPercentage for SpecifiedValue {
         fn has_viewport_percentage(&self) -> bool {
             self.horizontal.has_viewport_percentage() ||
             self.vertical.has_viewport_percentage() ||
             self.depth.has_viewport_percentage()
         }
@@ -1283,20 +1309,21 @@ pub fn parse_origin(_: &ParserContext, i
             }
         }
     }
 </%helpers:longhand>
 
 ${helpers.predefined_type("perspective",
                           "LengthOrNone",
                           "computed::LengthOrNone::None",
+                          products="servo",
                           animatable=True)}
 
 // FIXME: This prop should be animatable
-<%helpers:longhand name="perspective-origin" animatable="False">
+<%helpers:longhand name="perspective-origin" products="servo" animatable="False">
     use values::HasViewportPercentage;
     use values::specified::{LengthOrPercentage, Percentage};
 
     use cssparser::ToCss;
     use std::fmt;
 
     pub mod computed_value {
         use values::computed::LengthOrPercentage;
--- a/servo/components/style/properties/longhand/inherited_box.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_box.mako.rs
@@ -35,17 +35,17 @@
                          animatable=False)}
 
 // CSS Color Module Level 4
 // https://drafts.csswg.org/css-color/
 ${helpers.single_keyword("color-adjust",
                          "economy exact", products="gecko",
                          animatable=False)}
 
-<%helpers:longhand name="image-rendering" animatable="False">
+<%helpers:longhand name="image-rendering" products="servo" animatable="False">
     pub mod computed_value {
         use cssparser::ToCss;
         use std::fmt;
 
         #[derive(Copy, Clone, Debug, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
         pub enum T {
             Auto,
--- a/servo/components/style/properties/longhand/inherited_table.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_table.mako.rs
@@ -11,33 +11,45 @@
                          animatable=False)}
 ${helpers.single_keyword("empty-cells", "show hide",
                          gecko_constant_prefix="NS_STYLE_TABLE_EMPTY_CELLS",
                          animatable=False)}
 ${helpers.single_keyword("caption-side", "top bottom",
                          extra_gecko_values="right left top-outside bottom-outside",
                          animatable=False)}
 
-<%helpers:longhand name="border-spacing" animatable="False">
+<%helpers:longhand name="border-spacing" products="servo" animatable="False">
     use app_units::Au;
     use values::LocalToCss;
     use values::HasViewportPercentage;
 
     use cssparser::ToCss;
     use std::fmt;
 
     pub mod computed_value {
         use app_units::Au;
+        use properties::animated_properties::Interpolate;
 
         #[derive(Clone, Copy, Debug, PartialEq, RustcEncodable)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct T {
             pub horizontal: Au,
             pub vertical: Au,
         }
+
+        /// https://drafts.csswg.org/css-transitions/#animtype-simple-list
+        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)),
+                })
+            }
+        }
     }
 
     impl HasViewportPercentage for SpecifiedValue {
         fn has_viewport_percentage(&self) -> bool {
             return self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage()
         }
     }
 
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -187,17 +187,17 @@
     }
     pub fn parse(_context: &ParserContext, input: &mut Parser)
                  -> Result<SpecifiedValue, ()> {
         computed_value::T::parse(input)
     }
 </%helpers:longhand>
 
 // FIXME: This prop should be animatable.
-<%helpers:longhand name="letter-spacing" animatable="False">
+<%helpers:longhand name="letter-spacing" products="servo" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
     use values::HasViewportPercentage;
 
     impl HasViewportPercentage for SpecifiedValue {
         fn has_viewport_percentage(&self) -> bool {
             match *self {
--- a/servo/components/style/properties/longhand/list.mako.rs
+++ b/servo/components/style/properties/longhand/list.mako.rs
@@ -91,17 +91,17 @@
         }
     }
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(None)
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="quotes" animatable="False">
+<%helpers:longhand name="quotes" products="servo" animatable="False">
     use std::borrow::Cow;
     use std::fmt;
     use values::NoViewportPercentage;
     use values::computed::ComputedValueAsSpecified;
 
     use cssparser::{ToCss, Token};
 
     pub use self::computed_value::T as SpecifiedValue;
--- a/servo/components/style/properties/longhand/outline.mako.rs
+++ b/servo/components/style/properties/longhand/outline.mako.rs
@@ -73,9 +73,9 @@
 // TODO: Should they animate?
 % for corner in ["topleft", "topright", "bottomright", "bottomleft"]:
     ${helpers.predefined_type("-moz-outline-radius-" + corner, "BorderRadiusSize",
                               "computed::BorderRadiusSize::zero()",
                               "parse", products="gecko",
                               animatable=False)}
 % endfor
 
-${helpers.predefined_type("outline-offset", "Length", "Au(0)", animatable=True)}
+${helpers.predefined_type("outline-offset", "Length", "Au(0)", products="servo", animatable=True)}
--- a/servo/components/style/properties/longhand/position.mako.rs
+++ b/servo/components/style/properties/longhand/position.mako.rs
@@ -105,17 +105,17 @@
 
 ${helpers.single_keyword("align-self", "auto stretch flex-start flex-end center baseline",
                          experimental=True,
                          need_clone=True,
                          gecko_constant_prefix="NS_STYLE_ALIGN",
                          animatable=False)}
 
 // https://drafts.csswg.org/css-flexbox/#propdef-order
-<%helpers:longhand name="order" animatable="True">
+<%helpers:longhand name="order" products="servo" animatable="True">
     use values::computed::ComputedValueAsSpecified;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
 
     pub type SpecifiedValue = computed_value::T;
 
     pub mod computed_value {
         pub type T = i32;
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -1147,17 +1147,22 @@ impl PropertyDeclaration {
                         Err(()) => match shorthands::${shorthand.ident}::parse(context, input, result_list) {
                             Ok(()) => PropertyDeclarationParseResult::ValidOrIgnoredDeclaration,
                             Err(()) => PropertyDeclarationParseResult::InvalidValue,
                         }
                     }
                 },
             % endfor
 
-            _ => PropertyDeclarationParseResult::UnknownProperty
+            _ => {
+                if cfg!(all(debug_assertions, feature = "gecko")) && !name.starts_with('-') {
+                    println!("stylo: Unimplemented property setter: {}", name);
+                }
+                PropertyDeclarationParseResult::UnknownProperty
+            }
         }
     }
 
     pub fn shorthands(&self) -> &'static [Shorthand] {
         // first generate longhand to shorthands lookup map
         <%
             longhand_to_shorthand_map = {}
             for shorthand in data.shorthands: