Bug 1594946 - Treat 3d translate/scale as 2d if the value can be expressed as 2d. r=emilio
authorBoris Chiou <boris.chiou@gmail.com>
Fri, 15 Nov 2019 19:38:24 +0000
changeset 502273 998d992ca22bda687da401a82ddb42afc10af96a
parent 502272 09df60fc6e235e6d4c9594cc7edcb55cb82d460e
child 502274 c1e961421fe1aea3523b6fdf4dba49126a70f6a7
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs1594946
milestone72.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
Bug 1594946 - Treat 3d translate/scale as 2d if the value can be expressed as 2d. r=emilio For the individual transform properties if they spec a value that can be expressed as 2d we treat as 2d and serialize accordingly. We drop Translate::Translate and Scale::Scale, and then rename Translate::Translate3D as Translate::Translate, Scale::Scale3D as Scale::Scale. So now we use Translate::Translate to represent 2d and 3d translation, and Scale::Scale to represent 2d and 3d scale. There is no difference between 2d and 3d translate/scale in Gecko because we always convert them into 3d format to layers (on the compositor thread), so this change makes things simpler. Differential Revision: https://phabricator.services.mozilla.com/D52931
layout/painting/nsDisplayList.cpp
layout/style/StyleAnimationValue.cpp
layout/style/nsStyleTransformMatrix.cpp
servo/components/style/values/animated/transform.rs
servo/components/style/values/generics/transform.rs
servo/components/style/values/specified/transform.rs
testing/web-platform/meta/css/css-transforms/animation/scale-interpolation.html.ini
testing/web-platform/meta/css/css-transforms/animation/translate-interpolation.html.ini
testing/web-platform/tests/css/css-transforms/animation/scale-interpolation.html
testing/web-platform/tests/css/css-transforms/animation/translate-interpolation.html
testing/web-platform/tests/css/css-transforms/parsing/scale-parsing-valid.html
testing/web-platform/tests/css/css-transforms/parsing/translate-parsing-valid.html
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -231,22 +231,16 @@ static Scale GetScale(const StyleScale& 
   Scale result(1., 1., 1.);
   switch (aValue.tag) {
     case StyleScale::Tag::None:
       break;
     case StyleScale::Tag::Scale: {
       auto& scale = aValue.AsScale();
       result.x() = scale._0;
       result.y() = scale._1;
-      break;
-    }
-    case StyleScale::Tag::Scale3D: {
-      auto& scale = aValue.AsScale3D();
-      result.x() = scale._0;
-      result.y() = scale._1;
       result.z() = scale._2;
       break;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("Unsupported scale");
   }
   return result;
 }
@@ -267,21 +261,16 @@ static Translation GetTranslate(
 static Translation GetTranslate(const StyleTranslate& aValue,
                                 TransformReferenceBox& aRefBox) {
   Translation result(0, 0, 0);
   switch (aValue.tag) {
     case StyleTranslate::Tag::None:
       break;
     case StyleTranslate::Tag::Translate: {
       auto& translate = aValue.AsTranslate();
-      result = GetTranslate(aRefBox, translate._0, translate._1);
-      break;
-    }
-    case StyleTranslate::Tag::Translate3D: {
-      auto& translate = aValue.AsTranslate3D();
       result = GetTranslate(aRefBox, translate._0, translate._1, translate._2);
       break;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("Unsupported translate");
   }
   return result;
 }
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -420,22 +420,22 @@ already_AddRefed<RawServoAnimationValue>
       return Servo_AnimationValue_Color(aProperty, aAnimatable.get_nscolor())
           .Consume();
     case layers::Animatable::TRotate: {
       auto rotate = RotateFromLayers(aAnimatable.get_Rotate());
       return Servo_AnimationValue_Rotate(&rotate).Consume();
     }
     case layers::Animatable::TScale: {
       const layers::Scale& s = aAnimatable.get_Scale();
-      auto scale = StyleScale::Scale3D(s.x(), s.y(), s.z());
+      auto scale = StyleScale::Scale(s.x(), s.y(), s.z());
       return Servo_AnimationValue_Scale(&scale).Consume();
     }
     case layers::Animatable::TTranslation: {
       const layers::Translation& t = aAnimatable.get_Translation();
-      auto translate = StyleTranslate::Translate3D(
+      auto translate = StyleTranslate::Translate(
           LengthPercentage::FromPixels(t.x()),
           LengthPercentage::FromPixels(t.y()), Length{t.z()});
       return Servo_AnimationValue_Translate(&translate).Consume();
     }
     case layers::Animatable::TOffsetPath: {
       const layers::OffsetPath& p = aAnimatable.get_OffsetPath();
       switch (p.type()) {
         case layers::OffsetPath::TArrayOfPathCommand: {
--- a/layout/style/nsStyleTransformMatrix.cpp
+++ b/layout/style/nsStyleTransformMatrix.cpp
@@ -488,22 +488,19 @@ Matrix4x4 ReadTransforms(const StyleTran
 
 static void ProcessTranslate(Matrix4x4& aMatrix,
                              const StyleTranslate& aTranslate,
                              TransformReferenceBox& aRefBox) {
   switch (aTranslate.tag) {
     case StyleTranslate::Tag::None:
       return;
     case StyleTranslate::Tag::Translate:
-      return ProcessTranslate(aMatrix, aTranslate.AsTranslate()._0,
-                              aTranslate.AsTranslate()._1, aRefBox);
-    case StyleTranslate::Tag::Translate3D:
-      return ProcessTranslate3D(aMatrix, aTranslate.AsTranslate3D()._0,
-                                aTranslate.AsTranslate3D()._1,
-                                aTranslate.AsTranslate3D()._2, aRefBox);
+      return ProcessTranslate3D(aMatrix, aTranslate.AsTranslate()._0,
+                                aTranslate.AsTranslate()._1,
+                                aTranslate.AsTranslate()._2, aRefBox);
     default:
       MOZ_ASSERT_UNREACHABLE("Huh?");
   }
 }
 
 static void ProcessRotate(Matrix4x4& aMatrix, const StyleRotate& aRotate,
                           TransformReferenceBox& aRefBox) {
   switch (aRotate.tag) {
@@ -523,20 +520,17 @@ static void ProcessRotate(Matrix4x4& aMa
 
 static void ProcessScale(Matrix4x4& aMatrix, const StyleScale& aScale,
                          TransformReferenceBox& aRefBox) {
   switch (aScale.tag) {
     case StyleScale::Tag::None:
       return;
     case StyleScale::Tag::Scale:
       return ProcessScaleHelper(aMatrix, aScale.AsScale()._0,
-                                aScale.AsScale()._1, 1.0f);
-    case StyleScale::Tag::Scale3D:
-      return ProcessScaleHelper(aMatrix, aScale.AsScale3D()._0,
-                                aScale.AsScale3D()._1, aScale.AsScale3D()._2);
+                                aScale.AsScale()._1, aScale.AsScale()._2);
     default:
       MOZ_ASSERT_UNREACHABLE("Huh?");
   }
 }
 
 Matrix4x4 ReadTransforms(const StyleTranslate& aTranslate,
                          const StyleRotate& aRotate, const StyleScale& aScale,
                          const Maybe<MotionPathData>& aMotion,
--- a/servo/components/style/values/animated/transform.rs
+++ b/servo/components/style/values/animated/transform.rs
@@ -134,20 +134,20 @@ impl Animate for MatrixDecomposed2D {
 
 impl ComputeSquaredDistance for MatrixDecomposed2D {
     #[inline]
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
         // Use Radian to compute the distance.
         const RAD_PER_DEG: f64 = std::f64::consts::PI / 180.0;
         let angle1 = self.angle as f64 * RAD_PER_DEG;
         let angle2 = other.angle as f64 * RAD_PER_DEG;
-        Ok(self.translate.compute_squared_distance(&other.translate)? +
-            self.scale.compute_squared_distance(&other.scale)? +
-            angle1.compute_squared_distance(&angle2)? +
-            self.matrix.compute_squared_distance(&other.matrix)?)
+        Ok(self.translate.compute_squared_distance(&other.translate)?
+            + self.scale.compute_squared_distance(&other.scale)?
+            + angle1.compute_squared_distance(&angle2)?
+            + self.matrix.compute_squared_distance(&other.matrix)?)
     }
 }
 
 impl From<Matrix3D> for MatrixDecomposed2D {
     /// Decompose a 2D matrix.
     /// <https://drafts.csswg.org/css-transforms/#decomposing-a-2d-matrix>
     fn from(matrix: Matrix3D) -> MatrixDecomposed2D {
         let mut row0x = matrix.m11;
@@ -311,19 +311,19 @@ impl Animate for Scale3D {
 #[derive(Animate, Clone, Copy, Debug)]
 pub struct Skew(f32, f32, f32);
 
 impl ComputeSquaredDistance for Skew {
     // We have to use atan() to convert the skew factors into skew angles, so implement
     // ComputeSquaredDistance manually.
     #[inline]
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
-        Ok(self.0.atan().compute_squared_distance(&other.0.atan())? +
-            self.1.atan().compute_squared_distance(&other.1.atan())? +
-            self.2.atan().compute_squared_distance(&other.2.atan())?)
+        Ok(self.0.atan().compute_squared_distance(&other.0.atan())?
+            + self.1.atan().compute_squared_distance(&other.1.atan())?
+            + self.2.atan().compute_squared_distance(&other.2.atan())?)
     }
 }
 
 /// A 3d perspective transformation.
 #[derive(Clone, ComputeSquaredDistance, Copy, Debug)]
 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
 pub struct Perspective(pub f32, pub f32, pub f32, pub f32);
 
@@ -389,19 +389,19 @@ impl Quaternion {
 impl Animate for Quaternion {
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         use std::f64;
 
         let (this_weight, other_weight) = procedure.weights();
         debug_assert!(
             // Doule EPSILON since both this_weight and other_weght have calculation errors
             // which are approximately equal to EPSILON.
-            (this_weight + other_weight - 1.0f64).abs() <= f64::EPSILON * 2.0 ||
-                other_weight == 1.0f64 ||
-                other_weight == 0.0f64,
+            (this_weight + other_weight - 1.0f64).abs() <= f64::EPSILON * 2.0
+                || other_weight == 1.0f64
+                || other_weight == 0.0f64,
             "animate should only be used for interpolating or accumulating transforms"
         );
 
         // We take a specialized code path for accumulation (where other_weight
         // is 1).
         if let Procedure::Accumulate { .. } = procedure {
             debug_assert_eq!(other_weight, 1.0);
             if this_weight == 0.0 {
@@ -825,27 +825,27 @@ impl ComputeSquaredDistance for Matrix3D
 // ------------------------------------
 // Animation for Transform list.
 // ------------------------------------
 fn is_matched_operation(
     first: &ComputedTransformOperation,
     second: &ComputedTransformOperation,
 ) -> bool {
     match (first, second) {
-        (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) |
-        (&TransformOperation::Matrix3D(..), &TransformOperation::Matrix3D(..)) |
-        (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) |
-        (&TransformOperation::SkewX(..), &TransformOperation::SkewX(..)) |
-        (&TransformOperation::SkewY(..), &TransformOperation::SkewY(..)) |
-        (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) |
-        (&TransformOperation::Rotate3D(..), &TransformOperation::Rotate3D(..)) |
-        (&TransformOperation::RotateX(..), &TransformOperation::RotateX(..)) |
-        (&TransformOperation::RotateY(..), &TransformOperation::RotateY(..)) |
-        (&TransformOperation::RotateZ(..), &TransformOperation::RotateZ(..)) |
-        (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => true,
+        (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..))
+        | (&TransformOperation::Matrix3D(..), &TransformOperation::Matrix3D(..))
+        | (&TransformOperation::Skew(..), &TransformOperation::Skew(..))
+        | (&TransformOperation::SkewX(..), &TransformOperation::SkewX(..))
+        | (&TransformOperation::SkewY(..), &TransformOperation::SkewY(..))
+        | (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..))
+        | (&TransformOperation::Rotate3D(..), &TransformOperation::Rotate3D(..))
+        | (&TransformOperation::RotateX(..), &TransformOperation::RotateX(..))
+        | (&TransformOperation::RotateY(..), &TransformOperation::RotateY(..))
+        | (&TransformOperation::RotateZ(..), &TransformOperation::RotateZ(..))
+        | (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => true,
         // Match functions that have the same primitive transform function
         (a, b) if a.is_translate() && b.is_translate() => true,
         (a, b) if a.is_scale() && b.is_scale() => true,
         (a, b) if a.is_rotate() && b.is_rotate() => true,
         // InterpolateMatrix and AccumulateMatrix are for mismatched transforms
         _ => false,
     }
 }
@@ -890,89 +890,89 @@ impl Animate for ComputedTransform {
 
         match (this_remainder, other_remainder) {
             // If there is a remainder from *both* lists we must have had mismatched functions.
             // => Add the remainders to a suitable ___Matrix function.
             (Some(this_remainder), Some(other_remainder)) => match procedure {
                 Procedure::Add => {
                     debug_assert!(false, "Should have already dealt with add by the point");
                     return Err(());
-                },
+                }
                 Procedure::Interpolate { progress } => {
                     result.push(TransformOperation::InterpolateMatrix {
                         from_list: Transform(this_remainder.to_vec().into()),
                         to_list: Transform(other_remainder.to_vec().into()),
                         progress: Percentage(progress as f32),
                     });
-                },
+                }
                 Procedure::Accumulate { count } => {
                     result.push(TransformOperation::AccumulateMatrix {
                         from_list: Transform(this_remainder.to_vec().into()),
                         to_list: Transform(other_remainder.to_vec().into()),
                         count: cmp::min(count, i32::max_value() as u64) as i32,
                     });
-                },
+                }
             },
             // If there is a remainder from just one list, then one list must be shorter but
             // completely match the type of the corresponding functions in the longer list.
             // => Interpolate the remainder with identity transforms.
             (Some(remainder), None) | (None, Some(remainder)) => {
                 let fill_right = this_remainder.is_some();
                 result.append(
                     &mut remainder
                         .iter()
                         .map(|transform| {
                             let identity = transform.to_animated_zero().unwrap();
 
                             match transform {
                                 // We can't interpolate/accumulate ___Matrix types directly with a
                                 // matrix. Instead we need to wrap it in another ___Matrix type.
-                                TransformOperation::AccumulateMatrix { .. } |
-                                TransformOperation::InterpolateMatrix { .. } => {
+                                TransformOperation::AccumulateMatrix { .. }
+                                | TransformOperation::InterpolateMatrix { .. } => {
                                     let transform_list = Transform(vec![transform.clone()].into());
                                     let identity_list = Transform(vec![identity].into());
                                     let (from_list, to_list) = if fill_right {
                                         (transform_list, identity_list)
                                     } else {
                                         (identity_list, transform_list)
                                     };
 
                                     match procedure {
                                         Procedure::Add => Err(()),
                                         Procedure::Interpolate { progress } => {
                                             Ok(TransformOperation::InterpolateMatrix {
                                                 from_list,
                                                 to_list,
                                                 progress: Percentage(progress as f32),
                                             })
-                                        },
+                                        }
                                         Procedure::Accumulate { count } => {
                                             Ok(TransformOperation::AccumulateMatrix {
                                                 from_list,
                                                 to_list,
                                                 count: cmp::min(count, i32::max_value() as u64)
                                                     as i32,
                                             })
-                                        },
+                                        }
                                     }
-                                },
+                                }
                                 _ => {
                                     let (lhs, rhs) = if fill_right {
                                         (transform, &identity)
                                     } else {
                                         (&identity, transform)
                                     };
                                     lhs.animate(rhs, procedure)
-                                },
+                                }
                             }
                         })
                         .collect::<Result<Vec<_>, _>>()?,
                 );
-            },
-            (None, None) => {},
+            }
+            (None, None) => {}
         }
 
         Ok(Transform(result.into()))
     }
 }
 
 impl ComputeSquaredDistance for ComputedTransform {
     #[inline]
@@ -994,33 +994,33 @@ impl ComputeSquaredDistance for Computed
 /// <http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms>
 impl Animate for ComputedTransformOperation {
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         match (self, other) {
             (&TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other)) => {
                 Ok(TransformOperation::Matrix3D(
                     this.animate(other, procedure)?,
                 ))
-            },
+            }
             (&TransformOperation::Matrix(ref this), &TransformOperation::Matrix(ref other)) => {
                 Ok(TransformOperation::Matrix(this.animate(other, procedure)?))
-            },
+            }
             (
                 &TransformOperation::Skew(ref fx, ref fy),
                 &TransformOperation::Skew(ref tx, ref ty),
             ) => Ok(TransformOperation::Skew(
                 fx.animate(tx, procedure)?,
                 fy.animate(ty, procedure)?,
             )),
             (&TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t)) => {
                 Ok(TransformOperation::SkewX(f.animate(t, procedure)?))
-            },
+            }
             (&TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t)) => {
                 Ok(TransformOperation::SkewY(f.animate(t, procedure)?))
-            },
+            }
             (
                 &TransformOperation::Translate3D(ref fx, ref fy, ref fz),
                 &TransformOperation::Translate3D(ref tx, ref ty, ref tz),
             ) => Ok(TransformOperation::Translate3D(
                 fx.animate(tx, procedure)?,
                 fy.animate(ty, procedure)?,
                 fz.animate(tz, procedure)?,
             )),
@@ -1028,23 +1028,23 @@ impl Animate for ComputedTransformOperat
                 &TransformOperation::Translate(ref fx, ref fy),
                 &TransformOperation::Translate(ref tx, ref ty),
             ) => Ok(TransformOperation::Translate(
                 fx.animate(tx, procedure)?,
                 fy.animate(ty, procedure)?,
             )),
             (&TransformOperation::TranslateX(ref f), &TransformOperation::TranslateX(ref t)) => {
                 Ok(TransformOperation::TranslateX(f.animate(t, procedure)?))
-            },
+            }
             (&TransformOperation::TranslateY(ref f), &TransformOperation::TranslateY(ref t)) => {
                 Ok(TransformOperation::TranslateY(f.animate(t, procedure)?))
-            },
+            }
             (&TransformOperation::TranslateZ(ref f), &TransformOperation::TranslateZ(ref t)) => {
                 Ok(TransformOperation::TranslateZ(f.animate(t, procedure)?))
-            },
+            }
             (
                 &TransformOperation::Scale3D(ref fx, ref fy, ref fz),
                 &TransformOperation::Scale3D(ref tx, ref ty, ref tz),
             ) => Ok(TransformOperation::Scale3D(
                 animate_multiplicative_factor(*fx, *tx, procedure)?,
                 animate_multiplicative_factor(*fy, *ty, procedure)?,
                 animate_multiplicative_factor(*fz, *tz, procedure)?,
             )),
@@ -1067,35 +1067,35 @@ impl Animate for ComputedTransformOperat
             (
                 &TransformOperation::Rotate3D(fx, fy, fz, fa),
                 &TransformOperation::Rotate3D(tx, ty, tz, ta),
             ) => {
                 let animated = Rotate::Rotate3D(fx, fy, fz, fa)
                     .animate(&Rotate::Rotate3D(tx, ty, tz, ta), procedure)?;
                 let (fx, fy, fz, fa) = ComputedRotate::resolve(&animated);
                 Ok(TransformOperation::Rotate3D(fx, fy, fz, fa))
-            },
+            }
             (&TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta)) => {
                 Ok(TransformOperation::RotateX(fa.animate(&ta, procedure)?))
-            },
+            }
             (&TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta)) => {
                 Ok(TransformOperation::RotateY(fa.animate(&ta, procedure)?))
-            },
+            }
             (&TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta)) => {
                 Ok(TransformOperation::RotateZ(fa.animate(&ta, procedure)?))
-            },
+            }
             (&TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta)) => {
                 Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?))
