Bug 1429298 - Part 6: Apply motion path transform matrix. r=nical
authorBoris Chiou <boris.chiou@gmail.com>
Tue, 07 Aug 2018 18:07:01 -0700
changeset 481063 ad10cbe201c48fc8c622dcfb5915cb048ae871cd
parent 481062 96671e2dceadcdaedea6394516fa3e73f769af3b
child 481064 6d61347cec03f42e72c8f1cdcefcd1d5b14a15e1
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersnical
bugs1429298
milestone63.0a1
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.) 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);