Bug 1110460, part 8 - Support CSS animation of clip-path basic shapes. r=dholbert
authorJonathan Watt <jwatt@jwatt.org>
Mon, 18 Apr 2016 23:04:20 +0100
changeset 331836 8bcb31d3dfcaae0bbcdc53708a685cc3b4c5c628
parent 331835 a348abb73fe8d6fd95efa65368323108f0dc05c3
child 331837 038ff8933b43546cd77633bf522bc819c6b7b4fe
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1110460
milestone48.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 1110460, part 8 - Support CSS animation of clip-path basic shapes. r=dholbert
layout/style/StyleAnimationValue.cpp
layout/style/StyleAnimationValue.h
layout/style/nsCSSPropList.h
layout/style/test/test_transitions_per_property.html
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -829,16 +829,20 @@ StyleAnimationValue::ComputeDistance(nsC
 
         shadow1 = shadow1->mNext;
         shadow2 = shadow2->mNext;
         MOZ_ASSERT(!shadow1 == !shadow2, "lists should be same length");
       }
       aDistance = sqrt(squareDistance);
       return true;
     }
+    // The CSS Shapes spec doesn't define paced animations for shape functions.
+    case eUnit_Shape: {
+      return false;
+    }
     case eUnit_Filter:
       // FIXME: Support paced animations for filter function interpolation.
     case eUnit_Transform: {
       return false;
     }
     case eUnit_BackgroundPosition: {
       const nsCSSValueList *position1 = aStartValue.GetCSSValueListValue();
       const nsCSSValueList *position2 = aEndValue.GetCSSValueListValue();
@@ -1749,16 +1753,34 @@ AddFilterFunction(double aCoeff1, const 
   }
   if (!aList2) {
     return AddFilterFunctionImpl(aCoeff1, aList1, 0, aList1, aResultTail);
   }
 
   return AddFilterFunctionImpl(aCoeff1, aList1, aCoeff2, aList2, aResultTail);
 }
 