-            },
+            }
             (&TransformOperation::Rotate(fa), &TransformOperation::RotateZ(ta)) => {
                 Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?))
-            },
+            }
             (&TransformOperation::RotateZ(fa), &TransformOperation::Rotate(ta)) => {
                 Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?))
-            },
+            }
             (
                 &TransformOperation::Perspective(ref fd),
                 &TransformOperation::Perspective(ref td),
             ) => {
                 use crate::values::computed::CSSPixelLength;
                 use crate::values::generics::transform::create_perspective_matrix;
 
                 // From https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions:
@@ -1115,23 +1115,23 @@ impl Animate for ComputedTransformOperat
                 let used_value = if perspective_z == 0. {
                     0.
                 } else {
                     -1. / perspective_z
                 };
                 Ok(TransformOperation::Perspective(CSSPixelLength::new(
                     used_value,
                 )))
-            },
+            }
             _ if self.is_translate() && other.is_translate() => self
                 .to_translate_3d()
                 .animate(&other.to_translate_3d(), procedure),
             _ if self.is_scale() && other.is_scale() => {
                 self.to_scale_3d().animate(&other.to_scale_3d(), procedure)
-            },
+            }
             _ if self.is_rotate() && other.is_rotate() => self
                 .to_rotate_3d()
                 .animate(&other.to_rotate_3d(), procedure),
             _ => Err(()),
         }
     }
 }
 
