Bug 1429298 - Part 2: Define offset-path and implement it in style system. r=emilio
☠☠ backed out by e03480382807 ☠ ☠
authorBoris Chiou <boris.chiou@gmail.com>
Wed, 22 Aug 2018 01:24:13 +0000
changeset 487840 0b9ec0d707b5d23a37986a9818230dec1932a2f3
parent 487839 38ad1cc1b0c8804267e83d94940f17252fd26ce8
child 487841 761e9bb54adbc9f8164a5d83c8d9483b93e8f1fb
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)
reviewersemilio
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 2: Define offset-path and implement it in style system. r=emilio Define OffsetPath & SVGPathData on the servo-side, and StyleMotion & StyleSVGPath on the gecko-side. We parse the SVG Path string into a vector of PathCommand. To build the gfx::Path, we will convert it into gfx::Path later in a different patch. The basic flow is: - Parse SVG Path String into SVGPathData (in Rust). - Use cbindgen to make sure the layout of PathCommand and StylePathCommand, and then set the Box[PathCommand] into nsTArray<StylePathCommand>. - Try to convert nsTArray<StylePathCommand> into gfx::Path. (This part will be implemented in a different patch.) Finally, we use the gfx::Path to create a motion path transform. The layout implementation is in the later patch. Depends on D2962 Differential Revision: https://phabricator.services.mozilla.com/D2963
devtools/server/actors/animation-type-longhand.js
devtools/shared/css/generated/properties-db.js
layout/generic/nsFloatManager.cpp
layout/style/ServoBindings.cpp
layout/style/ServoBindings.h
layout/style/ServoBindings.toml
layout/style/ServoCSSPropList.mako.py
layout/style/nsComputedDOMStyle.cpp
layout/style/nsStyleConsts.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/property_database.js
servo/components/style/cbindgen.toml
servo/components/style/gecko/conversions.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhands/box.mako.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/computed/motion.rs
servo/components/style/values/specified/mod.rs
servo/components/style/values/specified/motion.rs
--- a/devtools/server/actors/animation-type-longhand.js
+++ b/devtools/server/actors/animation-type-longhand.js
@@ -205,16 +205,17 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
     "margin-inline-end",
     "margin-inline-start",
     "-moz-math-display",
     "max-block-size",
     "max-inline-size",
     "min-block-size",
     "-moz-min-font-size-ratio",
     "min-inline-size",
+    "offset-path",
     "padding-block-end",
     "padding-block-start",
     "padding-inline-end",
     "padding-inline-start",
     "rotate",
     "scale",
     "-moz-script-level",
     "-moz-top-layer",
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -2944,16 +2944,17 @@ exports.CSS_PROPERTIES = {
       "scroll-snap-points-x",
       "scroll-snap-points-y",
       "scroll-snap-destination",
       "scroll-snap-coordinate",
       "transform",
       "rotate",
       "scale",
       "translate",
+      "offset-path",
       "scroll-behavior",
       "scroll-snap-type-x",
       "scroll-snap-type-y",
       "overscroll-behavior-x",
       "overscroll-behavior-y",
       "isolation",
       "page-break-after",
       "page-break-before",
@@ -7410,16 +7411,30 @@ exports.CSS_PROPERTIES = {
       "inherit",
       "initial",
       "left",
       "right",
       "top",
       "unset"
     ]
   },