+static inline uint32_t
+ShapeArgumentCount(nsCSSKeyword aShapeFunction)
+{
+  switch (aShapeFunction) {
+    case eCSSKeyword_circle:
+      return 2; // radius and center point
+    case eCSSKeyword_polygon:
+      return 2; // fill rule and a list of points
+    case eCSSKeyword_ellipse:
+      return 3; // two radii and center point
+    case eCSSKeyword_inset:
+      return 5; // four edge offsets and a list of corner radii
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown shape type");
+      return 0;
+  }
+}
+
 static void
 AddPositions(double aCoeff1, const nsCSSValue& aPos1,
              double aCoeff2, const nsCSSValue& aPos2,
              nsCSSValue& aResultPos)
 {
   MOZ_ASSERT(aPos1.GetUnit() == eCSSUnit_Array &&
              aPos2.GetUnit() == eCSSUnit_Array,
              "Args should be CSS <position>s, encoded as arrays");
@@ -1869,16 +1891,150 @@ AddCSSValuePairList(nsCSSProperty aPrope
 
   if (aList1 || aList2) {
     return nullptr; // We can't interpolate lists of different lengths
   }
 
   return result;
 }
 
+static already_AddRefed<nsCSSValue::Array>
+AddShapeFunction(nsCSSProperty aProperty,
+                 double aCoeff1, const nsCSSValue::Array* aArray1,
+                 double aCoeff2, const nsCSSValue::Array* aArray2)
+{
+  MOZ_ASSERT(aArray1 && aArray1->Count() == 2, "expected shape function");
+  MOZ_ASSERT(aArray2 && aArray2->Count() == 2, "expected shape function");
+  MOZ_ASSERT(aArray1->Item(0).GetUnit() == eCSSUnit_Function,
+             "expected function");
+  MOZ_ASSERT(aArray2->Item(0).GetUnit() == eCSSUnit_Function,
+             "expected function");
+  MOZ_ASSERT(aArray1->Item(1).GetUnit() == eCSSUnit_Enumerated,
+             "expected geometry-box");
+  MOZ_ASSERT(aArray2->Item(1).GetUnit() == eCSSUnit_Enumerated,
+             "expected geometry-box");
+  MOZ_ASSERT(aArray1->Item(1).GetIntValue() == aArray2->Item(1).GetIntValue(),
+             "expected matching geometry-box values");
+  const nsCSSValue::Array* func1 = aArray1->Item(0).GetArrayValue();
+  const nsCSSValue::Array* func2 = aArray2->Item(0).GetArrayValue();
+  nsCSSKeyword shapeFuncName = func1->Item(0).GetKeywordValue();
+  if (shapeFuncName != func2->Item(0).GetKeywordValue()) {
+    return nullptr; // Can't add two shapes of different types.
+  }
+
+  RefPtr<nsCSSValue::Array> result = nsCSSValue::Array::Create(2);
+
+  nsCSSValue::Array* resultFuncArgs =
+    result->Item(0).InitFunction(shapeFuncName,
+                                 ShapeArgumentCount(shapeFuncName));
+  switch (shapeFuncName) {
+    case eCSSKeyword_ellipse:
+      // Add ellipses' |ry| values (but fail if we encounter an enum):
+      if (!AddCSSValuePixelPercentCalc(CSS_PROPERTY_VALUE_NONNEGATIVE,
+                                       GetCommonUnit(aProperty,
+                                                     func1->Item(2).GetUnit(),
+                                                     func2->Item(2).GetUnit()),
+                                       aCoeff1, func1->Item(2),
+                                       aCoeff2, func2->Item(2),
+                                       resultFuncArgs->Item(2))) {
+        return nullptr;
+      }
+      MOZ_FALLTHROUGH;  // to handle rx and center point
+    case eCSSKeyword_circle: {
+      // Add circles' |r| (or ellipses' |rx|) values:
+      if (!AddCSSValuePixelPercentCalc(CSS_PROPERTY_VALUE_NONNEGATIVE,
+                                       GetCommonUnit(aProperty,
+                                                     func1->Item(1).GetUnit(),
+                                                     func2->Item(1).GetUnit()),
+                                       aCoeff1, func1->Item(1),
+                                       aCoeff2, func2->Item(1),
+                                       resultFuncArgs->Item(1))) {
+        return nullptr;
+      }
+      // Add center points (defined as a <position>).
+      size_t posIndex = shapeFuncName == eCSSKeyword_circle ? 2 : 3;
+      AddPositions(aCoeff1, func1->Item(posIndex),
+                   aCoeff2, func2->Item(posIndex),
+                   resultFuncArgs->Item(posIndex));
+      break;
+    }
+    case eCSSKeyword_polygon: {
+      // Add polygons' corresponding points (if the fill rule matches):
+      int32_t fillRule = func1->Item(1).GetIntValue();
+      if (fillRule != func2->Item(1).GetIntValue()) {
+        return nullptr; // can't interpolate between different fill rules
+      }
+      resultFuncArgs->Item(1).SetIntValue(fillRule, eCSSUnit_Enumerated);
+
+      const nsCSSValuePairList* points1 = func1->Item(2).GetPairListValue();
+      const nsCSSValuePairList* points2 = func2->Item(2).GetPairListValue();
+      UniquePtr<nsCSSValuePairList> resultPoints =
+        AddCSSValuePairList(aProperty, aCoeff1, points1, aCoeff2, points2);
+      if (!resultPoints) {
+        return nullptr;
+      }
+      resultFuncArgs->Item(2).AdoptPairListValue(resultPoints.release());
+      break;
+    }
+    case eCSSKeyword_inset: {
+      MOZ_ASSERT(func1->Count() == 6 && func2->Count() == 6,
+                 "Update for CSSParserImpl::ParseInsetFunction changes");
+      // Items 1-4 are respectively the top, right, bottom and left offsets
+      // from the reference box.
+      for (size_t i = 1; i <= 4; ++i) {
+        if (!AddCSSValuePixelPercentCalc(CSS_PROPERTY_VALUE_NONNEGATIVE,
+                                         GetCommonUnit(aProperty,
+                                                       func1->Item(i).GetUnit(),
+                                                       func2->Item(i).GetUnit()),
+                                         aCoeff1, func1->Item(i),
+                                         aCoeff2, func2->Item(i),
+                                         resultFuncArgs->Item(i))) {
+          return nullptr;
+        }
+      }
+      // Item 5 contains the radii of the rounded corners for the inset
+      // rectangle.
+      MOZ_ASSERT(func1->Item(5).GetUnit() == eCSSUnit_Array &&
+                 func2->Item(5).GetUnit() == eCSSUnit_Array,
+                 "Expected two arrays");
+      const nsCSSValue::Array* radii1 = func1->Item(5).GetArrayValue();
+      const nsCSSValue::Array* radii2 = func2->Item(5).GetArrayValue();
+      MOZ_ASSERT(radii1->Count() == 4 && radii2->Count() == 4);
+      nsCSSValue::Array* resultRadii = nsCSSValue::Array::Create(4);
+      resultFuncArgs->Item(5).SetArrayValue(resultRadii, eCSSUnit_Array);
+      // We use an arbitrary border-radius property here to get the appropriate
+      // restrictions for radii since this is a <border-radius> value.
+      uint32_t restrictions =
+        nsCSSProps::ValueRestrictions(eCSSProperty_border_top_left_radius);
+      for (size_t i = 0; i < 4; ++i) {
+        const nsCSSValuePair& pair1 = radii1->Item(i).GetPairValue();
+        const nsCSSValuePair& pair2 = radii2->Item(i).GetPairValue();
+        UniquePtr<nsCSSValuePair>
+          pairResult(AddCSSValuePair(aProperty, restrictions,
+                                     aCoeff1, &pair1,
+                                     aCoeff2, &pair2));
+        if (!pairResult) {
+          return nullptr;
+        }
+        resultRadii->Item(i).SetPairValue(pairResult.release());
+      }
+      break;
+    }
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown shape type");
+      return nullptr;
+  }
+
+  // set the geometry-box value
+  result->Item(1).SetIntValue(aArray1->Item(1).GetIntValue(),
+                              eCSSUnit_Enumerated);
+
+  return result.forget();
+}
+
 static nsCSSValueList*
 AddTransformLists(double aCoeff1, const nsCSSValueList* aList1,
                   double aCoeff2, const nsCSSValueList* aList2)
 {
   nsAutoPtr<nsCSSValueList> result;
   nsCSSValueList **resultTail = getter_Transfers(result);
 
   do {
@@ -2391,17 +2547,28 @@ StyleAnimationValue::AddWeighted(nsCSSPr
           }
 
           longShadow = longShadow->mNext;
         }
       }
       aResultValue.SetAndAdoptCSSValueListValue(result.forget(), eUnit_Shadow);
       return true;
     }