@@ -1139,80 +1139,80 @@ impl Animate for ComputedTransformOperat
 // to trace the distance travelled by a point as its transform is interpolated between the two
 // lists. That, however, proves to be quite complicated so we take a simple approach for now.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1318591#c0.
 impl ComputeSquaredDistance for ComputedTransformOperation {
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
         match (self, other) {
             (&TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other)) => {
                 this.compute_squared_distance(other)
-            },
+            }
             (&TransformOperation::Matrix(ref this), &TransformOperation::Matrix(ref other)) => {
                 let this: Matrix3D = (*this).into();
                 let other: Matrix3D = (*other).into();
                 this.compute_squared_distance(&other)
-            },
+            }
             (
                 &TransformOperation::Skew(ref fx, ref fy),
                 &TransformOperation::Skew(ref tx, ref ty),
             ) => Ok(fx.compute_squared_distance(&tx)? + fy.compute_squared_distance(&ty)?),
-            (&TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t)) |
-            (&TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t)) => {
+            (&TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t))
+            | (&TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t)) => {
                 f.compute_squared_distance(&t)
-            },
+            }
             (
                 &TransformOperation::Translate3D(ref fx, ref fy, ref fz),
                 &TransformOperation::Translate3D(ref tx, ref ty, ref tz),
             ) => {
                 // For translate, We don't want to require doing layout in order
                 // to calculate the result, so drop the percentage part.
                 //
                 // However, dropping percentage makes us impossible to compute
                 // the distance for the percentage-percentage case, but Gecko
                 // uses the same formula, so it's fine for now.
                 let fx = fx.length_component().px();
                 let fy = fy.length_component().px();
                 let tx = tx.length_component().px();
                 let ty = ty.length_component().px();
 
-                Ok(fx.compute_squared_distance(&tx)? +
-                    fy.compute_squared_distance(&ty)? +
-                    fz.compute_squared_distance(&tz)?)
-            },
+                Ok(fx.compute_squared_distance(&tx)?
+                    + fy.compute_squared_distance(&ty)?
+                    + fz.compute_squared_distance(&tz)?)
+            }
             (
                 &TransformOperation::Scale3D(ref fx, ref fy, ref fz),
                 &TransformOperation::Scale3D(ref tx, ref ty, ref tz),
-            ) => Ok(fx.compute_squared_distance(&tx)? +
-                fy.compute_squared_distance(&ty)? +
-                fz.compute_squared_distance(&tz)?),
+            ) => Ok(fx.compute_squared_distance(&tx)?
+                + fy.compute_squared_distance(&ty)?
+                + fz.compute_squared_distance(&tz)?),
             (
                 &TransformOperation::Rotate3D(fx, fy, fz, fa),
                 &TransformOperation::Rotate3D(tx, ty, tz, ta),
             ) => Rotate::Rotate3D(fx, fy, fz, fa)
                 .compute_squared_distance(&Rotate::Rotate3D(tx, ty, tz, ta)),