+  "offset-path": {
+    "isInherited": false,
+    "subproperties": [
+      "offset-path"
+    ],
+    "supports": [],
+    "values": [
+      "inherit",
+      "initial",
+      "none",
+      "path",
+      "unset"
+    ]
+  },
   "opacity": {
     "isInherited": false,
     "subproperties": [
       "opacity"
     ],
     "supports": [],
     "values": [
       "inherit",
@@ -9327,16 +9342,20 @@ exports.PREFERENCES = [
     "background-blend-mode",
     "layout.css.background-blend-mode.enabled"
   ],
   [
     "font-variation-settings",
     "layout.css.font-variations.enabled"
   ],
   [
+    "offset-path",
+    "layout.css.motion-path.enabled"
+  ],
+  [
     "rotate",
     "layout.css.individual-transform.enabled"
   ],
   [
     "scale",
     "layout.css.individual-transform.enabled"
   ],
   [
--- a/layout/generic/nsFloatManager.cpp
+++ b/layout/generic/nsFloatManager.cpp
@@ -2404,16 +2404,20 @@ nsFloatManager::FloatInfo::FloatInfo(nsI
     case StyleShapeSourceType::None:
       // No need to create shape info.
       return;
 
     case StyleShapeSourceType::URL:
       MOZ_ASSERT_UNREACHABLE("shape-outside doesn't have URL source type!");
       return;
 
+    case StyleShapeSourceType::Path:
+      MOZ_ASSERT_UNREACHABLE("shape-outside doesn't have Path source type!");
+      return;
+
     case StyleShapeSourceType::Image: {
       float shapeImageThreshold = styleDisplay->mShapeImageThreshold;
       mShapeInfo = ShapeInfo::CreateImageShape(shapeOutside.GetShapeImage(),
                                                shapeImageThreshold,
                                                shapeMargin,
                                                mFrame,
                                                aMarginRect,
                                                aWM,
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -1946,16 +1946,45 @@ Gecko_NewBasicShape(mozilla::StyleShapeS
 
 void
 Gecko_NewShapeImage(mozilla::StyleShapeSource* aShape)
 {
   aShape->SetShapeImage(MakeUnique<nsStyleImage>());
 }
 
 void
+Gecko_NewStyleSVGPath(mozilla::StyleShapeSource* aShape)
+{
+  MOZ_ASSERT(aShape);
+  aShape->SetPath(MakeUnique<mozilla::StyleSVGPath>());
+}
+
+void
+Gecko_SetStyleMotion(UniquePtr<mozilla::StyleMotion>* aMotion,
+                     mozilla::StyleMotion* aValue)
+{
+  MOZ_ASSERT(aMotion);
+  aMotion->reset(aValue);
+}
+
+mozilla::StyleMotion*
+Gecko_NewStyleMotion()
+{
+  return new StyleMotion();
+}
+
+void
+Gecko_CopyStyleMotions(mozilla::UniquePtr<mozilla::StyleMotion>* aMotion,
+                       const mozilla::StyleMotion* aOther)
+{
+  MOZ_ASSERT(aMotion);
+  *aMotion = aOther ? MakeUnique<StyleMotion>(*aOther) : nullptr;
+}
+
+void
 Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len)
 {
   effects->mFilters.Clear();
   effects->mFilters.SetLength(new_len);
 }
 
 void
 Gecko_CopyFiltersFrom(nsStyleEffects* aSrc, nsStyleEffects* aDest)
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -519,16 +519,22 @@ void Gecko_SetStyleCoordCalcValue(nsStyl
 
 void Gecko_CopyShapeSourceFrom(mozilla::StyleShapeSource* dst, const mozilla::StyleShapeSource* src);
 
 void Gecko_DestroyShapeSource(mozilla::StyleShapeSource* shape);
 void Gecko_NewBasicShape(mozilla::StyleShapeSource* shape,
                          mozilla::StyleBasicShapeType type);
 void Gecko_NewShapeImage(mozilla::StyleShapeSource* shape);
 void Gecko_StyleShapeSource_SetURLValue(mozilla::StyleShapeSource* shape, mozilla::css::URLValue* uri);
+void Gecko_NewStyleSVGPath(mozilla::StyleShapeSource* shape);
+void Gecko_SetStyleMotion(mozilla::UniquePtr<mozilla::StyleMotion>* aMotion,
+                          mozilla::StyleMotion* aValue);
+mozilla::StyleMotion* Gecko_NewStyleMotion();
+void Gecko_CopyStyleMotions(mozilla::UniquePtr<mozilla::StyleMotion>* motion,
+                            const mozilla::StyleMotion* other);
 
 void Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len);
 void Gecko_CopyFiltersFrom(nsStyleEffects* aSrc, nsStyleEffects* aDest);
 void Gecko_nsStyleFilter_SetURLValue(nsStyleFilter* effects, mozilla::css::URLValue* uri);
 
 void Gecko_nsStyleSVGPaint_CopyFrom(nsStyleSVGPaint* dest, const nsStyleSVGPaint* src);
 void Gecko_nsStyleSVGPaint_SetURLValue(nsStyleSVGPaint* paint, mozilla::css::URLValue* uri);
 void Gecko_nsStyleSVGPaint_Reset(nsStyleSVGPaint* paint);
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -468,16 +468,17 @@ structs-types = [
     "mozilla::dom::ShadowRoot",
     "mozilla::AnonymousCounterStyle",
     "mozilla::AtomArray",
     "mozilla::FontStretch",
     "mozilla::FontSlantStyle",
     "mozilla::FontWeight",
     "mozilla::MallocSizeOf",
     "mozilla::OriginFlags",
+    "mozilla::StyleMotion",
     "mozilla::UniquePtr",
     "mozilla::StyleDisplayMode",
     "ServoRawOffsetArc",
     "DeclarationBlockMutationClosure",
     "nsAttrValue",
     "nsIContent",
     "nsINode",
     "nsIDocument",
--- a/layout/style/ServoCSSPropList.mako.py
+++ b/layout/style/ServoCSSPropList.mako.py
@@ -89,16 +89,17 @@ SERIALIZED_PREDEFINED_TYPES = [
     "FontVariationSettings",
     "FontWeight",
     "Integer",
     "Length",
     "LengthOrPercentage",
     "NonNegativeLength",
     "NonNegativeLengthOrPercentage",
     "ListStyleType",
+    "OffsetPath",
     "Opacity",
     "Resize",
     "url::ImageUrlOrNone",
 ]
 
 def serialized_by_servo(prop):
     # If the property requires layout information, no such luck.
     if "GETCS_NEEDS_LAYOUT_FLUSH" in prop.flags:
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -5043,16 +5043,21 @@ nsComputedDOMStyle::GetShapeSource(
       val->SetIdent(eCSSKeyword_none);
       return val.forget();
     }
     case StyleShapeSourceType::Image: {
       RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
       SetValueToStyleImage(*aShapeSource.GetShapeImage(), val);
       return val.forget();
     }
+    case StyleShapeSourceType::Path: {
+      // Bug 1246764: we have to support this for clip-path. For now, no one
+      // uses this.
+      MOZ_ASSERT_UNREACHABLE("Unexpected SVG Path type.");
+    }
   }
   return nullptr;
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetClipPath()
 {
   return GetShapeSource(StyleSVGReset()->mClipPath,
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -193,16 +193,17 @@ enum class StyleShapeRadius : uint8_t {
 
 // Shape source type
 enum class StyleShapeSourceType : uint8_t {
   None,
   URL,   // clip-path only
   Image, // shape-outside only
   Shape,
   Box,
+  Path,  // SVG path function
 };
 
 // -moz-stack-sizing
 enum class StyleStackSizing : uint8_t {
   Ignore,
   StretchToFit,
   IgnoreHorizontal,
   IgnoreVertical,
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1034,16 +1034,19 @@ StyleShapeSource::operator==(const Style
       return *mShapeImage == *aOther.mShapeImage;
 
     case StyleShapeSourceType::Shape:
       return *mBasicShape == *aOther.mBasicShape &&
         mReferenceBox == aOther.mReferenceBox;
 
     case StyleShapeSourceType::Box:
       return mReferenceBox == aOther.mReferenceBox;
+
+    case StyleShapeSourceType::Path:
+      return *mSVGPath == *aOther.mSVGPath;
   }
 
   MOZ_ASSERT_UNREACHABLE("Unexpected shape source type!");
   return true;
 }
 
 void
 StyleShapeSource::SetURL(css::URLValue* aValue)
@@ -1086,16 +1089,25 @@ StyleShapeSource::SetBasicShape(UniquePt
   MOZ_ASSERT(aBasicShape);
   DoDestroy();
   new (&mBasicShape) UniquePtr<StyleBasicShape>(std::move(aBasicShape));
   mReferenceBox = aReferenceBox;
   mType = StyleShapeSourceType::Shape;
 }
 
 void
+StyleShapeSource::SetPath(UniquePtr<StyleSVGPath> aPath)
+{
+  MOZ_ASSERT(aPath);
+  DoDestroy();
+  new (&mSVGPath) UniquePtr<StyleSVGPath>(std::move(aPath));
+  mType = StyleShapeSourceType::Path;
+}
+
+void
 StyleShapeSource::SetReferenceBox(StyleGeometryBox aReferenceBox)
 {
   DoDestroy();
   mReferenceBox = aReferenceBox;
   mType = StyleShapeSourceType::Box;
 }
 
 void
@@ -1118,30 +1130,37 @@ StyleShapeSource::DoCopy(const StyleShap
     case StyleShapeSourceType::Shape:
       SetBasicShape(MakeUnique<StyleBasicShape>(*aOther.GetBasicShape()),
                     aOther.GetReferenceBox());
       break;
 
     case StyleShapeSourceType::Box:
       SetReferenceBox(aOther.GetReferenceBox());
       break;
+
+    case StyleShapeSourceType::Path:
+      SetPath(MakeUnique<StyleSVGPath>(*aOther.GetPath()));
+      break;
   }
 }
 
 void
 StyleShapeSource::DoDestroy()
 {
   switch (mType) {
     case StyleShapeSourceType::Shape:
       mBasicShape.~UniquePtr<StyleBasicShape>();
       break;
     case StyleShapeSourceType::Image:
     case StyleShapeSourceType::URL:
       mShapeImage.~UniquePtr<nsStyleImage>();
       break;
+    case StyleShapeSourceType::Path:
+      mSVGPath.~UniquePtr<StyleSVGPath>();
+      break;
     case StyleShapeSourceType::None:
     case StyleShapeSourceType::Box:
       // Not a union type, so do nothing.
       break;
   }
   mType = StyleShapeSourceType::None;
 }
 
@@ -3612,16 +3631,19 @@ 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)
+  , 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)
@@ -3730,16 +3752,39 @@ CompareTransformValues(const RefPtr<nsCS
     } else {
       result |= nsChangeHint_UpdateOverflow;
     }
   }
 
   return result;
 }
 
+static inline nsChangeHint
+CompareMotionValues(const StyleMotion* aMotion,
+                    const StyleMotion* aNewMotion)
+{
+  nsChangeHint result = nsChangeHint(0);
+
+  // TODO: Bug 1482737: This probably doesn't need to UpdateOverflow
+  // (or UpdateTransformLayer) if there's already a transform.
+  if (!aMotion != !aNewMotion ||
+      (aMotion && *aMotion != *aNewMotion)) {
+    // Set the same hints as what we use for transform because motion path is
+    // a kind of transform and will be combined with other transforms.
+    result |= nsChangeHint_UpdateTransformLayer;
+    if ((aMotion && aMotion->HasPath()) &&
+        (aNewMotion && aNewMotion->HasPath())) {
+      result |= nsChangeHint_UpdatePostTransformOverflow;
+    } else {
+      result |= nsChangeHint_UpdateOverflow;
+    }
+  }
+  return result;
+}
+
 nsChangeHint
 nsStyleDisplay::CalcDifference(const nsStyleDisplay& aNewData) const
 {
   nsChangeHint hint = nsChangeHint(0);
 
   if (!DefinitelyEqualURIsAndPrincipal(mBinding, aNewData.mBinding)
       || mPosition != aNewData.mPosition
       || mDisplay != aNewData.mDisplay
@@ -3861,16 +3906,17 @@ nsStyleDisplay::CalcDifference(const nsS
     transformHint |= CompareTransformValues(mSpecifiedTransform,
                                             aNewData.mSpecifiedTransform);
     transformHint |= CompareTransformValues(mSpecifiedRotate, aNewData.
                                             mSpecifiedRotate);
     transformHint |= CompareTransformValues(mSpecifiedTranslate,
                                             aNewData.mSpecifiedTranslate);
     transformHint |= CompareTransformValues(mSpecifiedScale,
                                             aNewData.mSpecifiedScale);
+    transformHint |= CompareMotionValues(mMotion.get(), aNewData.mMotion.get());
 
     const nsChangeHint kUpdateOverflowAndRepaintHint =
       nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
     for (uint8_t index = 0; index < 3; ++index) {
       if (mTransformOrigin[index] != aNewData.mTransformOrigin[index]) {
         transformHint |= nsChangeHint_UpdateTransformLayer |
                          nsChangeHint_UpdatePostTransformOverflow;
         break;
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1966,16 +1966,37 @@ private:
   // (top, right, bottom, left) for inset
   nsTArray<nsStyleCoord> mCoordinates;
   // position of center for ellipse or circle
   mozilla::Position mPosition;
   // corner radii for inset (0 if not set)
   nsStyleCorners mRadius;
 };
 
+struct StyleSVGPath final
+{
+  const nsTArray<StylePathCommand>& Path() const
+  {
+    return mPath;
+  }
+
+  bool operator==(const StyleSVGPath& aOther) const
+  {
+    return mPath == aOther.mPath;
+  }
+
+  bool operator!=(const StyleSVGPath& aOther) const
+  {
+    return !(*this == aOther);
+  }
+
+private:
+  nsTArray<StylePathCommand> mPath;
+};
+
 struct StyleShapeSource final
 {
   StyleShapeSource();
 
   StyleShapeSource(const StyleShapeSource& aSource);
 
   ~StyleShapeSource();
 
@@ -2030,32 +2051,67 @@ struct StyleShapeSource final
     MOZ_ASSERT(mType == StyleShapeSourceType::Box ||
                mType == StyleShapeSourceType::Shape,
                "Wrong shape source type!");
     return mReferenceBox;
   }
 
   void SetReferenceBox(StyleGeometryBox aReferenceBox);
 
+  const StyleSVGPath* GetPath() const
+  {
+    MOZ_ASSERT(mType == StyleShapeSourceType::Path, "Wrong shape source type!");
+    return mSVGPath.get();
+  }
+  void SetPath(UniquePtr<StyleSVGPath> aPath);
+
 private:
   void* operator new(size_t) = delete;
 
   void DoCopy(const StyleShapeSource& aOther);
   void DoDestroy();
 
   union {
     mozilla::UniquePtr<StyleBasicShape> mBasicShape;
     mozilla::UniquePtr<nsStyleImage> mShapeImage;
-    // TODO: Bug 1429298, implement SVG Path function.
+    mozilla::UniquePtr<StyleSVGPath> mSVGPath;
     // TODO: Bug 1480665, implement ray() function.
   };
   StyleShapeSourceType mType = StyleShapeSourceType::None;
   StyleGeometryBox mReferenceBox = StyleGeometryBox::NoBox;
 };
 
+struct StyleMotion final
+{
+  bool operator==(const StyleMotion& aOther) const
+  {
+    return mOffsetPath == aOther.mOffsetPath;
+  }
+
+  bool operator!=(const StyleMotion& aOther) const
+  {
+    return !(*this == aOther);
+  }
+
+  const StyleShapeSource& OffsetPath() const
+  {
+    return mOffsetPath;
+  }
+
+  bool HasPath() const
+  {
+    // Bug 1186329: We have to check other acceptable types after supporting
+    // different values of offset-path. e.g. basic-shapes, ray.
+    return mOffsetPath.GetType() == StyleShapeSourceType::Path;
+  }
+
+private:
+  StyleShapeSource mOffsetPath;
+};
+
 } // namespace mozilla
 
 struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay
 {
   typedef mozilla::StyleGeometryBox StyleGeometryBox;
 
   explicit nsStyleDisplay(const nsPresContext* aContext);
   nsStyleDisplay(const nsStyleDisplay& aOther);
@@ -2120,16 +2176,17 @@ 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;
+  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
@@ -2375,17 +2432,18 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   }
 
   /* Returns whether the element has the -moz-transform property
    * or a related property. */
   bool HasTransformStyle() const {
     return mSpecifiedTransform || mSpecifiedRotate || mSpecifiedTranslate ||
            mSpecifiedScale ||
            mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D ||
-           (mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM);
+           (mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM) ||
+           (mMotion && mMotion->HasPath());
   }
 
   bool HasIndividualTransform() const {
     return mSpecifiedRotate || mSpecifiedTranslate || mSpecifiedScale;
   }
 
   bool HasPerspectiveStyle() const {
     return mChildPerspective.GetUnit() == eStyleUnit_Coord;
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -8168,16 +8168,38 @@ if (IsCSSPropertyPrefEnabled("layout.css
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "auto" ],
     other_values: [ "none", "thin" ],
     invalid_values: [ "1px" ]
   };
 }
 
+if (IsCSSPropertyPrefEnabled("layout.css.motion-path.enabled")) {
+  gCSSProperties["offset-path"] = {
+    domProp: "offsetPath",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "none" ],
+    other_values: [
+      "path('M 10 10 20 20 H 90 V 90 Z')",
+      "path('M10 10 20,20H90V90Z')",
+      "path('M 10 10 C 20 20, 40 20, 50 10')",
+      "path('M 10 80 C 40 10, 65 10, 95 80 S 1.5e2 150, 180 80')",
+      "path('M 10 80 Q 95 10 180 80')",
+      "path('M 10 80 Q 52.5 10, 95 80 T 180 80')",
+      "path('M 80 80 A 45 45, 0, 0, 0, 1.25e2 1.25e2 L 125 80 Z')",
+      "path('M100-200h20z')",
+      "path('M10,10L20.6.5z')"
+    ],
+    invalid_values: [ "path('')", "path()", "path(a)", "path('M 10 Z')" ,
+                      "path('M 10-10 20')", "path('M 10 10 C 20 20 40 20')" ]
+  };
+}
+
 const OVERFLOW_MOZKWS = [
   "-moz-scrollbars-none",
   "-moz-scrollbars-horizontal",
   "-moz-scrollbars-vertical",
 ];
 if (IsCSSPropertyPrefEnabled("layout.css.overflow.moz-scrollbars.enabled")) {
   gCSSProperties["overflow"].other_values.push(...OVERFLOW_MOZKWS);
 } else {
--- a/servo/components/style/cbindgen.toml
+++ b/servo/components/style/cbindgen.toml
@@ -2,25 +2,26 @@ header = """/* This Source Code Form is 
  * 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/. */"""
 autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen.
  * To generate this file:
  *   1. Get the latest cbindgen using `cargo install --force cbindgen`
  *      a. Alternatively, you can clone `https://github.com/eqrion/cbindgen` and use a tagged release
  *   2. Run `rustup run nightly cbindgen toolkit/library/rust/ --lockfile Cargo.lock --crate style -o layout/style/ServoStyleConsts.h`
  */"""
+include_guard = "mozilla_ServoStyleConsts_h"
 include_version = true
 braces = "SameLine"
 line_length = 80
 tab_width = 2
 language = "C++"
 namespaces = ["mozilla"]
 
 [struct]
 derive_eq = true
 
 [enum]
 derive_helper_methods = true
 
 [export]
 prefix = "Style"
-include = ["StyleDisplay", "StyleAppearance", "StyleDisplayMode"]
-item_types = ["enums"]
+include = ["StyleDisplay", "StyleAppearance", "StyleDisplayMode", "StylePathCommand"]
+item_types = ["enums", "structs", "typedefs"]
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -633,16 +633,17 @@ pub mod basic_shape {
     use gecko_bindings::structs::{StyleGeometryBox, StyleShapeSource, StyleShapeSourceType};
     use gecko_bindings::structs::{nsStyleCoord, nsStyleCorners};
     use gecko_bindings::sugar::ns_style_coord::{CoordDataMut, CoordDataValue};
     use gecko_bindings::sugar::refptr::RefPtr;
     use std::borrow::Borrow;
     use values::computed::basic_shape::{BasicShape, ClippingShape, FloatAreaShape, ShapeRadius};
     use values::computed::border::{BorderCornerRadius, BorderRadius};
     use values::computed::length::LengthOrPercentage;
+    use values::computed::motion::OffsetPath;
     use values::computed::position;
     use values::computed::url::ComputedUrl;
     use values::generics::basic_shape::{BasicShape as GenericBasicShape, InsetRect, Polygon};
     use values::generics::basic_shape::{Circle, Ellipse, FillRule};
     use values::generics::basic_shape::{GeometryBox, ShapeBox, ShapeSource};
     use values::generics::border::BorderRadius as GenericBorderRadius;
     use values::generics::rect::Rect;
 
@@ -664,16 +665,17 @@ pub mod basic_shape {
                     let reference_box = if self.mReferenceBox == StyleGeometryBox::NoBox {
                         None
                     } else {
                         Some(self.mReferenceBox.into())
                     };
                     Some(ShapeSource::Shape(shape, reference_box))
                 },
                 StyleShapeSourceType::URL | StyleShapeSourceType::Image => None,
+                StyleShapeSourceType::Path => None,
             }
         }
     }
 
     impl<'a> From<&'a StyleShapeSource> for ClippingShape {
         fn from(other: &'a StyleShapeSource) -> Self {
             match other.mType {
                 StyleShapeSourceType::URL => unsafe {
@@ -705,16 +707,39 @@ pub mod basic_shape {
                 },
                 _ => other
                     .into_shape_source()
                     .expect("Couldn't convert to StyleSource!"),
             }
         }
     }
 
+    impl<'a> From<&'a StyleShapeSource> for OffsetPath {
+        fn from(other: &'a StyleShapeSource) -> Self {
+            use gecko_bindings::structs::StylePathCommand;
+            use values::specified::motion::{SVGPathData, PathCommand};
+            match other.mType {
+                StyleShapeSourceType::Path => {
+                    let gecko_path = unsafe { &*other.__bindgen_anon_1.mSVGPath.as_ref().mPtr };
+                    let result: Vec<PathCommand> =
+                        gecko_path.mPath.iter().map(|gecko: &StylePathCommand| {
+                            // unsafe: cbindgen ensures the representation is the same.
+                            unsafe{ ::std::mem::transmute(*gecko) }
+                        }).collect();
+                    OffsetPath::Path(SVGPathData::new(result.into_boxed_slice()))
+                },
+                StyleShapeSourceType::None => OffsetPath::none(),
+                StyleShapeSourceType::Shape |
+                StyleShapeSourceType::Box |
+                StyleShapeSourceType::URL |
+                StyleShapeSourceType::Image => unreachable!("Unsupported offset-path type"),
+            }
+        }
+    }
+
     impl<'a> From<&'a StyleBasicShape> for BasicShape {
         fn from(other: &'a StyleBasicShape) -> Self {
             match other.mType {
                 StyleBasicShapeType::Inset => {
                     let t = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[0]);
                     let r = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[1]);
                     let b = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[2]);
                     let l = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[3]);
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -3048,17 +3048,17 @@ fn static_assert() {
                           animation-direction animation-fill-mode animation-play-state
                           animation-iteration-count animation-timing-function
                           transition-duration transition-delay
                           transition-timing-function transition-property
                           page-break-before page-break-after rotate
                           scroll-snap-points-x scroll-snap-points-y
                           scroll-snap-type-x scroll-snap-type-y scroll-snap-coordinate
                           perspective-origin -moz-binding will-change
-                          overscroll-behavior-x overscroll-behavior-y
+                          offset-path overscroll-behavior-x overscroll-behavior-y
                           overflow-clip-box-inline overflow-clip-box-block
                           perspective-origin -moz-binding will-change
                           shape-outside contain touch-action translate
                           scale""" %>
 <%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}">
     #[inline]
     pub fn set_display(&mut self, v: longhands::display::computed_value::T) {
         // unsafe: cbindgen ensures the representation is the same.
@@ -3676,16 +3676,61 @@ fn static_assert() {
         }
 
         return servo_flags;
     }
 
     ${impl_simple_copy("contain", "mContain")}
 
     ${impl_simple_type_with_conversion("touch_action")}
+
+    pub fn set_offset_path(&mut self, v: longhands::offset_path::computed_value::T) {
+        use gecko_bindings::bindings::{Gecko_NewStyleMotion, Gecko_NewStyleSVGPath};
+        use gecko_bindings::bindings::Gecko_SetStyleMotion;
+        use gecko_bindings::structs::StyleShapeSourceType;
+        use values::specified::OffsetPath;
+
+        let motion = unsafe { Gecko_NewStyleMotion().as_mut().unwrap() };
+        match v {
+            OffsetPath::None => motion.mOffsetPath.mType = StyleShapeSourceType::None,
+            OffsetPath::Path(servo_path) => {
+                motion.mOffsetPath.mType = StyleShapeSourceType::Path;
+                let gecko_path = unsafe {
+                    let ref mut source = motion.mOffsetPath;
+                    Gecko_NewStyleSVGPath(source);
+                    &mut source.__bindgen_anon_1.mSVGPath.as_mut().mPtr.as_mut().unwrap().mPath
+                };
+                unsafe { gecko_path.set_len(servo_path.commands().len() as u32) };
+                debug_assert_eq!(gecko_path.len(), servo_path.commands().len());
+                for (servo, gecko) in servo_path.commands().iter().zip(gecko_path.iter_mut()) {
+                    // unsafe: cbindgen ensures the representation is the same.
+                    *gecko = unsafe { transmute(*servo) };
+                }
+            },
+        }
+        unsafe { Gecko_SetStyleMotion(&mut self.gecko.mMotion, motion) };
+    }
+
+    pub fn clone_offset_path(&self) -> longhands::offset_path::computed_value::T {
+        use values::specified::OffsetPath;
+        match unsafe { self.gecko.mMotion.mPtr.as_ref() } {
+            None => OffsetPath::none(),
+            Some(v) => (&v.mOffsetPath).into()
+        }
+    }
+
+    pub fn copy_offset_path_from(&mut self, other: &Self) {
+        use gecko_bindings::bindings::Gecko_CopyStyleMotions;
+        unsafe { Gecko_CopyStyleMotions(&mut self.gecko.mMotion, other.gecko.mMotion.mPtr) };
+    }
+
+    pub fn reset_offset_path(&mut self, other: &Self) {
+        self.copy_offset_path_from(other);
+    }
+
 </%self:impl_trait>
 
 <%def name="simple_image_array_property(name, shorthand, field_name)">
     <%
         image_layers_field = "mImage" if shorthand == "background" else "mMask"
         copy_simple_image_array_property(name, shorthand, image_layers_field, field_name)
     %>
 
--- a/servo/components/style/properties/longhands/box.mako.rs
+++ b/servo/components/style/properties/longhands/box.mako.rs
@@ -351,16 +351,27 @@
     animation_value_type="ComputedValue",
     boxed=True,
     flags="CREATES_STACKING_CONTEXT FIXPOS_CB GETCS_NEEDS_LAYOUT_FLUSH",
     gecko_pref="layout.css.individual-transform.enabled",
     spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms",
     servo_restyle_damage="reflow_out_of_flow"
 )}
 
+// Motion Path Module Level 1
+${helpers.predefined_type(
+    "offset-path",
+    "OffsetPath",
+    "computed::OffsetPath::none()",
+    animation_value_type="none",
+    gecko_pref="layout.css.motion-path.enabled",
+    flags="CREATES_STACKING_CONTEXT FIXPOS_CB",
+    spec="https://drafts.fxtf.org/motion-1/#offset-path-property"
+)}
+
 // CSSOM View Module
 // https://www.w3.org/TR/cssom-view-1/
 ${helpers.single_keyword("scroll-behavior",
                          "auto smooth",
                          gecko_pref="layout.css.scroll-behavior.property-enabled",
                          products="gecko",
                          spec="https://drafts.csswg.org/cssom-view/#propdef-scroll-behavior",
                          animation_value_type="discrete")}
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -60,16 +60,17 @@ pub use super::{Auto, Either, None_};
 pub use super::specified::{BorderStyle, TextDecorationLine};
 pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNumber, LengthOrPercentage};
 pub use self::length::{LengthOrPercentageOrAuto, LengthOrPercentageOrNone, MaxLength, MozLength};
 pub use self::length::{CSSPixelLength, ExtremumLength, NonNegativeLength};
 pub use self::length::{NonNegativeLengthOrPercentage, NonNegativeLengthOrPercentageOrAuto};
 pub use self::list::Quotes;
 #[cfg(feature = "gecko")]
 pub use self::list::ListStyleType;
+pub use self::motion::OffsetPath;
 pub use self::outline::OutlineStyle;
 pub use self::percentage::{Percentage, NonNegativePercentage};
 pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, ZIndex};
 pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind};
 pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth};
 pub use self::svg::MozContextProperties;
 pub use self::table::XSpan;
 pub use self::text::{InitialLetter, LetterSpacing, LineHeight, MozTabSize};