-
+    case eUnit_Shape: {
+      RefPtr<nsCSSValue::Array> result =
+        AddShapeFunction(aProperty,
+                         aCoeff1, aValue1.GetCSSValueArrayValue(),
+                         aCoeff2, aValue2.GetCSSValueArrayValue());
+      if (!result) {
+        return false;
+      }
+      aResultValue.SetAndAdoptCSSValueArrayValue(result.forget().take(),
+                                                 eUnit_Shape);
+      return true;
+    }
     case eUnit_Filter: {
       const nsCSSValueList *list1 = aValue1.GetCSSValueListValue();
       const nsCSSValueList *list2 = aValue2.GetCSSValueListValue();
 
       nsAutoPtr<nsCSSValueList> result;
       nsCSSValueList **resultTail = getter_Transfers(result);
       while (list1 || list2) {
         MOZ_ASSERT(!*resultTail,
@@ -2890,16 +3057,21 @@ StyleAnimationValue::UncomputeValue(nsCS
         nsCSSValueList* computedList = aComputedValue.GetCSSValueListValue();
         if (computedList) {
           aSpecifiedValue.SetDependentListValue(computedList);
         } else {
           aSpecifiedValue.SetNoneValue();
         }
       }
       break;
+    case eUnit_Shape: {
+      nsCSSValue::Array* computedArray = aComputedValue.GetCSSValueArrayValue();
+      aSpecifiedValue.SetArrayValue(computedArray, eCSSUnit_Array);
+      break;
+    }
     case eUnit_Transform:
       aSpecifiedValue.
         SetSharedListValue(aComputedValue.GetCSSValueSharedListValue());
       break;
     case eUnit_CSSValuePairList:
       aSpecifiedValue.
         SetDependentPairListValue(aComputedValue.GetCSSValuePairListValue());
       break;
@@ -3217,16 +3389,106 @@ ExtractImageLayerSizePairList(const nsSt
         }
         break;
     }
   }
 
   aComputedValue.SetAndAdoptCSSValuePairListValue(result.forget());
 }
 
+static bool
+StyleClipBasicShapeToCSSArray(const nsStyleClipPath& aClipPath,
+                              nsCSSValue::Array* aResult)
+{
+  MOZ_ASSERT(aResult->Count() == 2,
+             "Expected array to be presized for a function and the sizing-box");
+
+  const nsStyleBasicShape* shape = aClipPath.GetBasicShape();
+  nsCSSKeyword functionName = shape->GetShapeTypeName();
+  RefPtr<nsCSSValue::Array> functionArray;
+  switch (shape->GetShapeType()) {
+    case nsStyleBasicShape::Type::eCircle:
+    case nsStyleBasicShape::Type::eEllipse: {
+      const nsTArray<nsStyleCoord>& coords = shape->Coordinates();
+      MOZ_ASSERT(coords.Length() == ShapeArgumentCount(functionName) - 1,
+                 "Unexpected radii count");
+      // The "+1" is for the center point:
+      functionArray = aResult->Item(0).InitFunction(functionName,
+                                                    coords.Length() + 1);
+      for (size_t i = 0; i < coords.Length(); ++i) {
+        if (coords[i].GetUnit() == eStyleUnit_Enumerated) {
+          functionArray->Item(i + 1).SetIntValue(coords[i].GetIntValue(),
+                                                 eCSSUnit_Enumerated);
+        } else if (!StyleCoordToCSSValue(coords[i],
+                                         functionArray->Item(i + 1))) {
+          return false;
+        }
+      }
+      // Set functionArray's last item to the circle or ellipse's center point:
+      SetPositionValue(shape->GetPosition(),
+                       functionArray->Item(functionArray->Count() - 1));
+      break;
+    }
+    case nsStyleBasicShape::Type::ePolygon: {
+      functionArray =
+        aResult->Item(0).InitFunction(functionName,
+                                      ShapeArgumentCount(functionName));
+      functionArray->Item(1).SetIntValue(shape->GetFillRule(),
+                                         eCSSUnit_Enumerated);
+      nsCSSValuePairList* list = functionArray->Item(2).SetPairListValue();
+      const nsTArray<nsStyleCoord>& coords = shape->Coordinates();
+      MOZ_ASSERT((coords.Length() % 2) == 0);
+      for (size_t i = 0; i < coords.Length(); i += 2) {
+        if (i > 0) {
+          list->mNext = new nsCSSValuePairList;
+          list = list->mNext;
+        }
+        if (!StyleCoordToCSSValue(coords[i], list->mXValue) ||
+            !StyleCoordToCSSValue(coords[i + 1], list->mYValue)) {
+          return false;
+        }
+      }
+      break;
+    }
+    case nsStyleBasicShape::Type::eInset: {
+      const nsTArray<nsStyleCoord>& coords = shape->Coordinates();
+      MOZ_ASSERT(coords.Length() == ShapeArgumentCount(functionName) - 1,
+                 "Unexpected offset count");
+      functionArray =
+        aResult->Item(0).InitFunction(functionName, coords.Length() + 1);
+      for (size_t i = 0; i < coords.Length(); ++i) {
+        if (!StyleCoordToCSSValue(coords[i], functionArray->Item(i + 1))) {
+          return false;
+        }
+      }
+      RefPtr<nsCSSValue::Array> radiusArray = nsCSSValue::Array::Create(4);
+      const nsStyleCorners& radii = shape->GetRadius();
+      NS_FOR_CSS_FULL_CORNERS(corner) {
+        auto pair = MakeUnique<nsCSSValuePair>();
+        if (!StyleCoordToCSSValue(radii.Get(NS_FULL_TO_HALF_CORNER(corner, false)),
+                                  pair->mXValue) ||
+            !StyleCoordToCSSValue(radii.Get(NS_FULL_TO_HALF_CORNER(corner, true)),
+                                  pair->mYValue)) {
+              return false;
+            }
+        radiusArray->Item(corner).SetPairValue(pair.release());
+      }
+      // Set the last item in functionArray to the radius array:
+      functionArray->Item(functionArray->Count() - 1).
+                       SetArrayValue(radiusArray, eCSSUnit_Array);
+      break;
+    }
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown shape type");
+      return false;
+  }
+  aResult->Item(1).SetIntValue(aClipPath.GetSizingBox(), eCSSUnit_Enumerated);
+  return true;
+}
+
 bool
 StyleAnimationValue::ExtractComputedValue(nsCSSProperty aProperty,
                                           nsStyleContext* aStyleContext,
                                           StyleAnimationValue& aComputedValue)
 {
   MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
              "bad property");
   const void* styleStruct =