-            (&TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta)) |
-            (&TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta)) |
-            (&TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta)) |
-            (&TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta)) => {
+            (&TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta))
+            | (&TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta))
+            | (&TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta))
+            | (&TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta)) => {
                 fa.compute_squared_distance(&ta)
-            },
+            }
             (
                 &TransformOperation::Perspective(ref fd),
                 &TransformOperation::Perspective(ref td),
             ) => fd.compute_squared_distance(td),
-            (&TransformOperation::Perspective(ref p), &TransformOperation::Matrix3D(ref m)) |
-            (&TransformOperation::Matrix3D(ref m), &TransformOperation::Perspective(ref p)) => {
+            (&TransformOperation::Perspective(ref p), &TransformOperation::Matrix3D(ref m))
+            | (&TransformOperation::Matrix3D(ref m), &TransformOperation::Perspective(ref p)) => {
                 // FIXME(emilio): Is this right? Why interpolating this with
                 // Perspective but not with anything else?
                 let mut p_matrix = Matrix3D::identity();
                 if p.px() > 0. {
                     p_matrix.m34 = -1. / p.px();
                 }
                 p_matrix.compute_squared_distance(&m)
-            },
+            }
             // Gecko cross-interpolates amongst all translate and all scale
             // functions (See ToPrimitive in layout/style/StyleAnimationValue.cpp)
             // without falling back to InterpolateMatrix
             _ if self.is_translate() && other.is_translate() => self
                 .to_translate_3d()
                 .compute_squared_distance(&other.to_translate_3d()),
             _ if self.is_scale() && other.is_scale() => self
                 .to_scale_3d()
@@ -1251,26 +1251,26 @@ impl Animate for ComputedRotate {
             (&Rotate::Rotate3D(fx, fy, fz, fa), &Rotate::None) => {
                 // No need to normalize `none`, so animate angle directly.
                 Ok(Rotate::Rotate3D(
                     fx,
                     fy,
                     fz,
                     fa.animate(&Angle::zero(), procedure)?,
                 ))
-            },
+            }
             (&Rotate::None, &Rotate::Rotate3D(tx, ty, tz, ta)) => {
                 // No need to normalize `none`, so animate angle directly.
                 Ok(Rotate::Rotate3D(
                     tx,
                     ty,
                     tz,
                     Angle::zero().animate(&ta, procedure)?,
                 ))
-            },
+            }
             (&Rotate::Rotate3D(_, ..), _) | (_, &Rotate::Rotate3D(_, ..)) => {
                 let (from, to) = (self.resolve(), other.resolve());
                 let (mut fx, mut fy, mut fz, fa) =
                     transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3);
                 let (mut tx, mut ty, mut tz, ta) =
                     transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3);
 
                 if fa == Angle::from_degrees(0.) {
@@ -1296,35 +1296,35 @@ impl Animate for ComputedRotate {
                 let (x, y, z, angle) = transform::get_normalized_vector_and_angle(
                     rq.0 as f32,
                     rq.1 as f32,
                     rq.2 as f32,
                     rq.3.acos() as f32 * 2.0,
                 );
 
                 Ok(Rotate::Rotate3D(x, y, z, Angle::from_radians(angle)))
-            },
+            }
             (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => {
                 // If this is a 2D rotation, we just animate the <angle>
                 let (from, to) = (self.resolve().3, other.resolve().3);
                 Ok(Rotate::Rotate(from.animate(&to, procedure)?))
-            },
+            }
         }
     }
 }
 
 impl ComputeSquaredDistance for ComputedRotate {
     #[inline]
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
         match (self, other) {
             (&Rotate::None, &Rotate::None) => Ok(SquaredDistance::from_sqrt(0.)),
-            (&Rotate::Rotate3D(_, _, _, a), &Rotate::None) |
-            (&Rotate::None, &Rotate::Rotate3D(_, _, _, a)) => {
+            (&Rotate::Rotate3D(_, _, _, a), &Rotate::None)
+            | (&Rotate::None, &Rotate::Rotate3D(_, _, _, a)) => {
                 a.compute_squared_distance(&Angle::zero())
-            },
+            }
             (&Rotate::Rotate3D(_, ..), _) | (_, &Rotate::Rotate3D(_, ..)) => {
                 let (from, to) = (self.resolve(), other.resolve());
                 let (mut fx, mut fy, mut fz, angle1) =
                     transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3);
                 let (mut tx, mut ty, mut tz, angle2) =
                     transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3);
 
                 if angle1 == Angle::zero() {
@@ -1341,17 +1341,17 @@ impl ComputeSquaredDistance for Computed
                     angle1.compute_squared_distance(&angle2)
                 } else {
                     let v1 = DirectionVector::new(fx, fy, fz);
                     let v2 = DirectionVector::new(tx, ty, tz);
                     let q1 = Quaternion::from_direction_and_angle(&v1, angle1.radians64());
                     let q2 = Quaternion::from_direction_and_angle(&v2, angle2.radians64());
                     q1.compute_squared_distance(&q2)
                 }
-            },
+            }
             (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => self
                 .resolve()
                 .3
                 .compute_squared_distance(&other.resolve().3),
         }
     }
 }
 
