Bug 1429298 - Part 6: Apply motion path transform matrix. r=nical
☠☠ backed out by e03480382807 ☠ ☠
authorBoris Chiou <boris.chiou@gmail.com>
Wed, 22 Aug 2018 01:22:28 +0000
changeset 487844 cc2785ab879e48a01bf87c20aaebedbad0277574
parent 487843 c217209a3b0494d4c4e6718aa6474221681aa660
child 487845 48214a8e1b6bc794ee0ec36d18e7099aa3250fa5
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnical
bugs1429298
milestone63.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 1429298 - Part 6: Apply motion path transform matrix. r=nical We implement the layout part of offset-path. Now we don't have offset-distance, so use the default value, 0%, for it. Note: rename mCombinedTransform as mIndividualTransform, which only stores the combined individual transforms. We apply the individual transforms, motion path transform, and specified transform in ReadTransforms. (We have to follow the order, so we don't combine the specified transform in FinishStyle.) Depends on D2967 Differential Revision: https://phabricator.services.mozilla.com/D2968
dom/svg/moz.build
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/painting/ActiveLayerTracker.cpp
layout/painting/nsDisplayList.cpp
layout/painting/nsDisplayList.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/nsStyleTransformMatrix.cpp
layout/style/nsStyleTransformMatrix.h
--- a/dom/svg/moz.build
+++ b/dom/svg/moz.build
@@ -76,16 +76,17 @@ EXPORTS.mozilla.dom += [
     'SVGImageElement.h',
     'SVGIRect.h',
     'SVGLineElement.h',
     'SVGMarkerElement.h',
     'SVGMaskElement.h',
     'SVGMatrix.h',
     'SVGMetadataElement.h',
     'SVGMPathElement.h',
+    'SVGPathData.h',
     'SVGPathElement.h',
     'SVGPatternElement.h',
     'SVGPolygonElement.h',
     'SVGPolylineElement.h',
     'SVGRect.h',
     'SVGRectElement.h',
     'SVGScriptElement.h',
     'SVGSetElement.h',
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -71,16 +71,17 @@
 #include "mozilla/dom/AnonymousContent.h"
 #include "mozilla/dom/HTMLBodyElement.h"
 #include "mozilla/dom/HTMLMediaElementBinding.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/HTMLImageElement.h"
 #include "mozilla/dom/DOMRect.h"
 #include "mozilla/dom/DOMStringList.h"
 #include "mozilla/dom/KeyframeEffect.h"
+#include "mozilla/dom/SVGPathData.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "imgIRequest.h"
 #include "nsIImageLoadingContent.h"
 #include "nsCOMPtr.h"
 #include "nsCSSProps.h"
 #include "nsListControlFrame.h"
 #include "mozilla/dom/Element.h"
 #include "nsCanvasFrame.h"
@@ -10261,8 +10262,99 @@ nsLayoutUtils::StyleForScrollbar(nsIFram
              "Root element is the only case for this fallback "
              "path to be triggered");
   RefPtr<ComputedStyle> style =
     pc->StyleSet()->ResolveServoStyle(*content->AsElement());
   // Dropping the strong reference is fine because the style should be
   // held strongly by the element.
   return style.get();
 }
