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 317739 8bcb31d3dfcaae0bbcdc53708a685cc3b4c5c628
parent 317738 a348abb73fe8d6fd95efa65368323108f0dc05c3
child 317740 038ff8933b43546cd77633bf522bc819c6b7b4fe
push id9480
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 17:12:58 +0000
treeherdermozilla-aurora@0d6a91c76a9e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1110460
milestone48.0a1
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, "");