@@ -1363,109 +1363,88 @@ impl ComputedTranslate {
         //
         // Unspecified translations default to 0px
         match *self {
             Translate::None => (
                 LengthPercentage::zero(),
                 LengthPercentage::zero(),
                 Length::zero(),
             ),
-            Translate::Translate3D(tx, ty, tz) => (tx, ty, tz),
-            Translate::Translate(tx, ty) => (tx, ty, Length::zero()),
+            Translate::Translate(tx, ty, tz) => (tx, ty, tz),
         }
     }
 }
 
 impl Animate for ComputedTranslate {
     #[inline]
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         match (self, other) {
             (&Translate::None, &Translate::None) => Ok(Translate::None),
-            (&Translate::Translate3D(_, ..), _) | (_, &Translate::Translate3D(_, ..)) => {
-                let (from, to) = (self.resolve(), other.resolve());
-                Ok(Translate::Translate3D(
-                    from.0.animate(&to.0, procedure)?,
-                    from.1.animate(&to.1, procedure)?,
-                    from.2.animate(&to.2, procedure)?,
-                ))
-            },
             (&Translate::Translate(_, ..), _) | (_, &Translate::Translate(_, ..)) => {
                 let (from, to) = (self.resolve(), other.resolve());
                 Ok(Translate::Translate(
                     from.0.animate(&to.0, procedure)?,
                     from.1.animate(&to.1, procedure)?,
+                    from.2.animate(&to.2, procedure)?,
                 ))
-            },
+            }
         }
     }
 }
 
 impl ComputeSquaredDistance for ComputedTranslate {
     #[inline]
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
         let (from, to) = (self.resolve(), other.resolve());
-        Ok(from.0.compute_squared_distance(&to.0)? +
-            from.1.compute_squared_distance(&to.1)? +
-            from.2.compute_squared_distance(&to.2)?)
+        Ok(from.0.compute_squared_distance(&to.0)?
+            + from.1.compute_squared_distance(&to.1)?
+            + from.2.compute_squared_distance(&to.2)?)
     }
 }
 
 /// <https://drafts.csswg.org/css-transforms-2/#propdef-scale>
 impl ComputedScale {
     fn resolve(&self) -> (Number, Number, Number) {
         // According to the spec:
         // https://drafts.csswg.org/css-transforms-2/#individual-transforms
         //
         // Unspecified scales default to 1
         match *self {
             Scale::None => (1.0, 1.0, 1.0),
-            Scale::Scale3D(sx, sy, sz) => (sx, sy, sz),
-            Scale::Scale(sx, sy) => (sx, sy, 1.),
+            Scale::Scale(sx, sy, sz) => (sx, sy, sz),
         }
     }
 }
 
 impl Animate for ComputedScale {
     #[inline]
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         match (self, other) {
             (&Scale::None, &Scale::None) => Ok(Scale::None),
-            (&Scale::Scale3D(_, ..), _) | (_, &Scale::Scale3D(_, ..)) => {
+            (&Scale::Scale(_, ..), _) | (_, &Scale::Scale(_, ..)) => {
                 let (from, to) = (self.resolve(), other.resolve());
                 // For transform lists, we add by appending to the list of
                 // transform functions. However, ComputedScale cannot be
                 // simply concatenated, so we have to calculate the additive
                 // result here.
                 if procedure == Procedure::Add {
                     // scale(x1,y1,z1)*scale(x2,y2,z2) = scale(x1*x2, y1*y2, z1*z2)
-                    return Ok(Scale::Scale3D(from.0 * to.0, from.1 * to.1, from.2 * to.2));
+                    return Ok(Scale::Scale(from.0 * to.0, from.1 * to.1, from.2 * to.2));
                 }
-                Ok(Scale::Scale3D(
+                Ok(Scale::Scale(
                     animate_multiplicative_factor(from.0, to.0, procedure)?,
                     animate_multiplicative_factor(from.1, to.1, procedure)?,
                     animate_multiplicative_factor(from.2, to.2, procedure)?,
                 ))
-            },
-            (&Scale::Scale(_, ..), _) | (_, &Scale::Scale(_, ..)) => {
-                let (from, to) = (self.resolve(), other.resolve());
-                // As with Scale3D, addition needs special handling.
-                if procedure == Procedure::Add {
-                    // scale(x1,y1)*scale(x2,y2) = scale(x1*x2, y1*y2)
-                    return Ok(Scale::Scale(from.0 * to.0, from.1 * to.1));
-                }
-                Ok(Scale::Scale(
-                    animate_multiplicative_factor(from.0, to.0, procedure)?,
-                    animate_multiplicative_factor(from.1, to.1, procedure)?,
-                ))
-            },
+            }
         }
     }
 }
 
 impl ComputeSquaredDistance for ComputedScale {
     #[inline]
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
         let (from, to) = (self.resolve(), other.resolve());
-        Ok(from.0.compute_squared_distance(&to.0)? +
-            from.1.compute_squared_distance(&to.1)? +
-            from.2.compute_squared_distance(&to.2)?)
+        Ok(from.0.compute_squared_distance(&to.0)?
+            + from.1.compute_squared_distance(&to.1)?
+            + from.2.compute_squared_distance(&to.2)?)
     }
 }
--- a/servo/components/style/values/generics/transform.rs
+++ b/servo/components/style/values/generics/transform.rs
@@ -298,17 +298,17 @@ where
     }
 
     /// Check if it is any translate function
     pub fn is_translate(&self) -> bool {
         use self::TransformOperation::*;
         match *self {
             Translate(..) | Translate3D(..) | TranslateX(..) | TranslateY(..) | TranslateZ(..) => {
                 true
-            },
+            }
             _ => false,
         }
     }
 
     /// Check if it is any scale function
     pub fn is_scale(&self) -> bool {
         use self::TransformOperation::*;
         match *self {
@@ -410,18 +410,18 @@ where
     Number: PartialEq + Copy + Into<f32> + Into<f64>,
     Length: ToAbsoluteLength,
     LoP: Zero + ToAbsoluteLength,
 {
     #[inline]
     fn is_3d(&self) -> bool {
         use self::TransformOperation::*;
         match *self {
-            Translate3D(..) | TranslateZ(..) | Rotate3D(..) | RotateX(..) | RotateY(..) |
-            RotateZ(..) | Scale3D(..) | ScaleZ(..) | Perspective(..) | Matrix3D(..) => true,
+            Translate3D(..) | TranslateZ(..) | Rotate3D(..) | RotateX(..) | RotateY(..)
+            | RotateZ(..) | Scale3D(..) | ScaleZ(..) | Perspective(..) | Matrix3D(..) => true,
             _ => false,
         }
     }
 
     /// If |reference_box| is None, we will drop the percent part from translate because
     /// we cannot resolve it without the layout info, for computed TransformOperation.
     /// However, for specified TransformOperation, we will return Err(()) if there is any relative
     /// lengths because the only caller, DOMMatrix, doesn't accept relative lengths.
@@ -439,59 +439,59 @@ where
                 let (ax, ay, az, theta) =
                     get_normalized_vector_and_angle(ax.into(), ay.into(), az.into(), theta);
                 Transform3D::create_rotation(
                     ax as f64,
                     ay as f64,
                     az as f64,
                     euclid::Angle::radians(theta),
                 )
-            },
+            }
             RotateX(theta) => {
                 let theta = euclid::Angle::radians(TWO_PI - theta.radians64());
                 Transform3D::create_rotation(1., 0., 0., theta)
-            },
+            }
             RotateY(theta) => {
                 let theta = euclid::Angle::radians(TWO_PI - theta.radians64());
                 Transform3D::create_rotation(0., 1., 0., theta)
-            },
+            }
             RotateZ(theta) | Rotate(theta) => {
                 let theta = euclid::Angle::radians(TWO_PI - theta.radians64());
                 Transform3D::create_rotation(0., 0., 1., theta)
-            },
+            }
             Perspective(ref d) => {
                 let m = create_perspective_matrix(d.to_pixel_length(None)?);
                 m.cast()
-            },
+            }
             Scale3D(sx, sy, sz) => Transform3D::create_scale(sx.into(), sy.into(), sz.into()),
             Scale(sx, sy) => Transform3D::create_scale(sx.into(), sy.into(), 1.),
             ScaleX(s) => Transform3D::create_scale(s.into(), 1., 1.),
             ScaleY(s) => Transform3D::create_scale(1., s.into(), 1.),
             ScaleZ(s) => Transform3D::create_scale(1., 1., s.into()),
             Translate3D(ref tx, ref ty, ref tz) => {
                 let tx = tx.to_pixel_length(reference_width)? as f64;
                 let ty = ty.to_pixel_length(reference_height)? as f64;
                 Transform3D::create_translation(tx, ty, tz.to_pixel_length(None)? as f64)
-            },
+            }
             Translate(ref tx, ref ty) => {
                 let tx = tx.to_pixel_length(reference_width)? as f64;
                 let ty = ty.to_pixel_length(reference_height)? as f64;
                 Transform3D::create_translation(tx, ty, 0.)
-            },
+            }
             TranslateX(ref t) => {
                 let t = t.to_pixel_length(reference_width)? as f64;
                 Transform3D::create_translation(t, 0., 0.)
-            },
+            }
             TranslateY(ref t) => {
                 let t = t.to_pixel_length(reference_height)? as f64;
                 Transform3D::create_translation(0., t, 0.)
-            },
+            }
             TranslateZ(ref z) => {
                 Transform3D::create_translation(0., 0., z.to_pixel_length(None)? as f64)
-            },
+            }
             Skew(theta_x, theta_y) => Transform3D::create_skew(
                 euclid::Angle::radians(theta_x.radians64()),
                 euclid::Angle::radians(theta_y.radians64()),
             ),
             SkewX(theta) => Transform3D::create_skew(
                 euclid::Angle::radians(theta.radians64()),
                 euclid::Angle::radians(0.),
             ),
