Bug 1408310 - Part 4: Use Servo CSS parser for DOMMatrix on Stylo. draft
authorBoris Chiou <boris.chiou@gmail.com>
Tue, 14 Nov 2017 19:22:05 +0800
changeset 700510 3a4d454e1e8f34d9c1f3d899ef0c82793a102c34
parent 700509 c70fabe7bf4eb88a0067053d8bab58044162d905
child 740908 578395c8dab9ec404743f8e20c00335dc0b12474
push id89871
push userbmo:boris.chiou@gmail.com
push dateMon, 20 Nov 2017 09:30:43 +0000
bugs1408310
milestone59.0a1
Bug 1408310 - Part 4: Use Servo CSS parser for DOMMatrix on Stylo. We convert a _simplified_ specified transform list into a gfx matrix by Servo backend. The _simplified_ means DOMMatrix only accepts a transform list without any relative lengths, percentage, or other keywords; otherwise, we throw a SyntaxError DOMException. MozReview-Commit-ID: K8d30W0i60b
dom/base/DOMMatrix.cpp
layout/style/ServoBindingList.h
servo/components/style/gecko/generated/bindings.rs
servo/components/style/values/computed/angle.rs
servo/components/style/values/computed/length.rs
servo/components/style/values/computed/transform.rs
servo/components/style/values/specified/angle.rs
servo/components/style/values/specified/length.rs
servo/components/style/values/specified/mod.rs
servo/components/style/values/specified/transform.rs
servo/ports/geckolib/glue.rs
testing/web-platform/meta/css/geometry-1/DOMMatrix-001.html.ini
--- a/dom/base/DOMMatrix.cpp
+++ b/dom/base/DOMMatrix.cpp
@@ -7,16 +7,17 @@
 #include "mozilla/dom/DOMMatrix.h"
 
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/DOMMatrixBinding.h"
 #include "mozilla/dom/DOMPoint.h"
 #include "mozilla/dom/DOMPointBinding.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/ToJSValue.h"