@@ -95,16 +96,17 @@ pub mod counters;
 pub mod effects;
 pub mod flex;
 pub mod font;
 #[cfg(feature = "gecko")]
 pub mod gecko;
 pub mod image;
 pub mod length;
 pub mod list;
+pub mod motion;
 pub mod outline;
 pub mod percentage;
 pub mod position;
 pub mod rect;
 pub mod resolution;
 pub mod svg;
 pub mod table;
 pub mod text;
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/computed/motion.rs
@@ -0,0 +1,10 @@
+/* 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 motion path.
+
+/// A computed offset-path. The computed value is as specified value.
+///
+/// https://drafts.fxtf.org/motion-1/#offset-path-property
+pub use values::specified::motion::OffsetPath as OffsetPath;
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -53,16 +53,17 @@ pub use self::length::{AbsoluteLength, C
 pub use self::length::{FontRelativeLength, Length, LengthOrNumber};
 pub use self::length::{LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrNone, MaxLength, MozLength};
 pub use self::length::{NoCalcLength, ViewportPercentageLength};
 pub use self::length::{NonNegativeLengthOrPercentage, NonNegativeLengthOrPercentageOrAuto};
 pub use self::list::Quotes;
 #[cfg(feature = "gecko")]
 pub use self::list::ListStyleType;
+pub use self::motion::OffsetPath;
 pub use self::outline::OutlineStyle;
 pub use self::rect::LengthOrNumberRect;
 pub use self::resolution::Resolution;
 pub use self::percentage::Percentage;
 pub use self::position::{GridAutoFlow, GridTemplateAreas, Position};
 pub use self::position::{PositionComponent, ZIndex};
 pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind};
 pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth};
@@ -96,16 +97,17 @@ pub mod flex;
 pub mod font;
 #[cfg(feature = "gecko")]
 pub mod gecko;
 pub mod grid;
 pub mod image;
 pub mod length;
 pub mod list;
 pub mod outline;
+pub mod motion;
 pub mod percentage;
 pub mod position;
 pub mod rect;
 pub mod resolution;
 pub mod source_size_list;
 pub mod svg;
 pub mod table;
 pub mod text;
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/specified/motion.rs
@@ -0,0 +1,643 @@
+/* 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 motion path.
+
+use cssparser::Parser;
+use parser::{Parse, ParserContext};
+use std::fmt::{self, Write};
+use std::iter::Peekable;
+use std::str::Chars;
+use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
+use style_traits::values::SequenceWriter;
+use values::CSSFloat;
+
+/// The offset-path value.
+///
+/// https://drafts.fxtf.org/motion-1/#offset-path-property
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)]
+pub enum OffsetPath {
+    // We could merge SVGPathData into ShapeSource, so we could reuse them. However,
+    // we don't want to support other value for offset-path, so use SVGPathData only for now.
+    /// Path value for path(<string>).
+    #[css(function)]
+    Path(SVGPathData),
+    /// None value.
+    None,
+    // Bug 1186329: Implement ray(), <basic-shape>, <geometry-box>, and <url>.
+}
+
+impl OffsetPath {
+    /// Return None.
+    #[inline]
+    pub fn none() -> Self {
+        OffsetPath::None
+    }
+}
+
+impl Parse for OffsetPath {
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>
+    ) -> Result<Self, ParseError<'i>> {
+        // Parse none.
+        if input.try(|i| i.expect_ident_matching("none")).is_ok() {
+            return Ok(OffsetPath::none());
+        }
+
+        // Parse possible functions.
+        let location = input.current_source_location();
+        let function = input.expect_function()?.clone();
+        input.parse_nested_block(move |i| {
+            match_ignore_ascii_case! { &function,
+                // Bug 1186329: Implement the parser for ray(), <basic-shape>, <geometry-box>,
+                // and <url>.
+                "path" => SVGPathData::parse(context, i).map(OffsetPath::Path),
+                _ => {
+                    Err(location.new_custom_error(
+                        StyleParseErrorKind::UnexpectedFunction(function.clone())
+                    ))
+                },
+            }
+        })
+    }
+}
+
+/// SVG Path parser.
+struct PathParser<'a> {
+    chars: Peekable<Chars<'a>>,
+    path: Vec<PathCommand>,
+}
+
+impl<'a> PathParser<'a> {
+    /// Parse a sub-path.
+    fn parse_subpath(&mut self) -> Result<(), ()> {
+        // Handle "moveto" Command first. If there is no "moveto", this is not a valid sub-path
+        // (i.e. not a valid moveto-drawto-command-group).
+        self.parse_moveto()?;
+
+        // Handle other commands.
+        loop {
+            skip_wsp(&mut self.chars);
+            if self.chars.peek().map_or(true, |m| *m == 'M' || *m == 'm') {
+                break;
+            }
+
+            match self.chars.next() {
+                Some(command) => {
+                    let abs = command.is_uppercase();
+                    match command {
+                        'Z' | 'z' => {
+                            // Note: A "closepath" coulbe be followed immediately by "moveto" or
+                            // any other command, so we don't break this loop.
+                            self.path.push(PathCommand::ClosePath);
+                        },
+                        'L' | 'l' => {
+                            skip_wsp(&mut self.chars);
+                            self.parse_lineto(abs)?;
+                        },
+                        'H' | 'h' => {
+                            skip_wsp(&mut self.chars);
+                            self.parse_h_lineto(abs)?;
+                        },
+                        'V' | 'v' => {
+                            skip_wsp(&mut self.chars);
+                            self.parse_v_lineto(abs)?;
+                        },
+                        'C' | 'c' => {
+                            skip_wsp(&mut self.chars);
+                            self.parse_curveto(abs)?;
+                        },
+                        'S' | 's' => {
+                            skip_wsp(&mut self.chars);
+                            self.parse_smooth_curveto(abs)?;
+                        },
+                        'Q' | 'q' => {
+                            skip_wsp(&mut self.chars);
+                            self.parse_quadratic_bezier_curveto(abs)?;
+                        },
+                        'T' | 't' => {
+                            skip_wsp(&mut self.chars);
+                            self.parse_smooth_quadratic_bezier_curveto(abs)?;
+                        },
+                        'A' | 'a' => {
+                            skip_wsp(&mut self.chars);
+                            self.parse_elliprical_arc(abs)?;
+                        },
+                        _ => return Err(()),
+                    }
+                },
+                _ => break, // no more commands.
+            }
+        }
+        Ok(())
+    }
+
+    /// Parse "moveto" command.
+    fn parse_moveto(&mut self) -> Result<(), ()> {
+        let command = match self.chars.next() {
+            Some(c) if c == 'M' || c == 'm' => c,
+            _ => return Err(()),
+        };
+
+        skip_wsp(&mut self.chars);
+        let point = parse_coord(&mut self.chars)?;
+        let absolute = command == 'M';
+        self.path.push(PathCommand::MoveTo { point, absolute } );
+
+        // End of string or the next character is a possible new command.
+        if !skip_wsp(&mut self.chars) ||
+           self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
+            return Ok(());
+        }
+        skip_comma_wsp(&mut self.chars);
+
+        // If a moveto is followed by multiple pairs of coordinates, the subsequent
+        // pairs are treated as implicit lineto commands.
+        self.parse_lineto(absolute)
+    }
+
+    /// Parse "lineto" command.
+    fn parse_lineto(&mut self, absolute: bool) -> Result<(), ()> {
+        loop {
+            let point = parse_coord(&mut self.chars)?;
+            self.path.push(PathCommand::LineTo { point, absolute });
+
+            // End of string or the next character is a possible new command.
+            if !skip_wsp(&mut self.chars) ||
+               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
+                break;
+            }
+            skip_comma_wsp(&mut self.chars);
+        }
+        Ok(())
+    }
+
+    /// Parse horizontal "lineto" command.
+    fn parse_h_lineto(&mut self, absolute: bool) -> Result<(), ()> {
+        loop {
+            let x = parse_number(&mut self.chars)?;
+            self.path.push(PathCommand::HorizontalLineTo { x, absolute });
+
+            // End of string or the next character is a possible new command.
+            if !skip_wsp(&mut self.chars) ||
+               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
+                break;
+            }
+            skip_comma_wsp(&mut self.chars);
+        }
+        Ok(())
+    }
+
+    /// Parse vertical "lineto" command.
+    fn parse_v_lineto(&mut self, absolute: bool) -> Result<(), ()> {
+        loop {
+            let y = parse_number(&mut self.chars)?;
+            self.path.push(PathCommand::VerticalLineTo { y, absolute });
+
+            // End of string or the next character is a possible new command.
+            if !skip_wsp(&mut self.chars) ||
+               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
+                break;
+            }
+            skip_comma_wsp(&mut self.chars);
+        }
+        Ok(())
+    }
+
+    /// Parse cubic Bézier curve command.
+    fn parse_curveto(&mut self, absolute: bool) -> Result<(), ()> {
+        loop {
+            let control1 = parse_coord(&mut self.chars)?;
+            skip_comma_wsp(&mut self.chars);
+            let control2 = parse_coord(&mut self.chars)?;
+            skip_comma_wsp(&mut self.chars);
+            let point = parse_coord(&mut self.chars)?;
+
+            self.path.push(PathCommand::CurveTo { control1, control2, point, absolute });
+
+            // End of string or the next character is a possible new command.
+            if !skip_wsp(&mut self.chars) ||
+               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
+                break;
+            }
+            skip_comma_wsp(&mut self.chars);
+        }
+        Ok(())
+    }
+
+    /// Parse smooth "curveto" command.
+    fn parse_smooth_curveto(&mut self, absolute: bool) -> Result<(), ()> {
+        loop {
+            let control2 = parse_coord(&mut self.chars)?;
+            skip_comma_wsp(&mut self.chars);
+            let point = parse_coord(&mut self.chars)?;
+
+            self.path.push(PathCommand::SmoothCurveTo { control2, point, absolute });
+
+            // End of string or the next character is a possible new command.
+            if !skip_wsp(&mut self.chars) ||
+               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
+                break;
+            }
+            skip_comma_wsp(&mut self.chars);
+        }
+        Ok(())
+    }
+
+    /// Parse quadratic Bézier curve command.
+    fn parse_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> {
+        loop {
+            let control1 = parse_coord(&mut self.chars)?;
+            skip_comma_wsp(&mut self.chars);
+            let point = parse_coord(&mut self.chars)?;
+
+            self.path.push(PathCommand::QuadBezierCurveTo { control1, point, absolute });
+
+            // End of string or the next character is a possible new command.
+            if !skip_wsp(&mut self.chars) ||
+               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
+                break;
+            }
+            skip_comma_wsp(&mut self.chars);
+        }
+        Ok(())
+    }
+
+    /// Parse smooth quadratic Bézier curveto command.
+    fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> {
+        loop {
+            let point = parse_coord(&mut self.chars)?;
+
+            self.path.push(PathCommand::SmoothQuadBezierCurveTo { point, absolute });
+
+            // End of string or the next character is a possible new command.
+            if !skip_wsp(&mut self.chars) ||
+               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
+                break;
+            }
+            skip_comma_wsp(&mut self.chars);
+        }
+        Ok(())
+    }
+
+    /// Parse elliptical arc curve command.
+    fn parse_elliprical_arc(&mut self, absolute: bool) -> Result<(), ()> {
+        // Parse a flag whose value is '0' or '1'; otherwise, return Err(()).
+        let parse_flag = |iter: &mut Peekable<Chars>| -> Result<bool, ()> {
+            let value = match iter.peek() {
+                Some(c) if *c == '0' || *c == '1' => *c == '1',
+                _ => return Err(()),
+            };
+            iter.next();
+            Ok(value)
+        };
+
+        loop {
+            let rx = parse_number(&mut self.chars)?;
+            skip_comma_wsp(&mut self.chars);
+            let ry = parse_number(&mut self.chars)?;
+            skip_comma_wsp(&mut self.chars);
+            let angle = parse_number(&mut self.chars)?;
+            skip_comma_wsp(&mut self.chars);
+            let large_arc_flag = parse_flag(&mut self.chars)?;
+            skip_comma_wsp(&mut self.chars);
+            let sweep_flag = parse_flag(&mut self.chars)?;
+            skip_comma_wsp(&mut self.chars);
+            let point = parse_coord(&mut self.chars)?;
+
+            self.path.push(
+                PathCommand::EllipticalArc {
+                    rx, ry, angle, large_arc_flag, sweep_flag, point, absolute
+                }
+            );
+
+            // End of string or the next character is a possible new command.
+            if !skip_wsp(&mut self.chars) ||
+               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
+                break;
+            }
+            skip_comma_wsp(&mut self.chars);
+        }
+        Ok(())
+    }
+}
+
+/// The SVG path data.
+///
+/// https://www.w3.org/TR/SVG11/paths.html#PathData
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue)]
+pub struct SVGPathData(Box<[PathCommand]>);
+
+impl SVGPathData {
+    /// Return SVGPathData by a slice of PathCommand.
+    #[inline]
+    pub fn new(cmd: Box<[PathCommand]>) -> Self {
+        debug_assert!(!cmd.is_empty());
+        SVGPathData(cmd)
+    }
+
+    /// Get the array of PathCommand.
+    #[inline]
+    pub fn commands(&self) -> &[PathCommand] {
+        debug_assert!(!self.0.is_empty());
+        &self.0
+    }
+}
+
+impl ToCss for SVGPathData {
+    #[inline]
+    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+    where
+        W: fmt::Write
+    {
+        dest.write_char('"')?;
+        {
+            let mut writer = SequenceWriter::new(dest, " ");
+            for command in self.0.iter() {
+                writer.item(command)?;
+            }
+        }
+        dest.write_char('"')
+    }
+}
+
+impl Parse for SVGPathData {
+    // We cannot use cssparser::Parser to parse a SVG path string because the spec wants to make
+    // the SVG path string as compact as possible. (i.e. The whitespaces may be dropped.)
+    // e.g. "M100 200L100 200" is a valid SVG path string. If we use tokenizer, the first ident
+    // is "M100", instead of "M", and this is not correct. Therefore, we use a Peekable
+    // str::Char iterator to check each character.
+    fn parse<'i, 't>(
+        _context: &ParserContext,
+        input: &mut Parser<'i, 't>
+    ) -> Result<Self, ParseError<'i>> {
+        let location = input.current_source_location();
+        let path_string = input.expect_string()?.as_ref();
+        if path_string.is_empty() {
+            // Treat an empty string as invalid, so we will not set it.
+            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+        }
+
+        // Parse the svg path string as multiple sub-paths.
+        let mut path_parser = PathParser {
+            chars: path_string.chars().peekable(),
+            path: Vec::new(),
+        };
+        while skip_wsp(&mut path_parser.chars) {
+            if path_parser.parse_subpath().is_err() {
+                return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+            }
+        }
+
+        Ok(SVGPathData::new(path_parser.path.into_boxed_slice()))
+    }
+}
+
+
+/// The SVG path command.
+/// The fields of these commands are self-explanatory, so we skip the documents.
+/// Note: the index of the control points, e.g. control1, control2, are mapping to the control
+/// points of the Bézier curve in the spec.
+///
+/// https://www.w3.org/TR/SVG11/paths.html#PathData
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo)]
+#[allow(missing_docs)]
+#[repr(C, u8)]
+pub enum PathCommand {
+    /// The unknown type.
+    /// https://www.w3.org/TR/SVG/paths.html#__svg__SVGPathSeg__PATHSEG_UNKNOWN
+    Unknown,
+    /// The "moveto" command.
+    MoveTo { point: CoordPair, absolute: bool },
+    /// The "lineto" command.
+    LineTo { point: CoordPair, absolute: bool },
+    /// The horizontal "lineto" command.
+    HorizontalLineTo { x: CSSFloat, absolute: bool },
+    /// The vertical "lineto" command.
+    VerticalLineTo { y: CSSFloat, absolute: bool },
+    /// The cubic Bézier curve command.
+    CurveTo { control1: CoordPair, control2: CoordPair, point: CoordPair, absolute: bool },
+    /// The smooth curve command.
+    SmoothCurveTo { control2: CoordPair, point: CoordPair, absolute: bool },
+    /// The quadratic Bézier curve command.
+    QuadBezierCurveTo { control1: CoordPair, point: CoordPair, absolute: bool },
+    /// The smooth quadratic Bézier curve command.
+    SmoothQuadBezierCurveTo { point: CoordPair, absolute: bool },
+    /// The elliptical arc curve command.
+    EllipticalArc {
+        rx: CSSFloat,
+        ry: CSSFloat,
+        angle: CSSFloat,
+        large_arc_flag: bool,
+        sweep_flag: bool,
+        point: CoordPair,
+        absolute: bool
+    },
+    /// The "closepath" command.
+    ClosePath,
+}
+
+impl ToCss for PathCommand {
+    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+    where
+        W: fmt::Write
+    {
+        use self::PathCommand::*;
+        match *self {
+            Unknown => dest.write_str("X"),
+            ClosePath => dest.write_str("Z"),
+            MoveTo { point, absolute } => {
+                dest.write_char(if absolute { 'M' } else { 'm' })?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            }
+            LineTo { point, absolute } => {
+                dest.write_char(if absolute { 'L' } else { 'l' })?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            }
+            CurveTo { control1, control2, point, absolute } => {
+                dest.write_char(if absolute { 'C' } else { 'c' })?;
+                dest.write_char(' ')?;
+                control1.to_css(dest)?;
+                dest.write_char(' ')?;
+                control2.to_css(dest)?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            },
+            QuadBezierCurveTo { control1, point, absolute } => {
+                dest.write_char(if absolute { 'Q' } else { 'q' })?;
+                dest.write_char(' ')?;
+                control1.to_css(dest)?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            },
+            EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, point, absolute } => {
+                dest.write_char(if absolute { 'A' } else { 'a' })?;
+                dest.write_char(' ')?;
+                rx.to_css(dest)?;
+                dest.write_char(' ')?;
+                ry.to_css(dest)?;
+                dest.write_char(' ')?;
+                angle.to_css(dest)?;
+                dest.write_char(' ')?;
+                (large_arc_flag as i32).to_css(dest)?;
+                dest.write_char(' ')?;
+                (sweep_flag as i32).to_css(dest)?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            },
+            HorizontalLineTo { x, absolute } => {
+                dest.write_char(if absolute { 'H' } else { 'h' })?;
+                dest.write_char(' ')?;
+                x.to_css(dest)
+            },
+            VerticalLineTo { y, absolute } => {
+                dest.write_char(if absolute { 'V' } else { 'v' })?;
+                dest.write_char(' ')?;
+                y.to_css(dest)
+            },
+            SmoothCurveTo { control2, point, absolute } => {
+                dest.write_char(if absolute { 'S' } else { 's' })?;
+                dest.write_char(' ')?;
+                control2.to_css(dest)?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            },
+            SmoothQuadBezierCurveTo { point, absolute } => {
+                dest.write_char(if absolute { 'T' } else { 't' })?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            },
+        }
+    }
+}
+
+/// The path coord type.
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss)]
+#[repr(C)]
+pub struct CoordPair(CSSFloat, CSSFloat);
+
+impl CoordPair {
+    /// Create a CoordPair.
+    #[inline]
+    pub fn new(x: CSSFloat, y: CSSFloat) -> Self {
+        CoordPair(x, y)
+    }
+}
+
+/// Parse a pair of numbers into CoordPair.
+fn parse_coord(iter: &mut Peekable<Chars>) -> Result<CoordPair, ()> {
+    let x = parse_number(iter)?;
+    skip_comma_wsp(iter);
+    let y = parse_number(iter)?;
+    Ok(CoordPair::new(x, y))
+}
+
+/// This is a special version which parses the number for SVG Path. e.g. "M 0.6.5" should be parsed
+/// as MoveTo with a coordinate of ("0.6", ".5"), instead of treating 0.6.5 as a non-valid floating
+/// point number. In other words, the logic here is similar with that of
+/// tokenizer::consume_numeric, which also consumes the number as many as possible, but here the
+/// input is a Peekable and we only accept an integer of a floating point number.
+///
+/// The "number" syntax in https://www.w3.org/TR/SVG/paths.html#PathDataBNF
+fn parse_number(iter: &mut Peekable<Chars>) -> Result<CSSFloat, ()> {
+    // 1. Check optional sign.
+    let sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') {
+        if iter.next().unwrap() == '-' { -1. } else { 1. }
+    } else {
+        1.
+    };
+
+    // 2. Check integer part.
+    let mut integral_part: f64 = 0.;
+    let got_dot = if !iter.peek().map_or(false, |&n: &char| n == '.') {
+        // If the first digit in integer part is neither a dot nor a digit, this is not a number.
+        if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) {
+            return Err(());
+        }
+
+        while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) {
+            integral_part =
+                integral_part * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64;
+        }
+
+        iter.peek().map_or(false, |&n: &char| n == '.')
+    } else {
+        true
+    };
+
+    // 3. Check fractional part.
+    let mut fractional_part: f64 = 0.;
+    if got_dot {
+        // Consume '.'.
+        iter.next();
+        // If the first digit in fractional part is not a digit, this is not a number.
+        if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) {
+            return Err(());
+        }
+
+        let mut factor = 0.1;
+        while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) {
+            fractional_part += iter.next().unwrap().to_digit(10).unwrap() as f64 * factor;
+            factor *= 0.1;
+        }
+    }
+
+    let mut value = sign * (integral_part + fractional_part);
+
+    // 4. Check exp part. The segment name of SVG Path doesn't include 'E' or 'e', so it's ok to
+    //    treat the numbers after 'E' or 'e' are in the exponential part.
+    if iter.peek().map_or(false, |&exp: &char| exp == 'E' || exp == 'e') {
+        // Consume 'E' or 'e'.
+        iter.next();
+        let exp_sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') {
+            if iter.next().unwrap() == '-' { -1. } else { 1. }
+        } else {
+            1.
+        };
+
+        let mut exp: f64 = 0.;
+        while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) {
+            exp = exp * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64;
+        }
+
+        value *= f64::powf(10., exp * exp_sign);
+    }
+
+    if value.is_finite() {
+        Ok(value.min(::std::f32::MAX as f64).max(::std::f32::MIN as f64) as CSSFloat)
+    } else {
+        Err(())
+    }
+}
+
+/// Skip all svg whitespaces, and return true if |iter| hasn't finished.
+#[inline]
+fn skip_wsp(iter: &mut Peekable<Chars>) -> bool {
+    // Note: SVG 1.1 defines the whitespaces as \u{9}, \u{20}, \u{A}, \u{D}.
+    //       However, SVG 2 has one extra whitespace: \u{C}.
+    //       Therefore, we follow the newest spec for the definition of whitespace,
+    //       i.e. \u{9}, \u{20}, \u{A}, \u{C}, \u{D}, by is_ascii_whitespace().
+    while iter.peek().map_or(false, |c: &char| c.is_ascii_whitespace()) {
+        iter.next();
+    }
+    iter.peek().is_some()
+}
+
+/// Skip all svg whitespaces and one comma, and return true if |iter| hasn't finished.
+#[inline]
+fn skip_comma_wsp(iter: &mut Peekable<Chars>) -> bool {
+    if !skip_wsp(iter) {
+        return false;
+    }
+
+    if *iter.peek().unwrap() != ',' {
+        return true;
+    }
+    iter.next();
+
+    skip_wsp(iter)
+}