@@ -3555,16 +3817,51 @@ StyleAnimationValue::ExtractComputedValu
 #ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
         case eCSSProperty_mask_size: {
           const nsStyleImageLayers& layers =
             static_cast<const nsStyleSVGReset*>(styleStruct)->mMask;
           ExtractImageLayerSizePairList(layers, aComputedValue);
           break;
         }
 #endif
+
+        case eCSSProperty_clip_path: {
+          const nsStyleSVGReset* svgReset =
+            static_cast<const nsStyleSVGReset*>(styleStruct);
+          const nsStyleClipPath& clipPath = svgReset->mClipPath;
+          const int32_t type = clipPath.GetType();
+
+          if (type == NS_STYLE_CLIP_PATH_URL) {
+            nsIDocument* doc = aStyleContext->PresContext()->Document();
+            RefPtr<nsStringBuffer> uriAsStringBuffer =
+              GetURIAsUtf16StringBuffer(clipPath.GetURL());
+            RefPtr<mozilla::css::URLValue> url =
+              new mozilla::css::URLValue(clipPath.GetURL(),
+                                         uriAsStringBuffer,
+                                         doc->GetDocumentURI(),
+                                         doc->NodePrincipal());
+            auto result = MakeUnique<nsCSSValue>();
+            result->SetURLValue(url);
+            aComputedValue.SetAndAdoptCSSValueValue(result.release(), eUnit_URL);
+          } else if (type == NS_STYLE_CLIP_PATH_BOX) {
+            aComputedValue.SetIntValue(clipPath.GetSizingBox(), eUnit_Enumerated);
+          } else if (type == NS_STYLE_CLIP_PATH_SHAPE) {
+            RefPtr<nsCSSValue::Array> result = nsCSSValue::Array::Create(2);
+            if (!StyleClipBasicShapeToCSSArray(clipPath, result)) {
+              return false;
+            }
+            aComputedValue.SetAndAdoptCSSValueArrayValue(result.forget().take(),
+                                                         eUnit_Shape);
+          } else {
+            MOZ_ASSERT(type == NS_STYLE_CLIP_PATH_NONE, "unknown type");
+            aComputedValue.SetNoneValue();
+          }
+          break;
+        }
+
         case eCSSProperty_filter: {
           const nsStyleEffects* effects =
             static_cast<const nsStyleEffects*>(styleStruct);
           const nsTArray<nsStyleFilter>& filters = effects->mFilters;
           nsAutoPtr<nsCSSValueList> result;
           nsCSSValueList **resultTail = getter_Transfers(result);
           for (uint32_t i = 0; i < filters.Length(); ++i) {
             nsCSSValueList *item = new nsCSSValueList;
@@ -3924,16 +4221,22 @@ StyleAnimationValue::operator=(const Sty
                  aOther.mValue.mCSSValueList,
                  "value lists other than shadows and filters may not be null");
       if (aOther.mValue.mCSSValueList) {
         mValue.mCSSValueList = aOther.mValue.mCSSValueList->Clone();
       } else {
         mValue.mCSSValueList = nullptr;
       }
       break;
+    case eUnit_Shape:
+      MOZ_ASSERT(aOther.mValue.mCSSValueArray,
+                 "value arrays may not be null");
+      mValue.mCSSValueArray = aOther.mValue.mCSSValueArray;
+      mValue.mCSSValueArray->AddRef();
+      break;
     case eUnit_Transform:
       mValue.mCSSValueSharedList = aOther.mValue.mCSSValueSharedList;
       mValue.mCSSValueSharedList->AddRef();
       break;
     case eUnit_CSSValuePairList:
       MOZ_ASSERT(aOther.mValue.mCSSValuePairList,
                  "value pair lists may not be null");
       mValue.mCSSValuePairList = aOther.mValue.mCSSValuePairList->Clone();
@@ -4059,16 +4362,28 @@ StyleAnimationValue::SetAndAdoptCSSRectV
   FreeValue();
   MOZ_ASSERT(IsCSSRectUnit(aUnit), "bad unit");
   MOZ_ASSERT(aRect != nullptr, "value pairs may not be null");
   mUnit = aUnit;
   mValue.mCSSRect = aRect; // take ownership
 }
 
 void
+StyleAnimationValue::SetAndAdoptCSSValueArrayValue(nsCSSValue::Array* aValue,
+                                                   Unit aUnit)
+{
+  FreeValue();
+  MOZ_ASSERT(IsCSSValueArrayUnit(aUnit), "bad unit");
+  MOZ_ASSERT(aValue != nullptr,
+             "not currently expecting any arrays to be null");
+  mUnit = aUnit;
+  mValue.mCSSValueArray = aValue; // take ownership
+}
+
+void
 StyleAnimationValue::SetAndAdoptCSSValueListValue(nsCSSValueList *aValueList,
                                                   Unit aUnit)
 {
   FreeValue();
   MOZ_ASSERT(IsCSSValueListUnit(aUnit), "bad unit");
   MOZ_ASSERT(aUnit == eUnit_Shadow || aUnit == eUnit_Filter ||
              aValueList != nullptr,
              "value lists other than shadows and filters may not be null");
@@ -4155,16 +4470,18 @@ StyleAnimationValue::operator==(const St
     case eUnit_CSSRect:
       return *mValue.mCSSRect == *aOther.mValue.mCSSRect;
     case eUnit_Dasharray:
     case eUnit_Shadow:
     case eUnit_Filter:
     case eUnit_BackgroundPosition:
       return nsCSSValueList::Equal(mValue.mCSSValueList,
                                    aOther.mValue.mCSSValueList);
+    case eUnit_Shape:
+      return *mValue.mCSSValueArray == *aOther.mValue.mCSSValueArray;
     case eUnit_Transform:
       return *mValue.mCSSValueSharedList == *aOther.mValue.mCSSValueSharedList;
     case eUnit_CSSValuePairList:
       return nsCSSValuePairList::Equal(mValue.mCSSValuePairList,
                                        aOther.mValue.mCSSValuePairList);
     case eUnit_UnparsedString:
       return (NS_strcmp(GetStringBufferValue(),
                         aOther.GetStringBufferValue()) == 0);
--- a/layout/style/StyleAnimationValue.h
+++ b/layout/style/StyleAnimationValue.h
@@ -276,16 +276,17 @@ public:
     eUnit_ObjectPosition, // nsCSSValue* (never null), always with a
                           // 4-entry nsCSSValue::Array
     eUnit_URL, // nsCSSValue* (never null), always with a css::URLValue
     eUnit_CSSValuePair, // nsCSSValuePair* (never null)
     eUnit_CSSValueTriplet, // nsCSSValueTriplet* (never null)
     eUnit_CSSRect, // nsCSSRect* (never null)
     eUnit_Dasharray, // nsCSSValueList* (never null)
     eUnit_Shadow, // nsCSSValueList* (may be null)
+    eUnit_Shape,  // nsCSSValue::Array* (never null)
     eUnit_Filter, // nsCSSValueList* (may be null)
     eUnit_Transform, // nsCSSValueList* (never null)
     eUnit_BackgroundPosition, // nsCSSValueList* (never null)
     eUnit_CSSValuePairList, // nsCSSValuePairList* (never null)
     eUnit_UnparsedString // nsStringBuffer* (never null)
   };
 
 private:
@@ -294,16 +295,17 @@ private:
     int32_t mInt;
     nscoord mCoord;
     float mFloat;
     nscolor mColor;
     nsCSSValue* mCSSValue;
     nsCSSValuePair* mCSSValuePair;
     nsCSSValueTriplet* mCSSValueTriplet;
     nsCSSRect* mCSSRect;
+    nsCSSValue::Array* mCSSValueArray;
     nsCSSValueList* mCSSValueList;
     nsCSSValueSharedList* mCSSValueSharedList;
     nsCSSValuePairList* mCSSValuePairList;
     nsStringBuffer* mString;
   } mValue;
 
 public:
   Unit GetUnit() const {
@@ -348,16 +350,20 @@ public:
   nsCSSValueTriplet* GetCSSValueTripletValue() const {
     NS_ASSERTION(IsCSSValueTripletUnit(mUnit), "unit mismatch");
     return mValue.mCSSValueTriplet;
   }
   nsCSSRect* GetCSSRectValue() const {
     NS_ASSERTION(IsCSSRectUnit(mUnit), "unit mismatch");
     return mValue.mCSSRect;
   }
+  nsCSSValue::Array* GetCSSValueArrayValue() const {
+    NS_ASSERTION(IsCSSValueArrayUnit(mUnit), "unit mismatch");
+    return mValue.mCSSValueArray;
+  }
   nsCSSValueList* GetCSSValueListValue() const {
     NS_ASSERTION(IsCSSValueListUnit(mUnit), "unit mismatch");
     return mValue.mCSSValueList;
   }
   nsCSSValueSharedList* GetCSSValueSharedListValue() const {
     NS_ASSERTION(IsCSSValueSharedListValue(mUnit), "unit mismatch");
     return mValue.mCSSValueSharedList;
   }
@@ -430,16 +436,17 @@ public:
   void SetUnparsedStringValue(const nsString& aString);
 
   // These setters take ownership of |aValue|, and are therefore named
   // "SetAndAdopt*".
   void SetAndAdoptCSSValueValue(nsCSSValue *aValue, Unit aUnit);
   void SetAndAdoptCSSValuePairValue(nsCSSValuePair *aValue, Unit aUnit);
   void SetAndAdoptCSSValueTripletValue(nsCSSValueTriplet *aValue, Unit aUnit);
   void SetAndAdoptCSSRectValue(nsCSSRect *aValue, Unit aUnit);
+  void SetAndAdoptCSSValueArrayValue(nsCSSValue::Array* aValue, Unit aUnit);
   void SetAndAdoptCSSValueListValue(nsCSSValueList *aValue, Unit aUnit);
   void SetAndAdoptCSSValuePairListValue(nsCSSValuePairList *aValue);
 
   void SetTransformValue(nsCSSValueSharedList* aList);
 
   StyleAnimationValue& operator=(const StyleAnimationValue& aOther);
 
   bool operator==(const StyleAnimationValue& aOther) const;
@@ -466,16 +473,19 @@ private:
     return aUnit == eUnit_CSSValuePair;
   }
   static bool IsCSSValueTripletUnit(Unit aUnit) {
     return aUnit == eUnit_CSSValueTriplet;
   }
   static bool IsCSSRectUnit(Unit aUnit) {
     return aUnit == eUnit_CSSRect;
   }
+  static bool IsCSSValueArrayUnit(Unit aUnit) {
+    return aUnit == eUnit_Shape;
+  }
   static bool IsCSSValueListUnit(Unit aUnit) {
     return aUnit == eUnit_Dasharray || aUnit == eUnit_Filter ||
            aUnit == eUnit_Shadow ||
            aUnit == eUnit_BackgroundPosition;
   }
   static bool IsCSSValueSharedListValue(Unit aUnit) {
     return aUnit == eUnit_Transform;
   }
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -1416,17 +1416,17 @@ CSS_PROP_SVGRESET(
     clip_path,
     ClipPath,
     CSS_PROPERTY_PARSE_FUNCTION |
         CSS_PROPERTY_CREATES_STACKING_CONTEXT,
     "",
     0,
     nullptr,
     CSS_PROP_NO_OFFSET,
-    eStyleAnimType_None)
+    eStyleAnimType_Custom)
 CSS_PROP_SVG(
     clip-rule,
     clip_rule,
     ClipRule,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kFillRuleKTable,
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -110,16 +110,17 @@ var supported_properties = {
     "border-top-color": [ test_color_transition,
                           test_border_color_transition ],
     "border-top-width": [ test_length_transition,
                            test_length_clamped ],
     "bottom": [ test_length_transition, test_percent_transition,
                 test_length_percent_calc_transition,
                 test_length_unclamped, test_percent_unclamped ],
     "clip": [ test_rect_transition ],
+    "clip-path": [ test_clip_path_transition ],
     "color": [ test_color_transition ],
     "fill": [ test_color_transition ],
     "fill-opacity" : [ test_float_zeroToOne_transition,
                        // opacity is clamped in computed style
                        // (not parsing/interpolation)
                        test_float_zeroToOne_clamped ],
     "filter" : [ test_filter_transition ],
     "flex-basis": [ test_length_transition, test_percent_transition,
@@ -581,16 +582,137 @@ var transformTests = [
     end: 'skewX(-45deg) rotate(90deg)',
     expected_uncomputed: 'skewX(22.5deg) rotate(90deg)' },
   { start: 'skewX(-60deg) rotate(90deg) translate(0)',
     end: 'skewX(60deg) rotate(90deg)',
     expected: computeMatrix('rotate(120deg) skewX(' + Math.atan(Math.tan(Math.PI * 60/180) / 2) + 'rad) scale(2, 0.5)'),
     round_error_ok: true },
 ];
 
+var clipPathTests = [
+  { start: "none", end: "none",
+    expected: ["none"] },
+  // none to shape
+  { start: "none",
+    end: "circle(500px at 500px 500px) border-box",
+    expected: ["circle", ["500px", "at", "500px", "500px"], "border-box"]
+  },
+  { start: "none",
+    end: "ellipse(500px 500px at 500px 500px) border-box",
+    expected: ["ellipse", ["500px", "500px", "at", "500px", "500px"], "border-box"]
+  },
+  { start: "none",
+    end: "polygon(evenodd, 500px 500px, 500px 500px) border-box",
+    expected: ["polygon", ["evenodd", "500px", "500px", "500px", "500px"], "border-box"]
+  },
+  { start: "none",
+    end: "inset(500px 500px 500px 500px round 500px 500px) border-box",
+    expected: ["inset", ["500px", "round", "500px"], "border-box"]
+  },
+  // matching functions
+  { start: "circle(100px)", end: "circle(500px)",
+    expected: ["circle", ["200px", "at", "50%", "50%"]] },
+  { start: "ellipse(100px 100px)", end: "ellipse(500px 500px)",
+    expected: ["ellipse", ["200px", "200px", "at", "50%", "50%"]] },
+  { start: "circle(100px at 100px 100px) border-box",
+    end: "circle(500px at 500px 500px) border-box",
+    expected: ["circle", ["200px", "at", "200px", "200px"]]
+  },
+  { start: "ellipse(100px 100px at 100px 100px) border-box",
+    end: "ellipse(500px 500px at 500px 500px) border-box",
+    expected: ["ellipse", ["200px", "200px", "at", "200px", "200px"]]
+  },
+  { start: "polygon(evenodd, 100px 100px, 100px 100px) border-box",
+    end: "polygon(evenodd, 500px 500px, 500px 500px) border-box",
+    expected: ["polygon", ["evenodd", "200px", "200px", "200px", "200px"]]
+  },
+  { start: "inset(100px 100px 100px 100px round 100px 100px) border-box",
+    end: "inset(500px 500px 500px 500px round 500px 500px) border-box",
+    expected: ["inset", ["200px", "round", "200px"]]
+  },
+  // matching functions percentage
+  { start: "circle(100%)", end: "circle(500%)",
+    expected: ["circle", ["200%", "at", "50%", "50%"]] },
+  { start: "ellipse(100% 100%)", end: "ellipse(500% 500%)",
+    expected: ["ellipse", ["200%", "200%", "at", "50%", "50%"]] },
+  { start: "circle(100% at 100% 100%) border-box",
+    end: "circle(500% at 500% 500%) border-box",
+    expected: ["circle", ["200%", "at", "200%", "200%"]]
+  },
+  { start: "ellipse(100% 100% at 100% 100%) border-box",
+    end: "ellipse(500% 500% at 500% 500%) border-box",
+    expected: ["ellipse", ["200%", "200%", "at", "200%", "200%"]]
+  },
+  { start: "polygon(evenodd, 100% 100%, 100% 100%) border-box",
+    end: "polygon(evenodd, 500% 500%, 500% 500%) border-box",
+    expected: ["polygon", ["evenodd", "200%", "200%", "200%", "200%"]]
+  },
+  { start: "inset(100% 100% 100% 100% round 100% 100%) border-box",
+    end: "inset(500% 500% 500% 500% round 500% 500%) border-box",
+    expected: ["inset", ["200%", "round", "200%"]] },
+  // no interpolation for keywords
+  { start: "circle()", end: "circle(50px)",
+    expected: ["circle", ["50px", "at", "50%", "50%"]] },
+  { start: "circle(closest-side)", end: "circle(500px)",
+    expected: ["circle", ["500px", "at", "50%", "50%"]] },
+  { start: "circle(farthest-side)", end: "circle(500px)",
+    expected: ["circle", ["500px", "at", "50%", "50%"]] },
+  { start: "circle(500px)", end: "circle(farthest-side)",
+    expected: ["circle", ["farthest-side", "at", "50%", "50%"]]},
+  { start: "circle(500px)", end: "circle(closest-side)",
+    expected: ["circle", ["closest-side", "at", "50%", "50%"]]},
+  { start: "ellipse()", end: "ellipse(50px 50px)",
+    expected: ["ellipse", ["50px", "50px", "at", "50%", "50%"]] },
+  { start: "ellipse(closest-side closest-side)", end: "ellipse(500px 500px)",
+    expected: ["ellipse", ["500px", "500px", "at", "50%", "50%"]] },
+  { start: "ellipse(farthest-side closest-side)", end: "ellipse(500px 500px)",
+    expected: ["ellipse", ["500px", "500px", "at", "50%", "50%"]] },
+  { start: "ellipse(farthest-side farthest-side)", end: "ellipse(500px 500px)",
+    expected: ["ellipse", ["500px", "500px", "at", "50%", "50%"]] },
+  { start: "ellipse(500px 500px)", end: "ellipse(farthest-side farthest-side)",
+    expected: ["ellipse", ["farthest-side", "farthest-side", "at", "50%", "50%"]] },
+  { start: "ellipse(500px 500px)", end: "ellipse(closest-side closest-side)",
+    expected: ["ellipse", ["closest-side", "closest-side", "at", "50%", "50%"]] },
+  // mismatching boxes
+  { start: "circle(100px at 100px 100px) border-box",
+    end: "circle(500px at 500px 500px) content-box",
+    expected: ["circle", ["500px", "at", "500px", "500px"], "content-box"]
+  },
+  { start: "ellipse(100px 100px at 100px 100px) border-box",
+    end: "ellipse(500px 500px at 500px 500px) content-box",
+    expected: ["ellipse", ["500px", "500px", "at", "500px", "500px"], "content-box"]
+  },
+  { start: "polygon(evenodd, 100px 100px, 100px 100px) border-box",
+    end: "polygon(evenodd, 500px 500px, 500px 500px) content-box",
+    expected: ["polygon", ["evenodd", "500px", "500px", "500px", "500px"], "content-box"]
+  },
+  { start: "inset(100px 100px 100px 100px round 100px 100px) border-box",
+    end: "inset(500px 500px 500px 500px round 500px 500px) content-box",
+    expected: ["inset", ["500px", "round", "500px"], "content-box"]
+  },
+  // mismatching functions
+  { start: "circle(100px at 100px 100px) border-box",
+    end: "ellipse(500px 500px at 500px 500px) border-box",
+    expected: ["ellipse", ["500px", "500px", "at", "500px", "500px"], "border-box"]
+  },
+  { start: "inset(0px round 20px)", end: "ellipse(500px 500px)",
+    expected: ["ellipse", ["500px", "500px", "at", "50%", "50%"]]
+  },
+  // shape to reference box
+  { start: "circle(20px)", end: "content-box", expected: ["content-box"] },
+  { start: "content-box", end: "circle(20px)", expected: ["circle", ["20px", "at", "50%", "50%"]] },
+  // url to shape
+  { start: "circle(20px)", end: "url('#a')", expected: ["url", ["\""+document.URL+"#a\""]] },
+  { start: "url('#a')", end: "circle(20px)", expected: ["circle", ["20px", "at", "50%", "50%"]] },
+  // url to none
+  { start: "none", end: "url('#a')", expected: ["url", ["\""+document.URL+"#a\""]] },
+  { start: "url('#a')", end: "none", expected: ["none"] },
+
+];
+
 var filterTests = [
   { start: "none", end: "none",
     expected: ["none"] },
   // function from none (number/length)
   { start: "none", end: "brightness(0.5)",
     expected: ["brightness", 0.875] },
   { start: "none", end: "contrast(0.5)",
     expected: ["contrast", 0.875] },
@@ -1187,27 +1309,98 @@ function get_color_from_shorthand_value(
 function test_color_shorthand_transition(prop) {
   test_color_transition(prop, get_color_from_shorthand_value, true);
 }
 
 function test_border_color_shorthand_transition(prop) {
   test_border_color_transition(prop, get_color_from_shorthand_value, true);
 }
 
+function test_clip_path_equals(computedValStr, expectedList)
+{
+  // Check simple case "none"
+  if (computedValStr == "none" && computedValStr == expectedList[0]) {
+    return true;
+  }
+  var start = String(computedValStr);
+
+  var regBox = /\s*(content\-box|padding\-box|border\-box|margin\-box|view\-box|stroke\-box|fill\-box)/
+  var matches = computedValStr.split(regBox);
+  var expectRefBox = typeof expectedList[expectedList.length - 1] == "string" &&
+                     expectedList[expectedList.length - 1].match(regBox) !== null;
+
+  // Found a reference box? Format: "shape()" or "shape() reference-box"
+  if (matches.length > 1) {
+    // Do we expect a reference box?
+    if (!expectRefBox) {
+      ok(false, "unexpected reference box found");
+    }
+    matches.pop();
+    is(matches.pop(), expectedList.pop(), "Reference boxes should match");
+  } else {
+    // No reference box found. Did we expect one?
+    if (expectRefBox) {
+      ok(false, "expected reference box");
+      return false;
+    }
+  }
+  computedValStr = matches[0];
+  if (expectedList.length == 0) {
+    if (computedValStr == "") {
+      return true;
+    } else {
+      ok(false, "expected clip-path shape");
+      return false;
+    }
+  }
+
+  // The regular expression does not filter out the last parenthesis.
+  // Remove last character for now.
+  is(computedValStr.substring(computedValStr.length - 1, computedValStr.length),
+                              ')', "Function should have close-paren");
+  computedValStr = computedValStr.substring(0, computedValStr.length - 1);
+
+  var regShape = /\)*\s*(circle|ellipse|polygon|inset|url)\(/
+  matches = computedValStr.split(regShape);
+  // First item must be empty. All other items are of functionName, functionValue.
+  if (!matches || matches.shift() != "") {
+    ok(false, "invalid value or unknown shape function");
+    return false;
+  }
+
+  // Split arguments at commas or whitspeaces.
+  funcParameters = matches[1].split(/\s*,\s*|\s+/);
+
+  // Check argument length.
+  if (funcParameters.length != expectedList[1].length) {
+    ok(false, "function parameters mismatch");
+    return false;
+  }
+
+  // Check argument values.
+  for (var i = 0; i < funcParameters.length; i++) {
+    if (funcParameters[i] != expectedList[1][i]) {
+      ok(false, "function parameters mismatch");
+      return false;
+    }
+  }
+  return true;
+}
+
 function filter_function_list_equals(computedValStr, expectedList)
 {
   // Check simple case "none"
   if (computedValStr == "none" && computedValStr == expectedList[0]) {
     return true;
   }
 
   // The regular expression does not filter out the last parenthesis.
   // Remove last character for now.
   is(computedValStr.substring(computedValStr.length - 1, computedValStr.length),
-                              ')', "Check if last character is close-paren");
+                              ')', "Last character should be close-paren");
   computedValStr = computedValStr.substring(0, computedValStr.length - 1);
 
   var reg = /\)*\s*(blur|brightness|contrast|grayscale|hue\-rotate|invert|opacity|saturate|sepia|drop\-shadow|url)\(/
   var matches = computedValStr.split(reg);
   // First item must be empty. All other items are of functionName, functionValue.
   if (!matches || matches.shift() != "") {
     ok(false, "computed style of 'filter' isn't in the format we expect");
     return false;
@@ -1247,16 +1440,34 @@ function filter_function_list_equals(com
     if (isNaN(functionValue) ||
       Math.abs(parseFloat(functionValue) - expected) > tolerance) {
       return false;
     }
   }
   return true;
 }
 
+function test_clip_path_transition(prop) {
+  if (!SpecialPowers.getBoolPref("layout.css.clip-path-shapes.enabled")) {
+    return;
+  }
+  for (var i in clipPathTests) {
+    var test = clipPathTests[i];
+    div.style.setProperty("transition-property", "none", "");
+    div.style.setProperty(prop, test.start, "");
+    cs.getPropertyValue(prop);
+    div.style.setProperty("transition-property", prop, "");
+    div.style.setProperty(prop, test.end, "");
+    var actual = cs.getPropertyValue(prop);
+    ok(test_clip_path_equals(actual, test.expected),
+       "Clip-path property is " + actual + " expected values of " +
+       test.expected);
+  }
+}
+
 function test_filter_transition(prop) {
   if (!SpecialPowers.getBoolPref("layout.css.filters.enabled")) {
     return;
   }
   for (var i in filterTests) {
     var test = filterTests[i];
     div.style.setProperty("transition-property", "none", "");
     div.style.setProperty(prop, test.start, "");