+#include "mozilla/ServoBindings.h"
 #include "nsCSSParser.h"
 #include "nsStyleTransformMatrix.h"
 
 #include <math.h>
 
 namespace mozilla {
 namespace dom {
 
@@ -667,48 +668,60 @@ DOMMatrix::InvertSelf()
 DOMMatrix*
 DOMMatrix::SetMatrixValue(const nsAString& aTransformList, ErrorResult& aRv)
 {
   // An empty string is a no-op.
   if (aTransformList.IsEmpty()) {
     return this;
   }
 
-  nsCSSValue value;
-  nsCSSParser parser;
-  bool parseSuccess = parser.ParseTransformProperty(aTransformList,
-                                                    true,
-                                                    value);
-  if (!parseSuccess) {
-    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-    return nullptr;
-  }
+  gfx::Matrix4x4 transform;
+  bool contains3dTransform = false;
+  if (mIsServo) {
+    NS_ConvertUTF16toUTF8 value(aTransformList);
+    bool status = Servo_ParseTransformIntoMatrix(&value,
+                                                 &contains3dTransform,
+                                                 &transform.components);
+    if (!status) {
+      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+      return nullptr;
+    }
+  } else {
+    nsCSSValue value;
+    nsCSSParser parser;
+    bool parseSuccess = parser.ParseTransformProperty(aTransformList,
+                                                      true,
+                                                      value);
+    if (!parseSuccess) {
+      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+      return nullptr;
+    }
 
-  // A value of "none" results in a 2D identity matrix.
-  if (value.GetUnit() == eCSSUnit_None) {
-    mMatrix3D = nullptr;
-    mMatrix2D = new gfx::Matrix();
-    return this;
+    // A value of "none" results in a 2D identity matrix.
+    if (value.GetUnit() == eCSSUnit_None) {
+      mMatrix3D = nullptr;
+      mMatrix2D = new gfx::Matrix();
+      return this;
+    }
+
+    // A value other than a transform-list is a syntax error.
+    if (value.GetUnit() != eCSSUnit_SharedList) {
+      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+      return nullptr;
+    }
+
+    RuleNodeCacheConditions dummy;
+    nsStyleTransformMatrix::TransformReferenceBox dummyBox;
+    transform = nsStyleTransformMatrix::ReadTransforms(
+                    value.GetSharedListValue()->mHead,
+                    nullptr, nullptr, dummy, dummyBox,
+                    nsPresContext::AppUnitsPerCSSPixel(),
+                    &contains3dTransform);
   }
 
-  // A value other than a transform-list is a syntax error.
-  if (value.GetUnit() != eCSSUnit_SharedList) {
-    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-    return nullptr;
-  }
-
-  RuleNodeCacheConditions dummy;
-  nsStyleTransformMatrix::TransformReferenceBox dummyBox;
-  bool contains3dTransform = false;
-  gfx::Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms(
-                               value.GetSharedListValue()->mHead,
-                               nullptr, nullptr, dummy, dummyBox,
-                               nsPresContext::AppUnitsPerCSSPixel(),
-                               &contains3dTransform);
-
   if (!contains3dTransform) {
     mMatrix3D = nullptr;
     mMatrix2D = new gfx::Matrix();
 
     SetA(transform._11);
     SetB(transform._12);
     SetC(transform._21);
     SetD(transform._22);
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -744,15 +744,22 @@ SERVO_BINDING_FUNC(Servo_IsValidCSSColor
 SERVO_BINDING_FUNC(Servo_ComputeColor, bool,
                    RawServoStyleSetBorrowedOrNull set,
                    nscolor current_color,
                    const nsAString* value,
                    nscolor* result_color);
 SERVO_BINDING_FUNC(Servo_ParseIntersectionObserverRootMargin, bool,
                    const nsAString* value,
                    nsCSSRect* result);
+// Returning false means the parsed transform contains relative lengths or
+// percentage value, so we cannot compute the matrix. In this case, we keep
+// |result| and |contains_3d_transform| as-is.
+SERVO_BINDING_FUNC(Servo_ParseTransformIntoMatrix, bool,
+                   const nsACString* value,
+                   bool* contains_3d_transform,
+                   RawGeckoGfxMatrix4x4* result);
 
 // AddRef / Release functions
 #define SERVO_ARC_TYPE(name_, type_)                                \
   SERVO_BINDING_FUNC(Servo_##name_##_AddRef, void, type_##Borrowed) \
   SERVO_BINDING_FUNC(Servo_##name_##_Release, void, type_##Borrowed)
 #include "mozilla/ServoArcTypeList.h"
 #undef SERVO_ARC_TYPE
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -1575,18 +1575,20 @@ extern "C" {
  pub fn Servo_CloneArcStringData ( string : * const ServoRawOffsetArc < RustString > , ) -> ServoRawOffsetArc < RustString > ; 
 } extern "C" {
  pub fn Servo_IsValidCSSColor ( value : * const nsAString , ) -> bool ; 
 } extern "C" {
  pub fn Servo_ComputeColor ( set : RawServoStyleSetBorrowedOrNull , current_color : nscolor , value : * const nsAString , result_color : * mut nscolor , ) -> bool ; 
 } extern "C" {
  pub fn Servo_ParseIntersectionObserverRootMargin ( value : * const nsAString , result : * mut nsCSSRect , ) -> bool ; 
 } extern "C" {
+ pub fn Servo_ParseTransformIntoMatrix ( value : * const nsACString , contains_3d_transform : * mut bool , result : * mut RawGeckoGfxMatrix4x4 , ) -> bool ;
+} extern "C" {
  pub fn Gecko_CreateCSSErrorReporter ( sheet : * mut ServoStyleSheet , loader : * mut Loader , uri : * mut nsIURI , ) -> * mut ErrorReporter ; 
 } extern "C" {
  pub fn Gecko_DestroyCSSErrorReporter ( reporter : * mut ErrorReporter , ) ; 
 } extern "C" {
  pub fn Gecko_ReportUnexpectedCSSError ( reporter : * mut ErrorReporter , message : * const :: std :: os :: raw :: c_char , param : * const :: std :: os :: raw :: c_char , paramLen : u32 , prefix : * const :: std :: os :: raw :: c_char , prefixParam : * const :: std :: os :: raw :: c_char , prefixParamLen : u32 , suffix : * const :: std :: os :: raw :: c_char , source : * const :: std :: os :: raw :: c_char , sourceLen : u32 , lineNumber : u32 , colNumber : u32 , ) ; 
 } extern "C" {
  pub fn Gecko_ContentList_AppendAll ( aContentList : * mut nsSimpleContentList , aElements : * mut * const RawGeckoElement , aLength : usize , ) ; 
 } extern "C" {
  pub fn Gecko_GetElementsWithId ( aDocument : * const nsIDocument , aId : * mut nsAtom , ) -> * const nsTArray < * mut Element > ; 
-}
\ No newline at end of file
+}
--- a/servo/components/style/values/computed/angle.rs
+++ b/servo/components/style/values/computed/angle.rs
@@ -1,17 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Computed angles.
 
 use euclid::Radians;
+use num_traits::Zero;
 use std::{f32, f64};
 use std::f64::consts::PI;
+use std::ops::Add;
 use values::CSSFloat;
 use values::animated::{Animate, Procedure};
 use values::distance::{ComputeSquaredDistance, SquaredDistance};
 
 /// A computed angle.
 #[animate(fallback = "Self::animate_fallback")]
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 #[derive(Animate, Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss)]
@@ -59,28 +61,55 @@ impl Angle {
             Angle::Deg(val) => val as f64 * RAD_PER_DEG,
             Angle::Grad(val) => val as f64 * RAD_PER_GRAD,
             Angle::Turn(val) => val as f64 * RAD_PER_TURN,
             Angle::Rad(val) => val as f64,
         };
         radians.min(f64::MAX).max(f64::MIN)
     }
 
-    /// Returns an angle that represents a rotation of zero radians.
-    pub fn zero() -> Self {
-        Self::from_radians(0.0)
-    }
-
     /// <https://drafts.csswg.org/css-transitions/#animtype-number>
     #[inline]
     fn animate_fallback(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         Ok(Angle::from_radians(self.radians().animate(&other.radians(), procedure)?))
     }
 }
 
+impl Add for Angle {
+    type Output = Self;
+
+    #[inline]
+    fn add(self, rhs: Self) -> Self {
+        match (self, rhs) {
+            (Angle::Deg(x), Angle::Deg(y)) => Angle::Deg(x + y),
+            (Angle::Grad(x), Angle::Grad(y)) => Angle::Grad(x + y),
+            (Angle::Turn(x), Angle::Turn(y)) => Angle::Turn(x + y),
+            (Angle::Rad(x), Angle::Rad(y)) => Angle::Rad(x + y),
+            _ => Angle::from_radians(self.radians() + rhs.radians()),
+        }
+    }
+}
+
+impl Zero for Angle {
+    #[inline]
+    fn zero() -> Self {
+        Angle::from_radians(0.0)
+    }
+
+    #[inline]
+    fn is_zero(&self) -> bool {
+        match *self {
+            Angle::Deg(val) |
+            Angle::Grad(val) |
+            Angle::Turn(val) |
+            Angle::Rad(val) => val == 0.
+        }
+    }
+}
+
 impl ComputeSquaredDistance for Angle {
     #[inline]
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
         // Use the formula for calculating the distance between angles defined in SVG:
         // https://www.w3.org/TR/SVG/animate.html#complexDistances
         self.radians64().compute_squared_distance(&other.radians64())
     }
 }
--- a/servo/components/style/values/computed/length.rs
+++ b/servo/components/style/values/computed/length.rs
@@ -261,16 +261,28 @@ impl specified::CalcLengthOrPercentage {
             percentage: self.percentage,
         }
     }
 
     /// Compute font-size or line-height taking into account text-zoom if necessary.
     pub fn to_computed_value_zoomed(&self, context: &Context, base_size: FontBaseSize) -> CalcLengthOrPercentage {
         self.to_computed_value_with_zoom(context, |abs| context.maybe_zoom_text(abs.into()).0, base_size)
     }
+
+    /// Compute the value into pixel length as CSSFloat without context,
+    /// so it returns Err(()) if there is any value other than pixel length.
+    pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
+        if self.vw.is_some() || self.vh.is_some() || self.vmin.is_some() || self.vmax.is_some() ||
+           self.em.is_some() || self.ex.is_some() || self.ch.is_some() || self.rem.is_some() ||
+           self.percentage.is_some() {
+            return Err(());
+        }
+
+        Ok(self.absolute.map(|len| len.to_px()).unwrap_or(0.))
+    }
 }
 
 impl ToComputedValue for specified::CalcLengthOrPercentage {
     type ComputedValue = CalcLengthOrPercentage;
 
     fn to_computed_value(&self, context: &Context) -> CalcLengthOrPercentage {
         // normal properties don't zoom, and compute em units against the current style's font-size
         self.to_computed_value_with_zoom(context, |abs| abs, FontBaseSize::CurrentStyle)
--- a/servo/components/style/values/computed/transform.rs
+++ b/servo/components/style/values/computed/transform.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Computed types for CSS values that are related to transformations.
 
 use app_units::Au;
-use euclid::{Rect, Transform3D, Vector3D};
-use std::f32;
+use euclid::{Radians, Rect, Transform3D, Vector3D};
+use num_traits::Zero;
 use super::{CSSFloat, Either};
 use values::animated::ToAnimatedZero;
 use values::computed::{Angle, Integer, Length, LengthOrPercentage, Number, Percentage};
 use values::computed::{LengthOrNumber, LengthOrPercentageOrNumber};
 use values::generics::transform::{Matrix as GenericMatrix, Matrix3D as GenericMatrix3D};
 use values::generics::transform::{Transform as GenericTransform, TransformOperation as GenericTransformOperation};
 use values::generics::transform::TimingFunction as GenericTimingFunction;
 use values::generics::transform::TransformOrigin as GenericTransformOrigin;
@@ -331,35 +331,36 @@ impl Transform {
         }
 
         let extract_pixel_length = |lop: &LengthOrPercentage| match *lop {
             LengthOrPercentage::Length(px) => px.px(),
             LengthOrPercentage::Percentage(_) => 0.,
             LengthOrPercentage::Calc(calc) => calc.length().px(),
         };
 
+        const TWO_PI: f32 = 2.0f32 * ::std::f32::consts::PI;
         for operation in list {
             let matrix = match *operation {
                 GenericTransformOperation::Rotate3D(ax, ay, az, theta) => {
-                    let theta = Angle::from_radians(2.0f32 * f32::consts::PI - theta.radians());
+                    let theta = TWO_PI - theta.radians();
                     let (ax, ay, az, theta) = Self::get_normalized_vector_and_angle(ax, ay, az, theta);
-                    Transform3D::create_rotation(ax, ay, az, theta.into())
+                    Transform3D::create_rotation(ax, ay, az, Radians::new(theta))
                 },
                 GenericTransformOperation::RotateX(theta) => {
-                    let theta = Angle::from_radians(2.0f32 * f32::consts::PI - theta.radians());
-                    Transform3D::create_rotation(1., 0., 0., theta.into())
+                    let theta = Radians::new(TWO_PI - theta.radians());
+                    Transform3D::create_rotation(1., 0., 0., theta)
                 },
                 GenericTransformOperation::RotateY(theta) => {
-                    let theta = Angle::from_radians(2.0f32 * f32::consts::PI - theta.radians());
-                    Transform3D::create_rotation(0., 1., 0., theta.into())
+                    let theta = Radians::new(TWO_PI - theta.radians());
+                    Transform3D::create_rotation(0., 1., 0., theta)
                 },
                 GenericTransformOperation::RotateZ(theta) |
                 GenericTransformOperation::Rotate(theta) => {
-                    let theta = Angle::from_radians(2.0f32 * f32::consts::PI - theta.radians());
-                    Transform3D::create_rotation(0., 0., 1., theta.into())
+                    let theta = Radians::new(TWO_PI - theta.radians());
+                    Transform3D::create_rotation(0., 0., 1., theta)
                 },
                 GenericTransformOperation::Perspective(d) => Self::create_perspective_matrix(d.px()),
                 GenericTransformOperation::Scale3D(sx, sy, sz) => Transform3D::create_scale(sx, sy, sz),
                 GenericTransformOperation::Scale(sx, sy) => Transform3D::create_scale(sx, sy.unwrap_or(sx), 1.),
                 GenericTransformOperation::ScaleX(s) => Transform3D::create_scale(s, 1., 1.),
                 GenericTransformOperation::ScaleY(s) => Transform3D::create_scale(1., s, 1.),
                 GenericTransformOperation::ScaleZ(s) => Transform3D::create_scale(1., 1., s),
                 GenericTransformOperation::Translate3D(tx, ty, tz) => {
@@ -453,39 +454,43 @@ impl Transform {
             transform = transform.pre_mul(&matrix);
         }
 
         Some(transform)
     }
 
     /// Return the transform matrix from a perspective length.
     #[inline]
-    pub fn create_perspective_matrix(d: CSSFloat) -> Transform3D<f32> {
+    pub fn create_perspective_matrix(d: CSSFloat) -> Transform3D<CSSFloat> {
         // TODO(gw): The transforms spec says that perspective length must
         // be positive. However, there is some confusion between the spec
         // and browser implementations as to handling the case of 0 for the
         // perspective value. Until the spec bug is resolved, at least ensure
         // that a provided perspective value of <= 0.0 doesn't cause panics
         // and behaves as it does in other browsers.
         // See https://lists.w3.org/Archives/Public/www-style/2016Jan/0020.html for more details.
         if d <= 0.0 {
             Transform3D::identity()
         } else {
             Transform3D::create_perspective(d)
         }
     }
 
     /// Return the normalized direction vector and its angle for Rotate3D.
-    pub fn get_normalized_vector_and_angle(x: f32, y: f32, z: f32, angle: Angle) -> (f32, f32, f32, Angle) {
+    pub fn get_normalized_vector_and_angle<T: Zero>(
+        x: CSSFloat,
+        y: CSSFloat,
+        z: CSSFloat,
+        angle: T
+    ) -> (CSSFloat, CSSFloat, CSSFloat, T) {
         use euclid::approxeq::ApproxEq;
-        use euclid::num::Zero;
         let vector = DirectionVector::new(x, y, z);
         if vector.square_length().approx_eq(&f32::zero()) {
             // https://www.w3.org/TR/css-transforms-1/#funcdef-rotate3d
             // A direction vector that cannot be normalized, such as [0, 0, 0], will cause the
             // rotation to not be applied, so we use identity matrix (i.e. rotate3d(0, 0, 1, 0)).
-            (0., 0., 1., Angle::zero())
+            (0., 0., 1., T::zero())
         } else {
             let vector = vector.normalize();
             (vector.x, vector.y, vector.z, angle)
         }
     }
 }
--- a/servo/components/style/values/specified/angle.rs
+++ b/servo/components/style/values/specified/angle.rs
@@ -76,16 +76,22 @@ impl Angle {
     }
 
     /// Returns the amount of radians this angle represents.
     #[inline]
     pub fn radians(self) -> f32 {
         self.value.radians()
     }
 
+    /// Returns the amount of radians this angle represents as f64.
+    #[inline]
+    pub fn radians64(self) -> f64 {
+        self.value.radians64()
+    }
+
     /// Returns `0deg`.
     pub fn zero() -> Self {
         Self::from_degrees(0.0, false)
     }
 
     /// Returns an `Angle` parsed from a `calc()` expression.
     pub fn from_calc(radians: CSSFloat) -> Self {
         Angle {
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -458,16 +458,25 @@ impl NoCalcLength {
     /// Checks whether the length value is zero.
     pub fn is_zero(&self) -> bool {
         match *self {
             NoCalcLength::Absolute(length) => length.is_zero(),
             _ => false
         }
     }
 
+    /// Get a px value without context.
+    #[inline]
+    pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
+        match *self {
+            NoCalcLength::Absolute(len) => Ok(len.to_px()),
+            _ => Err(()),
+        }
+    }
+
     /// Get an absolute length from a px value.
     #[inline]
     pub fn from_px(px_value: CSSFloat) -> NoCalcLength {
         NoCalcLength::Absolute(AbsoluteLength::Px(px_value))
     }
 }
 
 /// An extension to `NoCalcLength` to parse `calc` expressions.
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -210,16 +210,21 @@ impl Number {
         }
     }
 
     /// Returns the numeric value, clamped if needed.
     pub fn get(&self) -> f32 {
         self.calc_clamping_mode.map_or(self.value, |mode| mode.clamp(self.value))
     }
 
+    /// Returns the numeric value as f64, clamped if needed.
+    pub fn get64(&self) -> f64 {
+        self.get() as f64
+    }
+
     #[allow(missing_docs)]
     pub fn parse_non_negative<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                                       -> Result<Number, ParseError<'i>> {
         parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
     }
 
     #[allow(missing_docs)]
     pub fn parse_at_least_one<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
--- a/servo/components/style/values/specified/transform.rs
+++ b/servo/components/style/values/specified/transform.rs
@@ -1,23 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Specified types for CSS values that are related to transformations.
 
 use cssparser::Parser;
+use euclid::{Radians, Transform3D};
 use parser::{Parse, ParserContext};
 use selectors::parser::SelectorParseErrorKind;
 use style_traits::{ParseError, StyleParseErrorKind};
+use super::CSSFloat;
 use values::computed::{Context, LengthOrPercentage as ComputedLengthOrPercentage};
 use values::computed::{Percentage as ComputedPercentage, ToComputedValue};
 use values::computed::transform::TimingFunction as ComputedTimingFunction;
-use values::generics::transform::{Matrix3D, Transform as GenericTransform};
-use values::generics::transform::{StepPosition, TimingFunction as GenericTimingFunction, Matrix};
+use values::computed::transform::Transform as ComputedTransform;
+use values::generics::transform::{Matrix as GenericMatrix, Matrix3D as GenericMatrix3D};
+use values::generics::transform::{StepPosition, TimingFunction as GenericTimingFunction};
+use values::generics::transform::{Transform as GenericTransform};
 use values::generics::transform::{TimingKeyword, TransformOrigin as GenericTransformOrigin};
 use values::generics::transform::TransformOperation as GenericTransformOperation;
 use values::specified::{self, Angle, Number, Length, Integer};
 use values::specified::{LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrNumber};
 use values::specified::position::{Side, X, Y};
 
 /// A single operation in a specified CSS `transform`
 pub type TransformOperation = GenericTransformOperation<
@@ -30,16 +34,44 @@ pub type TransformOperation = GenericTra
     LengthOrPercentageOrNumber,
 >;
 /// A specified CSS `transform`
 pub type Transform = GenericTransform<TransformOperation>;
 
 /// The specified value of a CSS `<transform-origin>`
 pub type TransformOrigin = GenericTransformOrigin<OriginComponent<X>, OriginComponent<Y>, Length>;
 
+/// specified value of matrix()
+type Matrix = GenericMatrix<Number>;
+/// specified value of matrix3d()
+type Matrix3D = GenericMatrix3D<Number>;
+
+#[cfg_attr(rustfmt, rustfmt_skip)]
+impl From<Matrix> for Transform3D<f64> {
+    #[inline]
+    fn from(m: Matrix) -> Self {
+        Transform3D::row_major(
+            m.a.get64(), m.b.get64(), 0.0, 0.0,
+            m.c.get64(), m.d.get64(), 0.0, 0.0,
+            0.0,         0.0,         1.0, 0.0,
+            m.e.get64(), m.f.get64(), 0.0, 1.0)
+    }
+}
+
+#[cfg_attr(rustfmt, rustfmt_skip)]
+impl From<Matrix3D> for Transform3D<f64> {
+    #[inline]
+    fn from(m: Matrix3D) -> Self {
+        Transform3D::row_major(
+            m.m11.get64(), m.m12.get64(), m.m13.get64(), m.m14.get64(),
+            m.m21.get64(), m.m22.get64(), m.m23.get64(), m.m24.get64(),
+            m.m31.get64(), m.m32.get64(), m.m33.get64(), m.m34.get64(),
+            m.m41.get64(), m.m42.get64(), m.m43.get64(), m.m44.get64())
+    }
+}
 
 impl Transform {
     /// Internal parse function for deciding if we wish to accept prefixed values or not
     // Allow unitless zero angle for rotate() and skew() to align with gecko
     fn parse_internal<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
         prefixed: bool,
@@ -68,23 +100,23 @@ impl Transform {
                         input.expect_comma()?;
                         let d = specified::parse_number(context, input)?;
                         input.expect_comma()?;
                         if !prefixed {
                             // Standard matrix parsing.
                             let e = specified::parse_number(context, input)?;
                             input.expect_comma()?;
                             let f = specified::parse_number(context, input)?;
-                            Ok(GenericTransformOperation::Matrix(Matrix { a, b, c, d, e, f }))
+                            Ok(GenericTransformOperation::Matrix(GenericMatrix { a, b, c, d, e, f }))
                         } else {
                             // Non-standard prefixed matrix parsing for -moz-transform.
                             let e = LengthOrPercentageOrNumber::parse(context, input)?;
                             input.expect_comma()?;
                             let f = LengthOrPercentageOrNumber::parse(context, input)?;
-                            Ok(GenericTransformOperation::PrefixedMatrix(Matrix { a, b, c, d, e, f }))
+                            Ok(GenericTransformOperation::PrefixedMatrix(GenericMatrix { a, b, c, d, e, f }))
                         }
                     },
                     "matrix3d" => {
                         let m11 = specified::parse_number(context, input)?;
                         input.expect_comma()?;
                         let m12 = specified::parse_number(context, input)?;
                         input.expect_comma()?;
                         let m13 = specified::parse_number(context, input)?;
@@ -126,17 +158,17 @@ impl Transform {
                             // Non-standard prefixed matrix parsing for -moz-transform.
                             let m41 = LengthOrPercentageOrNumber::parse(context, input)?;
                             input.expect_comma()?;
                             let m42 = LengthOrPercentageOrNumber::parse(context, input)?;
                             input.expect_comma()?;
                             let m43 = LengthOrNumber::parse(context, input)?;
                             input.expect_comma()?;
                             let m44 = specified::parse_number(context, input)?;
-                            Ok(GenericTransformOperation::PrefixedMatrix3D(Matrix3D {
+                            Ok(GenericTransformOperation::PrefixedMatrix3D(GenericMatrix3D {
                                 m11, m12, m13, m14,
                                 m21, m22, m23, m24,
                                 m31, m32, m33, m34,
                                 m41, m42, m43, m44,
                             }))
                         }
                     },
                     "translate" => {
@@ -248,16 +280,148 @@ impl Transform {
                     _ => Err(()),
                 };
                 result
                     .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone())))
             })
         })?))
     }
 