+
+static float
+ResolveTransformOrigin(const nsStyleCoord& aCoord,
+                       TransformReferenceBox& aRefBox,
+                       TransformReferenceBox::DimensionGetter aGetter)
+{
+  float result = 0.0;
+  const float scale = mozilla::AppUnitsPerCSSPixel();
+  if (aCoord.GetUnit() == eStyleUnit_Calc) {
+    const nsStyleCoord::Calc *calc = aCoord.GetCalcValue();
+    result = NSAppUnitsToFloatPixels((aRefBox.*aGetter)(), scale) *
+               calc->mPercent +
+               NSAppUnitsToFloatPixels(calc->mLength, scale);
+  } else if (aCoord.GetUnit() == eStyleUnit_Percent) {
+    result = NSAppUnitsToFloatPixels((aRefBox.*aGetter)(), scale) *
+               aCoord.GetPercentValue();
+  } else {
+    MOZ_ASSERT(aCoord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
+    result = NSAppUnitsToFloatPixels(aCoord.GetCoordValue(), scale);
+  }
+  return result;
+}
+
+/* static */ Maybe<MotionPathData>
+nsLayoutUtils::ResolveMotionPath(const nsIFrame* aFrame)
+{
+  MOZ_ASSERT(aFrame);
+
+  const nsStyleDisplay* display = aFrame->StyleDisplay();
+  if (!display->mMotion || !display->mMotion->HasPath()) {
+    return Nothing();
+  }
+
+  const UniquePtr<StyleMotion>& motion = display->mMotion;
+  // Bug 1429299 - Implement offset-distance for motion path. For now, we use
+  // the default value, i.e. 0%.
+  float distance = 0.0;
+  float angle = 0.0;
+  Point point;
+  if (motion->OffsetPath().GetType() == StyleShapeSourceType::Path) {
+    // Build the path and compute the point and angle for creating the
+    // equivalent translate and rotate.
+    // Here we only need to build a valid path for motion path, so
+    // using the default values of stroke-width, stoke-linecap, and fill-rule
+    // is fine for now because what we want is get the point and its normal
+    // vector along the path, instead of rendering it.
+    // FIXME: Bug 1484780, we should cache the path to avoid rebuilding it here
+    // at every restyle. (Caching the path avoids the cost of flattening it
+    // again each time.)
+    RefPtr<DrawTarget> drawTarget =
+      gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+    RefPtr<PathBuilder> builder =
+      drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
+    RefPtr<gfx::Path> gfxPath =
+      SVGPathData::BuildPath(motion->OffsetPath().GetPath()->Path(),
+                             builder,
+                             NS_STYLE_STROKE_LINECAP_BUTT,
+                             0.0);
+    if (!gfxPath) {
+      return Nothing();
+    }
+    float pathLength = gfxPath->ComputeLength();
+    float computedDistance = distance * pathLength;
+    Point tangent;
+    point = gfxPath->ComputePointAtLength(computedDistance, &tangent);
+    // Bug 1429301 - Implement offset-rotate for motion path.
+    // After implement offset-rotate, |angle| will be adjusted more.
+    // For now, the default value of offset-rotate is "auto", so we use the
+    // directional tangent vector.
+    angle = atan2(tangent.y, tangent.x);
+  } else {
+    // Bug 1480665: Implement ray() function.
+    NS_WARNING("Unsupported offset-path value");
+  }
+
+  // Compute the offset for motion path translate.
+  // We need to resolve transform-origin here to calculate the correct path
+  // translate. (i.e. Center transform-origin on the path.)
+  TransformReferenceBox refBox(aFrame);
+  Point origin(
+    ResolveTransformOrigin(display->mTransformOrigin[0],
+                           refBox,
+                           &TransformReferenceBox::Width),
+    ResolveTransformOrigin(display->mTransformOrigin[1],
+                           refBox,
+                           &TransformReferenceBox::Height)
+  );
+  // Bug 1186329: the translate parameters will be adjusted more after we
+  // implement offset-position and offset-anchor.
+  return Some(MotionPathData { point - origin, angle });
+}
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -114,16 +114,21 @@ struct DisplayPortMarginsPropertyData {
                                  uint32_t aPriority)
     : mMargins(aMargins)
     , mPriority(aPriority)
   {}
   ScreenMargin mMargins;
   uint32_t mPriority;
 };
 
+struct MotionPathData {
+  gfx::Point mTranslate;
+  float mRotate;
+};
+
 } // namespace mozilla
 
 // For GetDisplayPort
 enum class RelativeTo {
   ScrollPort,
   ScrollFrame
 };
 