@@ -504,17 +504,17 @@ where
             InterpolateMatrix { .. } | AccumulateMatrix { .. } => {
                 // TODO: Convert InterpolateMatrix/AccumulateMatrix into a valid Transform3D by
                 // the reference box and do interpolation on these two Transform3D matrices.
                 // Both Gecko and Servo don't support this for computing distance, and Servo
                 // doesn't support animations on InterpolateMatrix/AccumulateMatrix, so
                 // return an identity matrix.
                 // Note: DOMMatrix doesn't go into this arm.
                 Transform3D::identity()
-            },
+            }
         };
         Ok(matrix)
     }
 }
 
 impl<T> Transform<T> {
     /// `none`
     pub fn none() -> Self {
@@ -671,17 +671,17 @@ where
                     x.to_css(dest)?;
                     dest.write_char(' ')?;
                     y.to_css(dest)?;
                     dest.write_char(' ')?;
                     z.to_css(dest)?;
                 }
                 dest.write_char(' ')?;
                 angle.to_css(dest)
-            },
+            }
         }
     }
 }
 
 #[derive(
     Clone,
     Copy,
     Debug,
@@ -695,50 +695,62 @@ where
 )]
 #[repr(C, u8)]
 /// A value of the `Scale` property
 ///
 /// <https://drafts.csswg.org/css-transforms-2/#individual-transforms>
 pub enum GenericScale<Number> {
     /// 'none'
     None,
-    /// '<number>{1,2}'
-    Scale(Number, Number),
-    /// '<number>{3}'
-    Scale3D(Number, Number, Number),
+    /// '<number>{1,3}'
+    Scale(Number, Number, Number),
 }
 
 pub use self::GenericScale as Scale;
 