+    /// Return the equivalent 3d matrix of this specified transform list.
+    /// This is used by DOMMatrix, which doesn't have any layout info, and
+    /// we only accept pixel length in this case. If there is any relative length
+    /// or percentage, it returns Err(()).
+    /// Normally, we return a pair: the first one is the transform matrix, and the second one
+    /// indicates if there is any 3d transform function in this transform list.
+    pub fn to_transform_3d_matrix(&self) -> Result<(Transform3D<CSSFloat>, bool), ()> {
+        let list = &self.0;
+        if list.len() == 0 {
+            return Ok((Transform3D::identity(), false));
+        }
+
+        let extract_lop = |lop: &LengthOrPercentage| match *lop {
+            LengthOrPercentage::Length(len) => len.to_computed_pixel_length_without_context(),
+            LengthOrPercentage::Calc(ref calc) => calc.to_computed_pixel_length_without_context(),
+            _ => Err(()),
+        };
+
+        let extract_length = |len: &Length| match *len {
+            Length::NoCalc(len) => len.to_computed_pixel_length_without_context(),
+            Length::Calc(ref calc) => calc.to_computed_pixel_length_without_context(),
+        };
+
+        // We intentionally use Transform3D<f64> during computation to avoid error propagation
+        // because using f32 to compute triangle functions (e.g. in create_rotation()) is not
+        // accurate enough. In Gecko, we also use "double" to compute the triangle functions.
+        // Therefore, let's use Transform3D<f64> during matrix computation and cast it into f32
+        // in the end.
+        let mut transform = Transform3D::<f64>::identity();
+        let mut contain_3d = false;
+
+        const TWO_PI: f64 = 2.0f64 * ::std::f64::consts::PI;
+        for operation in list {
+            let matrix = match *operation {
+                GenericTransformOperation::Rotate3D(ax, ay, az, theta) => {
+                    contain_3d = true;
+                    let theta = TWO_PI - theta.radians64();
+                    let (ax, ay, az, theta) =
+                        ComputedTransform::get_normalized_vector_and_angle(ax.get(),
+                                                                           ay.get(),
+                                                                           az.get(),
+                                                                           theta);
+                    Transform3D::create_rotation(ax as f64, ay as f64, az as f64,
+                                                 Radians::new(theta))
+                },
+                GenericTransformOperation::RotateX(theta) => {
+                    contain_3d = true;
+                    let theta = Radians::new(TWO_PI - theta.radians64());
+                    Transform3D::create_rotation(1., 0., 0., theta)
+                },
+                GenericTransformOperation::RotateY(theta) => {
+                    contain_3d = true;
+                    let theta = Radians::new(TWO_PI - theta.radians64());
+                    Transform3D::create_rotation(0., 1., 0., theta)
+                },
+                GenericTransformOperation::RotateZ(theta) |
+                GenericTransformOperation::Rotate(theta) => {
+                    if let GenericTransformOperation::RotateZ(_) = *operation {
+                        contain_3d = true;
+                    }
+                    let theta = Radians::new(TWO_PI - theta.radians64());
+                    Transform3D::create_rotation(0., 0., 1., theta)
+                },
+                GenericTransformOperation::Perspective(ref d) => {
+                    contain_3d = true;
+                    let m = ComputedTransform::create_perspective_matrix(extract_length(d)?);
+                    m.cast().expect("Casting from f32 to f64 should be successful")
+                },
+                GenericTransformOperation::Scale3D(sx, sy, sz) => {
+                    contain_3d = true;
+                    Transform3D::create_scale(sx.get64(), sy.get64(), sz.get64())
+                },
+                GenericTransformOperation::Scale(sx, sy) => {
+                    Transform3D::create_scale(sx.get64(), sy.unwrap_or(sx).get64(), 1.)
+                },
+                GenericTransformOperation::ScaleX(s) => {
+                    Transform3D::create_scale(s.get64(), 1., 1.)
+                },
+                GenericTransformOperation::ScaleY(s) => {
+                    Transform3D::create_scale(1., s.get64(), 1.)
+                },
+                GenericTransformOperation::ScaleZ(s) => {
+                    contain_3d = true;
+                    Transform3D::create_scale(1., 1., s.get64())
+                },
+                GenericTransformOperation::Translate3D(ref tx, ref ty, ref tz) => {
+                    contain_3d = true;
+                    Transform3D::create_translation(extract_lop(tx)? as f64,
+                                                    extract_lop(ty)? as f64,
+                                                    extract_length(tz)? as f64)
+                },
+                GenericTransformOperation::Translate(ref tx, Some(ref ty)) => {
+                    Transform3D::create_translation(extract_lop(tx)? as f64,
+                                                    extract_lop(ty)? as f64,
+                                                    0.)
+                },
+                GenericTransformOperation::TranslateX(ref t) |
+                GenericTransformOperation::Translate(ref t, None) => {
+                    Transform3D::create_translation(extract_lop(t)? as f64, 0., 0.)
+                },
+                GenericTransformOperation::TranslateY(ref t) => {
+                    Transform3D::create_translation(0., extract_lop(t)? as f64, 0.)
+                },
+                GenericTransformOperation::TranslateZ(ref z) => {
+                    contain_3d = true;
+                    Transform3D::create_translation(0., 0., extract_length(z)? as f64)
+                },
+                GenericTransformOperation::Skew(theta_x, theta_y) => {
+                    let theta_y = theta_y.unwrap_or(Angle::zero());
+                    Transform3D::create_skew(Radians::new(theta_x.radians64()),
+                                             Radians::new(theta_y.radians64()))
+                },
+                GenericTransformOperation::SkewX(theta) => {
+                    Transform3D::create_skew(Radians::new(theta.radians64()), Radians::new(0.))
+                },
+                GenericTransformOperation::SkewY(theta) => {
+                    Transform3D::create_skew(Radians::new(0.), Radians::new(theta.radians64()))
+                },
+                GenericTransformOperation::Matrix3D(m) => {
+                    contain_3d = true;
+                    m.into()
+                },
+                GenericTransformOperation::Matrix(m) => m.into(),
+                _ => unreachable!(),
+            };
+
+            transform = transform.pre_mul(&matrix);
+        }
+
+        Ok((transform.cast().expect("Casting from f64 to f32 should be successful"), contain_3d))
+    }
+
     /// Parses `-moz-transform` property. This prefixed property also accepts LengthOrPercentage
     /// in the nondiagonal homogeneous components of matrix and matrix3d.
     #[inline]
     pub fn parse_prefixed<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<Self, ParseError<'i>> {
         Transform::parse_internal(context, input, true)
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -4628,16 +4628,48 @@ pub extern "C" fn Servo_ParseIntersectio
             result.mLeft.set_from(rect.3);
             true
         }
         Err(..) => false,
     }
 }
 
 #[no_mangle]