@@ -3108,16 +3113,22 @@ public:
   }
 
   /**
    * Get the computed style from which the scrollbar style should be
    * used for the given scrollbar part frame.
    */
   static ComputedStyle* StyleForScrollbar(nsIFrame* aScrollbarPart);
 
+  /**
+   * Generate the motion path transform result.
+   **/
+  static mozilla::Maybe<mozilla::MotionPathData>
+  ResolveMotionPath(const nsIFrame* aFrame);
+
 private:
   static uint32_t sFontSizeInflationEmPerLine;
   static uint32_t sFontSizeInflationMinTwips;
   static uint32_t sFontSizeInflationLineThreshold;
   static int32_t  sFontSizeInflationMappingIntercept;
   static uint32_t sFontSizeInflationMaxRatio;
   static bool sFontSizeInflationForceEnabled;
   static bool sFontSizeInflationDisabledInMasterProcess;
--- a/layout/painting/ActiveLayerTracker.cpp
+++ b/layout/painting/ActiveLayerTracker.cpp
@@ -244,32 +244,40 @@ ActiveLayerTracker::TransferActivityToFr
   aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
   aFrame->SetProperty(LayerActivityProperty(), layerActivity);
 }
 
 static void
 IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, LayerActivity* aActivity)
 {
   const nsStyleDisplay* display = aFrame->StyleDisplay();
-  RefPtr<nsCSSValueSharedList> transformList = display->GetCombinedTransform();
-  if (!transformList) {
+  if (!display->mSpecifiedTransform &&
+      !display->HasIndividualTransform() &&
+      !(display->mMotion && display->mMotion->HasPath())) {
     // The transform was removed.
     aActivity->mPreviousTransformScale = Nothing();
-    IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
+    IncrementMutationCount(
+      &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
     return;
   }
 
   // Compute the new scale due to the CSS transform property.
   bool dummyBool;
   nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
-  Matrix4x4 transform =
-    nsStyleTransformMatrix::ReadTransforms(transformList->mHead,
-                                           refBox,
-                                           AppUnitsPerCSSPixel(),
-                                           &dummyBool);
+  Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms(
+      display->mIndividualTransform
+        ? display->mIndividualTransform->mHead
+        : nullptr,
+      nsLayoutUtils::ResolveMotionPath(aFrame),
+      display->mSpecifiedTransform
+        ? display->mSpecifiedTransform->mHead
+        : nullptr,
+      refBox,
+      AppUnitsPerCSSPixel(),
+      &dummyBool);
   Matrix transform2D;
   if (!transform.Is2D(&transform2D)) {
     // We don't attempt to handle 3D transforms; just assume the scale changed.
     aActivity->mPreviousTransformScale = Nothing();
     IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
     return;
   }
 
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -7844,21 +7844,24 @@ nsDisplayTransform::ComputePerspectiveMa
 
   aOutMatrix._34 =
     -1.0 / NSAppUnitsToFloatPixels(perspective, aAppUnitsPerPixel);
 
   aOutMatrix.ChangeBasis(Point3D(perspectiveOrigin.x, perspectiveOrigin.y, 0));
   return true;
 }
 
-nsDisplayTransform::FrameTransformProperties::FrameTransformProperties(const nsIFrame* aFrame,
-                                                                       float aAppUnitsPerPixel,
-                                                                       const nsRect* aBoundsOverride)
+nsDisplayTransform::FrameTransformProperties::FrameTransformProperties(
+  const nsIFrame* aFrame,
+  float aAppUnitsPerPixel,
+  const nsRect* aBoundsOverride)
   : mFrame(aFrame)
-  , mTransformList(aFrame->StyleDisplay()->GetCombinedTransform())
+  , mIndividualTransformList(aFrame->StyleDisplay()->mIndividualTransform)
+  , mMotion(nsLayoutUtils::ResolveMotionPath(aFrame))
+  , mTransformList(aFrame->StyleDisplay()->mSpecifiedTransform)
   , mToTransformOrigin(GetDeltaToTransformOrigin(aFrame, aAppUnitsPerPixel, aBoundsOverride))
 {
 }
 
 /* Wraps up the transform matrix in a change-of-basis matrix pair that
  * translates from local coordinate space to transform coordinate space, then
  * hands it back.
  */
@@ -7917,20 +7920,26 @@ nsDisplayTransform::GetResultingTransfor
   // Call IsSVGTransformed() regardless of the value of
   // disp->mSpecifiedTransform, since we still need any
   // parentsChildrenOnlyTransform.
   Matrix svgTransform, parentsChildrenOnlyTransform;
   bool hasSVGTransforms =
     frame && frame->IsSVGTransformed(&svgTransform,
                                      &parentsChildrenOnlyTransform);
   /* Transformed frames always have a transform, or are preserving 3d (and might still have perspective!) */
-  if (aProperties.mTransformList) {
-    result = nsStyleTransformMatrix::ReadTransforms(aProperties.mTransformList->mHead,
-                                                    refBox, aAppUnitsPerPixel,
-                                                    &dummyBool);
+  if (aProperties.HasTransform()) {
+    result = nsStyleTransformMatrix::ReadTransforms(
+        aProperties.mIndividualTransformList
+          ? aProperties.mIndividualTransformList->mHead
+          : nullptr,
+        aProperties.mMotion,
+        aProperties.mTransformList
+          ? aProperties.mTransformList->mHead
+          : nullptr,
+        refBox, aAppUnitsPerPixel, &dummyBool);
   } else if (hasSVGTransforms) {
     // Correct the translation components for zoom:
     float pixelsPerCSSPx = AppUnitsPerCSSPixel() /
                              aAppUnitsPerPixel;
     svgTransform._31 *= pixelsPerCSSPx;
     svgTransform._32 *= pixelsPerCSSPx;
     result = Matrix4x4::From2D(svgTransform);
   }
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -63,16 +63,17 @@ class nsIScrollableFrame;
 class nsSubDocumentFrame;
 class nsDisplayCompositorHitTestInfo;
 class nsDisplayScrollInfoLayer;
 class nsCaret;
 enum class nsDisplayOwnLayerFlags;
 
 namespace mozilla {
 class FrameLayerBuilder;
+struct MotionPathData;
 namespace layers {
 class Layer;
 class ImageLayer;
 class ImageContainer;
 class StackingContextHelper;
 class WebRenderCommand;
 class WebRenderScrollData;
 class WebRenderLayerScrollData;
@@ -6572,25 +6573,37 @@ public:
                                        float aAppUnitsPerPixel,
                                        Matrix4x4& aOutMatrix);
 
   struct FrameTransformProperties
   {
     FrameTransformProperties(const nsIFrame* aFrame,
                              float aAppUnitsPerPixel,
                              const nsRect* aBoundsOverride);
+    // This constructor is used on the compositor (for animations).
+    // Bug 1186329, Bug 1425837, If we want to support compositor animationsf
+    // or individual transforms and motion path, we may need to update this.
+    // For now, let mIndividualTransformList and mMotion as nullptr and
+    // Nothing().
     FrameTransformProperties(RefPtr<const nsCSSValueSharedList>&&
                                aTransformList,
                              const Point3D& aToTransformOrigin)
       : mFrame(nullptr)
       , mTransformList(std::move(aTransformList))
       , mToTransformOrigin(aToTransformOrigin)
     {}
 
+    bool HasTransform() const
+    {
+      return mIndividualTransformList || mTransformList || mMotion.isSome();
+    }
+
     const nsIFrame* mFrame;
+    const RefPtr<const nsCSSValueSharedList> mIndividualTransformList;
+    const mozilla::Maybe<mozilla::MotionPathData> mMotion;
     const RefPtr<const nsCSSValueSharedList> mTransformList;
     const Point3D mToTransformOrigin;
   };
 
   /**
    * Given a frame with the -moz-transform property or an SVG transform,
    * returns the transformation matrix for that frame.
    *
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -3631,20 +3631,20 @@ nsStyleDisplay::nsStyleDisplay(const nsS
   , mScrollSnapCoordinate(aSource.mScrollSnapCoordinate)
   , mBackfaceVisibility(aSource.mBackfaceVisibility)
   , mTransformStyle(aSource.mTransformStyle)
   , mTransformBox(aSource.mTransformBox)
   , mSpecifiedTransform(aSource.mSpecifiedTransform)
   , mSpecifiedRotate(aSource.mSpecifiedRotate)
   , mSpecifiedTranslate(aSource.mSpecifiedTranslate)
   , mSpecifiedScale(aSource.mSpecifiedScale)
+  , mIndividualTransform(aSource.mIndividualTransform)
   , mMotion(aSource.mMotion
             ? MakeUnique<StyleMotion>(*aSource.mMotion)
             : nullptr)
-  , mCombinedTransform(aSource.mCombinedTransform)
   , mTransformOrigin{ aSource.mTransformOrigin[0],
                       aSource.mTransformOrigin[1],
                       aSource.mTransformOrigin[2] }
   , mChildPerspective(aSource.mChildPerspective)
   , mPerspectiveOrigin{ aSource.mPerspectiveOrigin[0],
                         aSource.mPerspectiveOrigin[1] }
   , mVerticalAlign(aSource.mVerticalAlign)
   , mTransitions(aSource.mTransitions)
@@ -3699,19 +3699,18 @@ nsStyleDisplay::~nsStyleDisplay()
   ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedTransform",
                                 mSpecifiedTransform);
   ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedRotate",
                                 mSpecifiedRotate);
   ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedTranslate",
                                 mSpecifiedTranslate);
   ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedScale",
                                 mSpecifiedScale);
-  ReleaseSharedListOnMainThread("nsStyleDisplay::mCombinedTransform",
-                                mCombinedTransform);
-
+  ReleaseSharedListOnMainThread("nsStyleDisplay::mIndividualTransform",
+                                mIndividualTransform);
   MOZ_COUNT_DTOR(nsStyleDisplay);
 }
 
 void
 nsStyleDisplay::FinishStyle(
     nsPresContext* aPresContext, const nsStyleDisplay* aOldStyle)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -3729,17 +3728,17 @@ nsStyleDisplay::FinishStyle(
       const nsStyleImage* oldShapeImage =
         (aOldStyle &&
          aOldStyle->mShapeOutside.GetType() == StyleShapeSourceType::Image)
           ?  &*aOldStyle->mShapeOutside.GetShapeImage() : nullptr;
       shapeImage->ResolveImage(aPresContext, oldShapeImage);
     }
   }
 
-  GenerateCombinedTransform();
+  GenerateCombinedIndividualTransform();
 }
 
 static inline nsChangeHint
 CompareTransformValues(const RefPtr<nsCSSValueSharedList>& aList,
                        const RefPtr<nsCSSValueSharedList>& aNewList)
 {
   nsChangeHint result = nsChangeHint(0);
 
@@ -4026,70 +4025,67 @@ nsStyleDisplay::CalcDifference(const nsS
        mScrollSnapCoordinate != aNewData.mScrollSnapCoordinate)) {
     hint |= nsChangeHint_NeutralChange;
   }
 
   return hint;
 }
 
 void
-nsStyleDisplay::GenerateCombinedTransform()
+nsStyleDisplay::GenerateCombinedIndividualTransform()
 {
   // FIXME(emilio): This should probably be called from somewhere like what we
   // do for image layers, instead of FinishStyle.
   //
   // This does and undoes the work a ton of times in Stylo.
-  mCombinedTransform = nullptr;
+  mIndividualTransform = nullptr;
 
   // Follow the order defined in the spec to append transform functions.
   // https://drafts.csswg.org/css-transforms-2/#ctm
-  AutoTArray<nsCSSValueSharedList*, 4> shareLists;
+  AutoTArray<nsCSSValueSharedList*, 3> shareLists;
   if (mSpecifiedTranslate) {
     shareLists.AppendElement(mSpecifiedTranslate.get());
   }
   if (mSpecifiedRotate) {
     shareLists.AppendElement(mSpecifiedRotate.get());
   }
   if (mSpecifiedScale) {
     shareLists.AppendElement(mSpecifiedScale.get());
   }
-  if (mSpecifiedTransform) {
-    shareLists.AppendElement(mSpecifiedTransform.get());
-  }
 
   if (shareLists.Length() == 0) {
     return;
   }
-
   if (shareLists.Length() == 1) {
-    mCombinedTransform = shareLists[0];
+    mIndividualTransform = shareLists[0];
     return;
   }
 
-  // In common, we may have 3 transform functions(for rotate, translate and
-  // scale) in mSpecifiedTransform, one rotate function in mSpecifiedRotate,
-  // one translate function in mSpecifiedTranslate, and one scale function in
-  // mSpecifiedScale. So 6 slots are enough for the most cases.
-  AutoTArray<nsCSSValueList*, 6> valueLists;
+  // In common, we may have 3 transform functions:
+  // 1. one rotate function in mSpecifiedRotate,
+  // 2. one translate function in mSpecifiedTranslate,
+  // 3. one scale function in mSpecifiedScale.
+  AutoTArray<nsCSSValueList*, 3> valueLists;
   for (auto list: shareLists) {
     if (list) {
       valueLists.AppendElement(list->mHead->Clone());
     }
   }
 
   // Check we have at least one list or else valueLists.Length() - 1 below will
   // underflow.
   MOZ_ASSERT(valueLists.Length());
 
   for (uint32_t i = 0; i < valueLists.Length() - 1; i++) {
     valueLists[i]->mNext = valueLists[i + 1];
   }
 
-  mCombinedTransform = new nsCSSValueSharedList(valueLists[0]);
-}
+  mIndividualTransform = new nsCSSValueSharedList(valueLists[0]);
+}
+
 // --------------------
 // nsStyleVisibility
 //
 
 nsStyleVisibility::nsStyleVisibility(const nsPresContext* aContext)
   : mDirection(aContext->GetBidi() == IBMBIDI_TEXTDIRECTION_RTL
                  ? NS_STYLE_DIRECTION_RTL
                  : NS_STYLE_DIRECTION_LTR)
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2176,24 +2176,21 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   // null, as appropriate.)
   uint8_t mBackfaceVisibility;
   uint8_t mTransformStyle;
   StyleGeometryBox mTransformBox;
   RefPtr<nsCSSValueSharedList> mSpecifiedTransform;
   RefPtr<nsCSSValueSharedList> mSpecifiedRotate;
   RefPtr<nsCSSValueSharedList> mSpecifiedTranslate;
   RefPtr<nsCSSValueSharedList> mSpecifiedScale;
+  // Used to store the final combination of mSpecifiedRotate,
+  // mSpecifiedTranslate, and mSpecifiedScale.
+  RefPtr<nsCSSValueSharedList> mIndividualTransform;
   mozilla::UniquePtr<mozilla::StyleMotion> mMotion;
 
-  // Used to store the final combination of mSpecifiedTranslate,
-  // mSpecifiedRotate, mSpecifiedScale and mSpecifiedTransform.
-  // Use GetCombinedTransform() to get the final transform, instead of
-  // accessing mCombinedTransform directly.
-  RefPtr<nsCSSValueSharedList> mCombinedTransform;
-
   nsStyleCoord mTransformOrigin[3]; // percent, coord, calc, 3rd param is coord, calc only
   nsStyleCoord mChildPerspective; // none, coord
   nsStyleCoord mPerspectiveOrigin[2]; // percent, coord, calc
 
   nsStyleCoord mVerticalAlign;  // coord, percent, calc, enum (NS_STYLE_VERTICAL_ALIGN_*)
 
   nsStyleAutoArray<mozilla::StyleTransition> mTransitions;
 
@@ -2523,31 +2520,27 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
    */
   inline bool IsFixedPosContainingBlockForNonSVGTextFrames(
     mozilla::ComputedStyle&) const;
   inline bool
     IsFixedPosContainingBlockForContainLayoutAndPaintSupportingFrames() const;
   inline bool IsFixedPosContainingBlockForTransformSupportingFrames() const;
 
   /**
-   * Returns the final combined transform.
+   * Returns the final combined individual transform.
    **/
-  already_AddRefed<nsCSSValueSharedList> GetCombinedTransform() const {
-    if (mCombinedTransform) {
-      return do_AddRef(mCombinedTransform);
-    }
-
-    // backward compatible to gecko-backed style system.
-    return mSpecifiedTransform ? do_AddRef(mSpecifiedTransform) : nullptr;
+  already_AddRefed<nsCSSValueSharedList> GetCombinedTransform() const
+  {
+    return mIndividualTransform ? do_AddRef(mIndividualTransform) : nullptr;
   }
 
 private:
   // Helpers for above functions, which do some but not all of the tests
   // for them (since transform must be tested separately for each).
-  void GenerateCombinedTransform();
+  void GenerateCombinedIndividualTransform();
 };
 
 struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleTable
 {
   explicit nsStyleTable(const nsPresContext* aContext);
   nsStyleTable(const nsStyleTable& aOther);
   ~nsStyleTable();
   void FinishStyle(nsPresContext*, const nsStyleTable*) {}
--- a/layout/style/nsStyleTransformMatrix.cpp
+++ b/layout/style/nsStyleTransformMatrix.cpp
@@ -921,40 +921,84 @@ SetIdentityMatrix(nsCSSValue::Array* aMa
 
   MOZ_ASSERT(aMatrix->Count() == 17, "Invalid matrix3d");
   Matrix4x4 m;
   for (size_t i = 0; i < 16; ++i) {
     aMatrix->Item(i + 1).SetFloatValue(m.components[i], eCSSUnit_Number);
   }
 }
 
-Matrix4x4
-ReadTransforms(const nsCSSValueList* aList,
-               TransformReferenceBox& aRefBox,
-               float aAppUnitsPerMatrixUnit,
-               bool* aContains3dTransform)
+static void
+ReadTransformsImpl(Matrix4x4& aMatrix,
+                   const nsCSSValueList* aList,
+                   TransformReferenceBox& aRefBox,
+                   bool* aContains3dTransform)
 {
-  Matrix4x4 result;
-
   for (const nsCSSValueList* curr = aList; curr != nullptr; curr = curr->mNext) {
     const nsCSSValue &currElem = curr->mValue;
     if (currElem.GetUnit() != eCSSUnit_Function) {
       NS_ASSERTION(currElem.GetUnit() == eCSSUnit_None &&
                    !aList->mNext,
                    "stream should either be a list of functions or a "
                    "lone None");
       continue;
     }
     NS_ASSERTION(currElem.GetArrayValue()->Count() >= 1,
                  "Incoming function is too short!");
 
     /* Read in a single transform matrix. */
-    MatrixForTransformFunction(result, currElem.GetArrayValue(), aRefBox,
+    MatrixForTransformFunction(aMatrix, currElem.GetArrayValue(), aRefBox,
                                aContains3dTransform);
   }
+}
+
+Matrix4x4
+ReadTransforms(const nsCSSValueList* aList,
+               TransformReferenceBox& aRefBox,
+               float aAppUnitsPerMatrixUnit,
+               bool* aContains3dTransform)
+{
+  Matrix4x4 result;
+  ReadTransformsImpl(result, aList, aRefBox, aContains3dTransform);
+
+  float scale = float(AppUnitsPerCSSPixel()) / aAppUnitsPerMatrixUnit;
+  result.PreScale(1/scale, 1/scale, 1/scale);
+  result.PostScale(scale, scale, scale);
+
+  return result;
+}
+
+Matrix4x4
+ReadTransforms(const nsCSSValueList* aIndividualTransforms,
+               const Maybe<MotionPathData>& aMotion,
+               const nsCSSValueList* aTransform,
+               TransformReferenceBox& aRefBox,
+               float aAppUnitsPerMatrixUnit,
+               bool* aContains3dTransform)
+{
+  Matrix4x4 result;
+
+  if (aIndividualTransforms) {
+    ReadTransformsImpl(result, aIndividualTransforms, aRefBox,
+                       aContains3dTransform);
+  }
+
+  if (aMotion.isSome()) {
+    // Create the equivalent translate and rotate function, according to the
+    // order in spec. We combine the translate and then the rotate.
+    // https://drafts.fxtf.org/motion-1/#calculating-path-transform
+    result.PreTranslate(aMotion->mTranslate.x, aMotion->mTranslate.y, 0.0);
+    if (aMotion->mRotate != 0.0) {
+      result.RotateZ(aMotion->mRotate);
+    }
+  }
+
+  if (aTransform) {
+    ReadTransformsImpl(result, aTransform, aRefBox, aContains3dTransform);
+  }
 
   float scale = float(AppUnitsPerCSSPixel()) / aAppUnitsPerMatrixUnit;
   result.PreScale(1/scale, 1/scale, 1/scale);
   result.PostScale(scale, scale, scale);
 
   return result;
 }
 
--- a/layout/style/nsStyleTransformMatrix.h
+++ b/layout/style/nsStyleTransformMatrix.h
@@ -19,16 +19,20 @@
 
 #include <limits>
 
 class nsIFrame;
 class nsPresContext;
 struct gfxQuaternion;
 struct nsRect;
 
+namespace mozilla {
+struct MotionPathData;
+}
+
 /**
  * A helper to generate gfxMatrixes from css transform functions.
  */
 namespace nsStyleTransformMatrix {
   // The operator passed to Servo backend.
   enum class MatrixTransformOperator: uint8_t {
     Interpolate,
     Accumulate
@@ -195,16 +199,26 @@ namespace nsStyleTransformMatrix {
    *
    * eCSSUnit_Pixel (as they are in an StyleAnimationValue)
    */
   mozilla::gfx::Matrix4x4 ReadTransforms(const nsCSSValueList* aList,
                                          TransformReferenceBox& aBounds,
                                          float aAppUnitsPerMatrixUnit,
                                          bool* aContains3dTransform);
 
+  // Generate the gfx::Matrix for CSS Transform Module Level 2.
+  // https://drafts.csswg.org/css-transforms-2/#ctm
+  mozilla::gfx::Matrix4x4
+  ReadTransforms(const nsCSSValueList* aIndividualTransforms,
+                 const mozilla::Maybe<mozilla::MotionPathData>& aMotion,
+                 const nsCSSValueList* aTransform,
+                 TransformReferenceBox& aRefBox,
+                 float aAppUnitsPerMatrixUnit,
+                 bool* aContains3dTransform);
+
   /**
    * Given two nsStyleCoord values, compute the 2d position with respect to the
    * given TransformReferenceBox that these values describe, in device pixels.
    */
   mozilla::gfx::Point Convert2DPosition(nsStyleCoord const (&aValue)[2],
                                         TransformReferenceBox& aRefBox,
                                         int32_t aAppUnitsPerDevPixel);