-impl<Number: ToCss + PartialEq> ToCss for Scale<Number> {
+impl<Number> ToCss for Scale<Number>
+where
+    Number: ToCss + PartialEq + Copy,
+    f32: From<Number>,
+{
     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
     where
         W: fmt::Write,
+        f32: From<Number>,
     {
         match *self {
             Scale::None => dest.write_str("none"),
-            Scale::Scale(ref x, ref y) => {
+            Scale::Scale(ref x, ref y, ref z) => {
                 x.to_css(dest)?;
-                if x != y {
+
+                let is_3d = f32::from(*z) != 1.0;
+                if is_3d || x != y {
                     dest.write_char(' ')?;
                     y.to_css(dest)?;
                 }
+
+                if is_3d {
+                    dest.write_char(' ')?;
+                    z.to_css(dest)?;
+                }
                 Ok(())
-            },
-            Scale::Scale3D(ref x, ref y, ref z) => {
-                x.to_css(dest)?;
-                dest.write_char(' ')?;
-                y.to_css(dest)?;
-                dest.write_char(' ')?;
-                z.to_css(dest)
-            },
+            }
         }
     }
 }
 
+#[inline]
+fn y_axis_and_z_axis_are_zero<LengthPercentage: Zero, Length: Zero>(
+    _: &LengthPercentage,
+    y: &LengthPercentage,
+    z: &Length,
+) -> bool {
+    y.is_zero() && z.is_zero()
+}
+
 #[derive(
     Clone,
     Debug,
     MallocSizeOf,
     PartialEq,
     SpecifiedValueInfo,
     ToAnimatedZero,
     ToComputedValue,
@@ -750,35 +762,34 @@ impl<Number: ToCss + PartialEq> ToCss fo
 /// A value of the `translate` property
 ///
 /// https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization:
 ///
 /// If a 2d translation is specified, the property must serialize with only one
 /// or two values (per usual, if the second value is 0px, the default, it must
 /// be omitted when serializing).
 ///
-/// If a 3d translation is specified, all three values must be serialized.
-///
-/// We don't omit the 3rd component even if it is 0px for now, and the
-/// related spec issue is https://github.com/w3c/csswg-drafts/issues/3305
+/// If a 3d translation is specified and the value can be expressed as 2d, we treat as 2d and
+/// serialize accoringly. Otherwise, we serialize all three values.
+/// https://github.com/w3c/csswg-drafts/issues/3305
 ///
 /// <https://drafts.csswg.org/css-transforms-2/#individual-transforms>
 pub enum GenericTranslate<LengthPercentage, Length>
 where
     LengthPercentage: Zero,
+    Length: Zero,
 {
     /// 'none'
     None,
-    /// '<length-percentage>' or '<length-percentage> <length-percentage>'
+    /// <length-percentage> [ <length-percentage> <length>? ]?
     Translate(
         LengthPercentage,
-        #[css(skip_if = "Zero::is_zero")] LengthPercentage,
+        #[css(contextual_skip_if = "y_axis_and_z_axis_are_zero")] LengthPercentage,
+        #[css(skip_if = "Zero::is_zero")] Length,
     ),
-    /// '<length-percentage> <length-percentage> <length>'
-    Translate3D(LengthPercentage, LengthPercentage, Length),
 }
 
 pub use self::GenericTranslate as Translate;
 
 #[allow(missing_docs)]
 #[derive(
     Clone,
     Copy,
--- a/servo/components/style/values/specified/transform.rs
+++ b/servo/components/style/values/specified/transform.rs
@@ -416,27 +416,32 @@ impl Parse for Translate {
         if input.try(|i| i.expect_ident_matching("none")).is_ok() {
             return Ok(generic::Translate::None);
         }
 
         let tx = specified::LengthPercentage::parse(context, input)?;
         if let Ok(ty) = input.try(|i| specified::LengthPercentage::parse(context, i)) {
             if let Ok(tz) = input.try(|i| specified::Length::parse(context, i)) {
                 // 'translate: <length-percentage> <length-percentage> <length>'
-                return Ok(generic::Translate::Translate3D(tx, ty, tz));
+                return Ok(generic::Translate::Translate(tx, ty, tz));
             }
 
             // translate: <length-percentage> <length-percentage>'
-            return Ok(generic::Translate::Translate(tx, ty));
+            return Ok(generic::Translate::Translate(
+                tx,
+                ty,
+                specified::Length::zero(),
+            ));
         }
 
         // 'translate: <length-percentage> '
         Ok(generic::Translate::Translate(
             tx,
             specified::LengthPercentage::zero(),
+            specified::Length::zero(),
         ))
     }
 }
 
 /// A specified CSS `scale`
 pub type Scale = generic::Scale<Number>;
 
 impl Parse for Scale {
@@ -447,19 +452,19 @@ impl Parse for Scale {
         if input.try(|i| i.expect_ident_matching("none")).is_ok() {
             return Ok(generic::Scale::None);
         }
 
         let sx = Number::parse(context, input)?;
         if let Ok(sy) = input.try(|i| Number::parse(context, i)) {
             if let Ok(sz) = input.try(|i| Number::parse(context, i)) {
                 // 'scale: <number> <number> <number>'
-                return Ok(generic::Scale::Scale3D(sx, sy, sz));
+                return Ok(generic::Scale::Scale(sx, sy, sz));
             }
 
             // 'scale: <number> <number>'
-            return Ok(generic::Scale::Scale(sx, sy));
+            return Ok(generic::Scale::Scale(sx, sy, Number::new(1.0)));
         }
 
         // 'scale: <number>'
-        Ok(generic::Scale::Scale(sx, sx))
+        Ok(generic::Scale::Scale(sx, sx, Number::new(1.0)))
     }
 }
--- a/testing/web-platform/meta/css/css-transforms/animation/scale-interpolation.html.ini
+++ b/testing/web-platform/meta/css/css-transforms/animation/scale-interpolation.html.ini
@@ -1,108 +1,72 @@
 [scale-interpolation.html]
-  [CSS Transitions with transition: all: property <scale> from [26 17 9\] to [2 1\] at (1) should be [2 1\]]
-    expected: FAIL
-
   [CSS Animations: property <scale> from [none\] to [4 3 2\] at (0) should be [none\]]
     expected: FAIL
 
   [Web Animations: property <scale> from [none\] to [4 3 2\] at (0) should be [none\]]
     expected: FAIL
 
-  [CSS Animations: property <scale> from [26 17 9\] to [2 1\] at (1) should be [2 1\]]
-    expected: FAIL
-
-  [CSS Transitions: property <scale> from [26 17 9\] to [2 1\] at (1) should be [2 1\]]
-    expected: FAIL
-
   [CSS Transitions: property <scale> from [none\] to [4 3 2\] at (0) should be [none\]]
     expected: FAIL
 
   [CSS Transitions with transition: all: property <scale> from [none\] to [4 3 2\] at (0) should be [none\]]
     expected: FAIL
 
-  [Web Animations: property <scale> from [26 17 9\] to [2 1\] at (1) should be [2 1\]]
-    expected: FAIL
-
   [CSS Transitions: property <scale> from [inherit\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
   [CSS Transitions: property <scale> from [2 0.5 1\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
   [CSS Transitions with transition: all: property <scale> from [2 0.5 1\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
-  [Web Animations: property <scale> from [1\] to [10 -5 0\] at (0) should be [1\]]
-    expected: FAIL
-
-  [CSS Transitions with transition: all: property <scale> from [-10 5 1\] to [1\] at (1) should be [1\]]
-    expected: FAIL
-
   [Web Animations: property <scale> from [2 0.5 1\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
   [CSS Animations: property <scale> from [2 0.5 1\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
   [CSS Transitions: property <scale> from [unset\] to [1.5 1\] at (0) should be [none\]]
     expected: FAIL
 
-  [CSS Transitions: property <scale> from [-10 5 1\] to [1\] at (1) should be [1\]]
-    expected: FAIL
-
   [CSS Transitions with transition: all: property <scale> from [unset\] to [1.5 1\] at (0) should be [none\]]
     expected: FAIL
 
-  [CSS Animations: property <scale> from [-10 5 1\] to [1\] at (1) should be [1\]]
-    expected: FAIL
-
-  [CSS Transitions: property <scale> from [1\] to [10 -5 0\] at (0) should be [1\]]
-    expected: FAIL
-
   [Web Animations: property <scale> from [initial\] to [inherit\] at (0) should be [none\]]
     expected: FAIL
 
   [CSS Animations: property <scale> from [initial\] to [2 0.5 1\] at (0) should be [none\]]
     expected: FAIL
 
   [CSS Transitions with transition: all: property <scale> from [inherit\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
   [CSS Transitions with transition: all: property <scale> from [initial\] to [2 0.5 1\] at (0) should be [none\]]
     expected: FAIL
 
   [Web Animations: property <scale> from [initial\] to [2 0.5 1\] at (0) should be [none\]]
     expected: FAIL
 
-  [CSS Transitions with transition: all: property <scale> from [1\] to [10 -5 0\] at (0) should be [1\]]
-    expected: FAIL
-
   [Web Animations: property <scale> from [unset\] to [1.5 1\] at (0) should be [none\]]
     expected: FAIL
 
   [Web Animations: property <scale> from [inherit\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
   [CSS Animations: property <scale> from [unset\] to [1.5 1\] at (0) should be [none\]]
     expected: FAIL
 
   [CSS Transitions with transition: all: property <scale> from [initial\] to [inherit\] at (0) should be [none\]]
     expected: FAIL
 
   [CSS Animations: property <scale> from [inherit\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
-  [Web Animations: property <scale> from [-10 5 1\] to [1\] at (1) should be [1\]]
-    expected: FAIL
-
-  [CSS Animations: property <scale> from [1\] to [10 -5 0\] at (0) should be [1\]]
-    expected: FAIL
-
   [CSS Transitions: property <scale> from [initial\] to [inherit\] at (0) should be [none\]]
     expected: FAIL
 
   [CSS Transitions: property <scale> from [initial\] to [2 0.5 1\] at (0) should be [none\]]
     expected: FAIL
 
   [CSS Animations: property <scale> from [initial\] to [inherit\] at (0) should be [none\]]
     expected: FAIL
--- a/testing/web-platform/meta/css/css-transforms/animation/translate-interpolation.html.ini
+++ b/testing/web-platform/meta/css/css-transforms/animation/translate-interpolation.html.ini
@@ -1,63 +1,39 @@
 [translate-interpolation.html]
-  [CSS Transitions: property <translate> from [480px 400px 320px\] to [240% 160%\] at (1) should be [240% 160%\]]
-    expected: FAIL
-
   [CSS Transitions with transition: all: property <translate> from [none\] to [8px 80% 800px\] at (0) should be [none\]]
     expected: FAIL
 
-  [CSS Transitions with transition: all: property <translate> from [480px 400px 320px\] to [240% 160%\] at (1) should be [240% 160%\]]
-    expected: FAIL
-
   [CSS Transitions: property <translate> from [480px 400px 320px\] to [240% 160%\] at (0) should be [480px 400px 320px\]]
     expected: FAIL
 
   [CSS Animations: property <translate> from [none\] to [8px 80% 800px\] at (0) should be [none\]]
     expected: FAIL
 
-  [CSS Animations: property <translate> from [480px 400px 320px\] to [240% 160%\] at (1) should be [240% 160%\]]
-    expected: FAIL
-
   [CSS Transitions with transition: all: property <translate> from [480px 400px 320px\] to [240% 160%\] at (0) should be [480px 400px 320px\]]
     expected: FAIL
 
   [CSS Animations: property <translate> from [480px 400px 320px\] to [240% 160%\] at (0) should be [480px 400px 320px\]]
     expected: FAIL
 
-  [Web Animations: property <translate> from [480px 400px 320px\] to [240% 160%\] at (1) should be [240% 160%\]]
-    expected: FAIL
-
   [Web Animations: property <translate> from [none\] to [8px 80% 800px\] at (0) should be [none\]]
     expected: FAIL
 
   [CSS Transitions: property <translate> from [none\] to [8px 80% 800px\] at (0) should be [none\]]
     expected: FAIL
 
   [Web Animations: property <translate> from [480px 400px 320px\] to [240% 160%\] at (0) should be [480px 400px 320px\]]
     expected: FAIL
 
   [CSS Transitions: property <translate> from [inherit\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
-  [CSS Transitions with transition: all: property <translate> from [-100px -50px 100px\] to [0px\] at (1) should be [0px\]]
-    expected: FAIL
-
-  [CSS Transitions: property <translate> from [0px\] to [-100px -50px 100px\] at (0) should be [0px\]]
-    expected: FAIL
-
-  [Web Animations: property <translate> from [-100px -50px 100px\] to [0px\] at (1) should be [0px\]]
-    expected: FAIL
-
   [CSS Transitions with transition: all: property <translate> from [inherit\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
-  [CSS Animations: property <translate> from [-100px -50px 100px\] to [0px\] at (1) should be [0px\]]
-    expected: FAIL
-
   [Web Animations: property <translate> from [unset\] to [20px\] at (0) should be [none\]]
     expected: FAIL
 
   [CSS Transitions: property <translate> from [200px 100px 400px\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
   [CSS Transitions with transition: all: property <translate> from [initial\] to [200px 100px 200px\] at (0) should be [none\]]
     expected: FAIL
@@ -69,46 +45,34 @@
     expected: FAIL
 
   [CSS Animations: property <translate> from [initial\] to [200px 100px 200px\] at (0) should be [none\]]
     expected: FAIL
 
   [CSS Transitions: property <translate> from [unset\] to [20px\] at (0) should be [none\]]
     expected: FAIL
 
-  [Web Animations: property <translate> from [0px\] to [-100px -50px 100px\] at (0) should be [0px\]]
-    expected: FAIL
-
   [CSS Transitions with transition: all: property <translate> from [initial\] to [inherit\] at (0) should be [none\]]
     expected: FAIL
 
-  [CSS Transitions with transition: all: property <translate> from [0px\] to [-100px -50px 100px\] at (0) should be [0px\]]
-    expected: FAIL
-
   [CSS Animations: property <translate> from [initial\] to [inherit\] at (0) should be [none\]]
     expected: FAIL
 
-  [CSS Transitions: property <translate> from [-100px -50px 100px\] to [0px\] at (1) should be [0px\]]
-    expected: FAIL
-
   [CSS Animations: property <translate> from [200px 100px 400px\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
   [CSS Transitions: property <translate> from [initial\] to [200px 100px 200px\] at (0) should be [none\]]
     expected: FAIL
 
   [CSS Transitions: property <translate> from [initial\] to [inherit\] at (0) should be [none\]]
     expected: FAIL
 
   [Web Animations: property <translate> from [initial\] to [inherit\] at (0) should be [none\]]
     expected: FAIL
 
-  [CSS Animations: property <translate> from [0px\] to [-100px -50px 100px\] at (0) should be [0px\]]
-    expected: FAIL
-
   [CSS Transitions with transition: all: property <translate> from [200px 100px 400px\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
   [CSS Animations: property <translate> from [inherit\] to [initial\] at (1) should be [none\]]
     expected: FAIL
 
   [Web Animations: property <translate> from [inherit\] to [initial\] at (1) should be [none\]]
     expected: FAIL
--- a/testing/web-platform/tests/css/css-transforms/animation/scale-interpolation.html
+++ b/testing/web-platform/tests/css/css-transforms/animation/scale-interpolation.html
@@ -87,22 +87,22 @@
         {at: 2, expect: '19 -11 -1'},
       ]);
 
       test_interpolation({
         property: 'scale',
         from: '-10 5 1',
         to: '1',
       }, [
-        {at: -1, expect: '-21 9 1'},
-        {at: 0, expect: '-10 5 1'},
-        {at: 0.25, expect: '-7.25 4 1'},
-        {at: 0.75, expect: '-1.75 2 1'},
+        {at: -1, expect: '-21 9'},
+        {at: 0, expect: '-10 5'},
+        {at: 0.25, expect: '-7.25 4'},
+        {at: 0.75, expect: '-1.75 2'},
         {at: 1, expect: '1'},
-        {at: 2, expect: '12 -3 1'},
+        {at: 2, expect: '12 -3'},
       ]);
 
       // Handling of the none value.
       test_interpolation({
         property: 'scale',
         from: 'none',
         to: 'none',
       }, [
@@ -143,35 +143,35 @@
       ]);
 
       // Test initial value; for 'scale' this is 'none'.
       test_interpolation({
         property: 'scale',
         from: 'initial',
         to: '2 0.5 1',
       }, [
-        {at: -1, expect: '0 1.5 1'},
+        {at: -1, expect: '0 1.5'},
         {at: 0, expect: 'none'},
-        {at: 0.25, expect: '1.25 0.875 1'},
-        {at: 0.75, expect: '1.75 0.625 1'},
-        {at: 1, expect: '2 0.5 1'},
-        {at: 2, expect: '3 0 1'},
+        {at: 0.25, expect: '1.25 0.875'},
+        {at: 0.75, expect: '1.75 0.625'},
+        {at: 1, expect: '2 0.5'},
+        {at: 2, expect: '3 0'},
       ]);
 
       test_interpolation({
         property: 'scale',
         from: '2 0.5 1',
         to: 'initial',
       }, [
-        {at: -1, expect: '3 0 1'},
-        {at: 0, expect: '2 0.5 1'},
-        {at: 0.25, expect: '1.75 0.6251 1'},
-        {at: 0.75, expect: '1.25 0.875 1'},
+        {at: -1, expect: '3 0'},
+        {at: 0, expect: '2 0.5'},
+        {at: 0.25, expect: '1.75 0.6251'},
+        {at: 0.75, expect: '1.25 0.875'},
         {at: 1, expect: 'none'},
-        {at: 2, expect: '0 1.5 1'},
+        {at: 2, expect: '0 1.5'},
       ]);
 
 
       // Test unset value; for 'scale' this is 'none'.
       test_interpolation({
         property: 'scale',
         from: 'unset',
         to: '1.5 1',
@@ -189,27 +189,27 @@
         property: 'scale',
         from: 'inherit',
         to: '2 0.5 1',
       }, [
         {at: -1, expect: '-1 1.5 3'},
         {at: 0, expect: '0.5 1 2'},
         {at: 0.25, expect: '0.875 0.875 1.75'},
         {at: 0.75, expect: '1.625 0.625 1.25'},
-        {at: 1, expect: '2 0.5 1'},
+        {at: 1, expect: '2 0.5'},
         {at: 2, expect: '3.5 0 0'},
       ]);
 
       test_interpolation({
         property: 'scale',
         from: '2 0.5 1',
         to: 'inherit',
       }, [
         {at: -1, expect: '3.5 0 0'},
-        {at: 0, expect: '2 0.5 1'},
+        {at: 0, expect: '2 0.5'},
         {at: 0.25, expect: '1.625 0.625 1.25'},
         {at: 0.75, expect: '0.875 0.875 1.75'},
         {at: 1, expect: '0.5 1 2'},
         {at: 2, expect: '-1 1.5 3'},
       ]);
 
       // Test combination of initial and inherit.
       test_interpolation({
--- a/testing/web-platform/tests/css/css-transforms/animation/translate-interpolation.html
+++ b/testing/web-platform/tests/css/css-transforms/animation/translate-interpolation.html
@@ -4,24 +4,16 @@
     <meta charset="utf-8">
     <title>translate interpolation</title>
     <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#propdef-translate">
     <meta name="assert" content="translate supports <length> and <percentage> animation.">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <script src="/css/support/interpolation-testcommon.js"></script>
     <style>
-      body {
-        width: 500px;
-        height: 500px;
-      }
-      div {
-        width: 10px;
-        height: 10px;
-      }
       .parent {
         translate: 100px 200px 300px;
       }
       .target {
         width: 100px;
         height: 100px;
         background-color: black;
         translate: 10px;
--- a/testing/web-platform/tests/css/css-transforms/parsing/scale-parsing-valid.html
+++ b/testing/web-platform/tests/css/css-transforms/parsing/scale-parsing-valid.html
@@ -12,16 +12,16 @@
 <body>
 <script>
 test_valid_value("scale", "none");
 
 test_valid_value("scale", "1");
 
 test_valid_value("scale", "100");
 test_valid_value("scale", "100 100", "100");
-test_valid_value("scale", "100 100 1");
+test_valid_value("scale", "100 100 1", "100");
 
 test_valid_value("scale", "100 200");
-test_valid_value("scale", "100 200 1");
+test_valid_value("scale", "100 200 1", "100 200");
 test_valid_value("scale", "100 200 300");
 </script>
 </body>
 </html>
--- a/testing/web-platform/tests/css/css-transforms/parsing/translate-parsing-valid.html
+++ b/testing/web-platform/tests/css/css-transforms/parsing/translate-parsing-valid.html
@@ -18,19 +18,20 @@ test_valid_value("translate", "100%");
 
 test_valid_value("translate", "100px 0px", "100px");
 test_valid_value("translate", "100px 0.1px", "100px 0.1px");
 test_valid_value("translate", "100px 0%", "100px");
 test_valid_value("translate", "100px calc(10px - 10%)", "100px calc(10px - 10%)");
 test_valid_value("translate", "100px 200%");
 test_valid_value("translate", "100% 200px");
 
-test_valid_value("translate", "100px 200px 0px");
+test_valid_value("translate", "100px 200px 0px", "100px 200px");
+test_valid_value("translate", "100px 0px 0px", "100px");
 test_valid_value("translate", "100px 200px 300px");
 test_valid_value("translate", "100% 200% 300px");
 
 test_valid_value("translate", "calc(10% + 10px) calc(20% + 20px) calc(30em + 30px)");
 
 test_valid_value("translate", "0", "0px");
-test_valid_value("translate", "1px 2px 0", "1px 2px 0px");
+test_valid_value("translate", "1px 2px 0", "1px 2px");
 </script>
 </body>
 </html>