+pub extern "C" fn Servo_ParseTransformIntoMatrix(
+    value: *const nsACString,
+    contain_3d: *mut bool,
+    result: *mut RawGeckoGfxMatrix4x4
+) -> bool {
+    let result = unsafe { result.as_mut() }.expect("not a valid matrix");
+    let contain_3d = unsafe { contain_3d.as_mut() }.expect("not a valid bool");
+
+    let id = PropertyId::Longhand(LonghandId::Transform);
+    let mut declarations = SourcePropertyDeclaration::new();
+    if let Ok(_) = parse_property_into(&mut declarations, id, value,
+                                       unsafe { DUMMY_URL_DATA },
+                                       structs::ParsingMode_Default,
+                                       QuirksMode::NoQuirks,
+                                       &NullReporter) {
+        let mut block = PropertyDeclarationBlock::new();
+        block.extend(declarations.drain(), Importance::Normal, DeclarationSource::CssOm);
+
+        if let Some(decl) = block.get(PropertyDeclarationId::Longhand(LonghandId::Transform)) {
+            if let &PropertyDeclaration::Transform(ref value) = decl.0 {
+                if let Ok((m, is_3d)) = value.to_transform_3d_matrix() {
+                    *result = m.to_row_major_array();
+                    *contain_3d = is_3d;
+                    return true;
+                }
+            }
+        }
+    }
+    false
+}
+
+#[no_mangle]
 pub unsafe extern "C" fn Servo_SourceSizeList_Parse(
     value: *const nsACString,
 ) -> *mut RawServoSourceSizeList {
     let value = (*value).as_str_unchecked();
     let mut input = ParserInput::new(value);
     let mut parser = Parser::new(&mut input);
 
     let context = ParserContext::new(
@@ -4673,8 +4705,9 @@ pub unsafe extern "C" fn Servo_SourceSiz
 
     result.0
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn Servo_SourceSizeList_Drop(list: RawServoSourceSizeListOwned) {
     let _ = list.into_box::<SourceSizeList>();
 }
+
--- a/testing/web-platform/meta/css/geometry-1/DOMMatrix-001.html.ini
+++ b/testing/web-platform/meta/css/geometry-1/DOMMatrix-001.html.ini
@@ -86,17 +86,18 @@
 
   [new DOMMatrix(sequence) 6 elements]
     expected: FAIL
 
   [new DOMMatrix("scale(2) translateX(5px) translateY(5px)")]
     expected: FAIL
 
   [new DOMMatrix(" ")]
-    expected: FAIL
+    expected:
+      if not stylo: FAIL
 
   [new DOMMatrix(sequence)]
     expected: FAIL
 
   [new DOMMatrix(matrix)]
     expected: FAIL
 
   [new DOMMatrix(sequence) 17 elements]
@@ -251,20 +252,22 @@
 
   [new DOMMatrix("scale(2) translateX(calc(2 * 2.5px)) translateY(5px)")]
     expected: FAIL
 
   [new DOMMatrix("scale(2) translateX(5px) translateY(5px) rotate(5deg) rotate(-5deg)")]
     expected: FAIL
 
   [new DOMMatrix("rotate(5)")]
-    expected: FAIL
+    expected:
+      if not stylo: FAIL
 
   [new DOMMatrix("rotate(5, 5, 5)")]
-    expected: FAIL
+    expected:
+      if not stylo: FAIL
 
   [new DOMMatrixReadOnly("scale(2, 2) translateX(5px) translateY(5px)")]
     expected: FAIL
 
   [new DOMMatrixReadOnly("scale(2)translateX(5px)translateY(5px)")]
     expected: FAIL
 
   [new DOMMatrixReadOnly("scale(2) translateX(calc(2 * 2.5px)) translateY(5px)")]