Allow calc() as an intermediate common animation unit for background-position and background-size. (Bug 520234) r=bzbarsky a=blocking2.0:beta7
authorL. David Baron <dbaron@dbaron.org>
Wed, 15 Sep 2010 08:11:26 -0700
changeset 53925 91d20b5e47d82ce1ae421276823ac3ef0731e2dc
parent 53924 576e1b23a27b50442c4e0c8e18c2af6c7ee1b455
child 53926 6b4bc4c7d2b5b5aeebb806d9495be52a80ba069d
push id15739
push userdbaron@mozilla.com
push dateWed, 15 Sep 2010 15:12:15 +0000
treeherdermozilla-central@cfa340639ce6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky, blocking2
bugs520234
milestone2.0b7pre
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
Allow calc() as an intermediate common animation unit for background-position and background-size. (Bug 520234) r=bzbarsky a=blocking2.0:beta7
layout/style/nsStyleAnimation.cpp
layout/style/test/test_transitions_per_property.html
--- a/layout/style/nsStyleAnimation.cpp
+++ b/layout/style/nsStyleAnimation.cpp
@@ -88,16 +88,39 @@ GetCommonUnit(nsCSSProperty aProperty,
       // We can use calc() as the common unit.
       return nsStyleAnimation::eUnit_Calc;
     }
     return nsStyleAnimation::eUnit_Null;
   }
   return aFirstUnit;
 }
 
+static
+nsCSSUnit
+GetCommonUnit(nsCSSProperty aProperty,
+              nsCSSUnit aFirstUnit,
+              nsCSSUnit aSecondUnit)
+{
+  if (aFirstUnit != aSecondUnit) {
+    if (nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_STORES_CALC) &&
+        (aFirstUnit == eCSSUnit_Pixel ||
+         aFirstUnit == eCSSUnit_Percent ||
+         aFirstUnit == eCSSUnit_Calc) &&
+        (aSecondUnit == eCSSUnit_Pixel ||
+         aSecondUnit == eCSSUnit_Percent ||
+         aSecondUnit == eCSSUnit_Calc)) {
+      // We can use calc() as the common unit.
+      return eCSSUnit_Calc;
+    }
+    return eCSSUnit_Null;
+  }
+  return aFirstUnit;
+}
+
+
 // Greatest Common Divisor
 static PRUint32
 gcd(PRUint32 a, PRUint32 b)
 {
   // Euclid's algorithm; O(N) in the worst case.  (There are better
   // ways, but we don't need them for stroke-dasharray animation.)
   NS_ABORT_IF_FALSE(a > 0 && b > 0, "positive numbers expected");
 
@@ -128,16 +151,47 @@ nscoordToCSSValue(nscoord aCoord, nsCSSV
 }
 
 // Like nsStyleCoord::Calc, but with length in float pixels instead of nscoord.
 struct CalcValue {
   float mLength, mPercent;
   PRBool mHasPercent;
 };
 
+// Requires a canonical calc() value that we generated.
+static CalcValue
+ExtractCalcValueInternal(const nsCSSValue& aValue)
+{
+  NS_ABORT_IF_FALSE(aValue.GetUnit() == eCSSUnit_Calc, "unexpected unit");
+  nsCSSValue::Array *arr = aValue.GetArrayValue();
+  NS_ABORT_IF_FALSE(arr->Count() == 1, "unexpected length");
+
+  const nsCSSValue &topval = arr->Item(0);
+  CalcValue result;
+  if (topval.GetUnit() == eCSSUnit_Pixel) {
+    result.mLength = topval.GetFloatValue();
+    result.mPercent = 0.0f;
+    result.mHasPercent = PR_FALSE;
+  } else {
+    NS_ABORT_IF_FALSE(topval.GetUnit() == eCSSUnit_Calc_Plus,
+                      "unexpected unit");
+    nsCSSValue::Array *arr2 = topval.GetArrayValue();
+    const nsCSSValue &len = arr2->Item(0);
+    const nsCSSValue &pct = arr2->Item(1);
+    NS_ABORT_IF_FALSE(len.GetUnit() == eCSSUnit_Pixel, "unexpected unit");
+    NS_ABORT_IF_FALSE(pct.GetUnit() == eCSSUnit_Percent, "unexpected unit");
+    result.mLength = len.GetFloatValue();
+    result.mPercent = pct.GetPercentValue();
+    result.mHasPercent = PR_TRUE;
+  }
+
+  return result;
+}
+
+// Requires a canonical calc() value that we generated.
 static CalcValue
 ExtractCalcValue(const nsStyleAnimation::Value& aValue)
 {
   CalcValue result;
   if (aValue.GetUnit() == nsStyleAnimation::eUnit_Coord) {
     result.mLength =
       nsPresContext::AppUnitsToFloatCSSPixels(aValue.GetCoordValue());
     result.mPercent = 0.0f;
@@ -148,39 +202,36 @@ ExtractCalcValue(const nsStyleAnimation:
     result.mLength = 0.0f;
     result.mPercent = aValue.GetPercentValue();
     result.mHasPercent = PR_TRUE;
     return result;
   }
   NS_ABORT_IF_FALSE(aValue.GetUnit() == nsStyleAnimation::eUnit_Calc,
                     "unexpected unit");
   nsCSSValue *val = aValue.GetCSSValueValue();
-  NS_ABORT_IF_FALSE(val->GetUnit() == eCSSUnit_Calc, "unexpected unit");
-  nsCSSValue::Array *arr = val->GetArrayValue();
-  NS_ABORT_IF_FALSE(arr->Count() == 1, "unexpected length");
+  return ExtractCalcValueInternal(*val);
+}
 
-  const nsCSSValue &topval = arr->Item(0);
-  if (topval.GetUnit() == eCSSUnit_Pixel) {
-    result.mLength = topval.GetFloatValue();
+static CalcValue
+ExtractCalcValue(const nsCSSValue& aValue)
+{
+  CalcValue result;
+  if (aValue.GetUnit() == eCSSUnit_Pixel) {
+    result.mLength = aValue.GetFloatValue();
     result.mPercent = 0.0f;
     result.mHasPercent = PR_FALSE;
-  } else {
-    NS_ABORT_IF_FALSE(topval.GetUnit() == eCSSUnit_Calc_Plus,
-                      "unexpected unit");
-    nsCSSValue::Array *arr2 = topval.GetArrayValue();
-    const nsCSSValue &len = arr2->Item(0);
-    const nsCSSValue &pct = arr2->Item(1);
-    NS_ABORT_IF_FALSE(len.GetUnit() == eCSSUnit_Pixel, "unexpected unit");
-    NS_ABORT_IF_FALSE(pct.GetUnit() == eCSSUnit_Percent, "unexpected unit");
-    result.mLength = len.GetFloatValue();
-    result.mPercent = pct.GetPercentValue();
+    return result;
+  }
+  if (aValue.GetUnit() == eCSSUnit_Percent) {
+    result.mLength = 0.0f;
+    result.mPercent = aValue.GetPercentValue();
     result.mHasPercent = PR_TRUE;
+    return result;
   }
-
-  return result;
+  return ExtractCalcValueInternal(aValue);
 }
 
 static bool
 SetCalcValue(const nsStyleCoord::Calc* aCalc, nsCSSValue& aValue)
 {
   nsRefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(1);
   if (!arr)
     return false;
@@ -306,46 +357,58 @@ nsStyleAnimation::ComputeDistance(nsCSSP
       float difflen = v2.mLength - v1.mLength;
       float diffpct = v2.mPercent - v1.mPercent;
       aDistance = sqrt(difflen * difflen + diffpct * diffpct);
       return PR_TRUE;
     }
     case eUnit_CSSValuePair: {
       const nsCSSValuePair *pair1 = aStartValue.GetCSSValuePairValue();
       const nsCSSValuePair *pair2 = aEndValue.GetCSSValuePairValue();
-      if (pair1->mXValue.GetUnit() != pair2->mXValue.GetUnit() ||
-          pair1->mYValue.GetUnit() != pair2->mYValue.GetUnit()) {
-        // At least until we have calc()
+      nsCSSUnit unit[2];
+      unit[0] = GetCommonUnit(aProperty, pair1->mXValue.GetUnit(),
+                              pair2->mXValue.GetUnit());
+      unit[1] = GetCommonUnit(aProperty, pair1->mYValue.GetUnit(),
+                              pair2->mYValue.GetUnit());
+      if (unit[0] == eCSSUnit_Null || unit[1] == eCSSUnit_Null) {
         return PR_FALSE;
       }
 
       double squareDistance = 0.0;
-      static nsCSSValue nsCSSValuePair::* const pairValues[] = {
+      static nsCSSValue nsCSSValuePair::* const pairValues[2] = {
         &nsCSSValuePair::mXValue, &nsCSSValuePair::mYValue
       };
-      for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(pairValues); ++i) {
+      for (PRUint32 i = 0; i < 2; ++i) {
         nsCSSValue nsCSSValuePair::*member = pairValues[i];
-        NS_ABORT_IF_FALSE((pair1->*member).GetUnit() ==
-                            (pair2->*member).GetUnit(),
-                          "should have returned above");
-        double diff;
-        switch ((pair1->*member).GetUnit()) {
-          case eCSSUnit_Pixel:
-            diff = (pair1->*member).GetFloatValue() -
-                   (pair2->*member).GetFloatValue();
+        double diffsquared;
+        switch (unit[i]) {
+          case eCSSUnit_Pixel: {
+            float diff = (pair1->*member).GetFloatValue() -
+                         (pair2->*member).GetFloatValue();
+            diffsquared = diff * diff;
             break;
-          case eCSSUnit_Percent:
-            diff = (pair1->*member).GetPercentValue() -
-                   (pair2->*member).GetPercentValue();
+          }
+          case eCSSUnit_Percent: {
+            float diff = (pair1->*member).GetPercentValue() -
+                         (pair2->*member).GetPercentValue();
+            diffsquared = diff * diff;
             break;
+          }
+          case eCSSUnit_Calc: {
+            CalcValue v1 = ExtractCalcValue(pair1->*member);
+            CalcValue v2 = ExtractCalcValue(pair2->*member);
+            float difflen = v2.mLength - v1.mLength;
+            float diffpct = v2.mPercent - v1.mPercent;
+            diffsquared = difflen * difflen + diffpct * diffpct;
+            break;
+          }
           default:
             NS_ABORT_IF_FALSE(PR_FALSE, "unexpected unit");
             return PR_FALSE;
         }
-        squareDistance += diff * diff;
+        squareDistance += diffsquared;
       }
 
       aDistance = sqrt(squareDistance);
       return PR_TRUE;
     }
     case eUnit_CSSRect: {
       const nsCSSRect *rect1 = aStartValue.GetCSSRectValue();
       const nsCSSRect *rect2 = aEndValue.GetCSSRectValue();
@@ -549,34 +612,48 @@ nsStyleAnimation::ComputeDistance(nsCSSP
       do {
         static nsCSSValue nsCSSValuePairList::* const pairListValues[] = {
           &nsCSSValuePairList::mXValue,
           &nsCSSValuePairList::mYValue,
         };
         for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(pairListValues); ++i) {
           const nsCSSValue &v1 = list1->*(pairListValues[i]);
           const nsCSSValue &v2 = list2->*(pairListValues[i]);
-          if (v1.GetUnit() != v2.GetUnit()) {
+          nsCSSUnit unit =
+            GetCommonUnit(aProperty, v1.GetUnit(), v2.GetUnit());
+          if (unit == eCSSUnit_Null) {
             return PR_FALSE;
           }
-          double diff = 0.0;
-          switch (v1.GetUnit()) {
-            case eCSSUnit_Pixel:
-              diff = v1.GetFloatValue() - v2.GetFloatValue();
+          double diffsquared = 0.0;
+          switch (unit) {
+            case eCSSUnit_Pixel: {
+              float diff = v1.GetFloatValue() - v2.GetFloatValue();
+              diffsquared = diff * diff;
               break;
-            case eCSSUnit_Percent:
-              diff = v1.GetPercentValue() - v2.GetPercentValue();
+            }
+            case eCSSUnit_Percent: {
+              float diff = v1.GetPercentValue() - v2.GetPercentValue();
+              diffsquared = diff * diff;
               break;
+            }
+            case eCSSUnit_Calc: {
+              CalcValue val1 = ExtractCalcValue(v1);
+              CalcValue val2 = ExtractCalcValue(v2);
+              float difflen = val2.mLength - val1.mLength;
+              float diffpct = val2.mPercent - val1.mPercent;
+              diffsquared = difflen * difflen + diffpct * diffpct;
+              break;
+            }
             default:
               if (v1 != v2) {
                 return PR_FALSE;
               }
               break;
           }
-          squareDistance += diff * diff;
+          squareDistance += diffsquared;
         }
         list1 = list1->mNext;
         list2 = list2->mNext;
       } while (list1 && list2);
       if (list1 || list2) {
         // We can't interpolate lists of different lengths.
         return PR_FALSE;
       }
@@ -630,20 +707,22 @@ AddCSSValuePercent(double aCoeff1, const
                    nsCSSValue &aResult)
 {
   NS_ABORT_IF_FALSE(aValue1.GetUnit() == eCSSUnit_Percent, "unexpected unit");
   NS_ABORT_IF_FALSE(aValue2.GetUnit() == eCSSUnit_Percent, "unexpected unit");
   aResult.SetPercentValue(aCoeff1 * aValue1.GetPercentValue() +
                           aCoeff2 * aValue2.GetPercentValue());
 }
 
+// Add two non-canonical-form calc values (eUnit_Transform) to make
+// another non-canonical-form calc value.
 static void
-AddCSSValueCalc(double aCoeff1, const nsCSSValue &aValue1,
-                double aCoeff2, const nsCSSValue &aValue2,
-                nsCSSValue &aResult)
+AddCSSValueNoncanonicalCalc(double aCoeff1, const nsCSSValue &aValue1,
+                            double aCoeff2, const nsCSSValue &aValue2,
+                            nsCSSValue &aResult)
 {
   NS_ABORT_IF_FALSE(aValue1.GetUnit() == eCSSUnit_Percent ||
                     aValue1.GetUnit() == eCSSUnit_Pixel ||
                     aValue1.IsCalcUnit(), "unexpected unit");
   NS_ABORT_IF_FALSE(aValue2.GetUnit() == eCSSUnit_Percent ||
                     aValue2.GetUnit() == eCSSUnit_Pixel ||
                     aValue2.IsCalcUnit(), "unexpected unit");
   nsRefPtr<nsCSSValue::Array> a1 = nsCSSValue::Array::Create(2),
@@ -658,16 +737,36 @@ AddCSSValueCalc(double aCoeff1, const ns
   a2->Item(1) = aValue2.GetUnit() == eCSSUnit_Calc
                   ? aValue2.GetArrayValue()->Item(0) : aValue2;
   atop->Item(0).SetArrayValue(a1, eCSSUnit_Calc_Times_L);
   atop->Item(1).SetArrayValue(a2, eCSSUnit_Calc_Times_L);
   acalc->Item(0).SetArrayValue(atop, eCSSUnit_Calc_Plus);
   aResult.SetArrayValue(acalc, eCSSUnit_Calc);
 }
 
+// Add two canonical-form calc values (eUnit_Calc) to make another
+// canonical-form calc value.
+static void
+AddCSSValueCanonicalCalc(double aCoeff1, const nsCSSValue &aValue1,
+                         double aCoeff2, const nsCSSValue &aValue2,
+                         nsCSSValue &aResult)
+{
+  CalcValue v1 = ExtractCalcValue(aValue1);
+  CalcValue v2 = ExtractCalcValue(aValue2);
+  NS_ABORT_IF_FALSE(v1.mHasPercent || v2.mHasPercent,
+                    "only used on properties that always have percent in calc");
+  nsRefPtr<nsCSSValue::Array> a = nsCSSValue::Array::Create(2),
+                              acalc = nsCSSValue::Array::Create(1);
+  a->Item(0).SetFloatValue(aCoeff1 * v1.mLength + aCoeff2 * v2.mLength,
+                           eCSSUnit_Pixel);
+  a->Item(1).SetPercentValue(aCoeff1 * v1.mPercent + aCoeff2 * v2.mPercent);
+  acalc->Item(0).SetArrayValue(a, eCSSUnit_Calc_Plus);
+  aResult.SetArrayValue(acalc, eCSSUnit_Calc);
+}
+
 static void
 AddCSSValueAngle(const nsCSSValue &aValue1, double aCoeff1,
                  const nsCSSValue &aValue2, double aCoeff2,
                  nsCSSValue &aResult)
 {
   aResult.SetFloatValue(aCoeff1 * aValue1.GetAngleValueInRadians() +
                         aCoeff2 * aValue2.GetAngleValueInRadians(),
                         eCSSUnit_Radian);
@@ -745,17 +844,17 @@ AddTransformTranslate(const nsCSSValue &
                     "unexpected unit");
   NS_ABORT_IF_FALSE(aValue2.GetUnit() == eCSSUnit_Percent ||
                     aValue2.GetUnit() == eCSSUnit_Pixel ||
                     aValue2.IsCalcUnit(),
                     "unexpected unit");
 
   if (aValue1.GetUnit() != aValue2.GetUnit() || aValue1.IsCalcUnit()) {
     // different units; create a calc() expression
-    AddCSSValueCalc(aCoeff1, aValue1, aCoeff2, aValue2, aResult);
+    AddCSSValueNoncanonicalCalc(aCoeff1, aValue1, aCoeff2, aValue2, aResult);
   } else if (aValue1.GetUnit() == eCSSUnit_Percent) {
     // both percent
     AddCSSValuePercent(aCoeff1, aValue1, aCoeff2, aValue2, aResult);
   } else {
     // both pixels
     AddCSSValuePixel(aCoeff1, aValue1, aCoeff2, aValue2, aResult);
   }
 }
@@ -1380,41 +1479,46 @@ nsStyleAnimation::AddWeighted(nsCSSPrope
         arr->Item(0).SetFloatValue(len, eCSSUnit_Pixel);
       }
       aResultValue.SetAndAdoptCSSValueValue(val, eUnit_Calc);
       return PR_TRUE;
     }
     case eUnit_CSSValuePair: {
       const nsCSSValuePair *pair1 = aValue1.GetCSSValuePairValue();
       const nsCSSValuePair *pair2 = aValue2.GetCSSValuePairValue();
-      if (pair1->mXValue.GetUnit() != pair2->mXValue.GetUnit() ||
-          pair1->mYValue.GetUnit() != pair2->mYValue.GetUnit()) {
-        // At least until we have calc()
+      nsCSSUnit unit[2];
+      unit[0] = GetCommonUnit(aProperty, pair1->mXValue.GetUnit(),
+                              pair2->mXValue.GetUnit());
+      unit[1] = GetCommonUnit(aProperty, pair1->mYValue.GetUnit(),
+                              pair2->mYValue.GetUnit());
+      if (unit[0] == eCSSUnit_Null || unit[1] == eCSSUnit_Null) {
         return PR_FALSE;
       }
 
       nsAutoPtr<nsCSSValuePair> result(new nsCSSValuePair);
-      static nsCSSValue nsCSSValuePair::* const pairValues[] = {
+      static nsCSSValue nsCSSValuePair::* const pairValues[2] = {
         &nsCSSValuePair::mXValue, &nsCSSValuePair::mYValue
       };
-      for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(pairValues); ++i) {
+      for (PRUint32 i = 0; i < 2; ++i) {
         nsCSSValue nsCSSValuePair::*member = pairValues[i];
-        NS_ABORT_IF_FALSE((pair1->*member).GetUnit() ==
-                            (pair2->*member).GetUnit(),
-                          "should have returned above");
-        switch ((pair1->*member).GetUnit()) {
+        switch (unit[i]) {
           case eCSSUnit_Pixel:
             AddCSSValuePixel(aCoeff1, pair1->*member, aCoeff2, pair2->*member,
                              result->*member);
             break;
           case eCSSUnit_Percent:
             AddCSSValuePercent(aCoeff1, pair1->*member,
                                aCoeff2, pair2->*member,
                                result->*member);
             break;
+          case eCSSUnit_Calc:
+            AddCSSValueCanonicalCalc(aCoeff1, pair1->*member,
+                                     aCoeff2, pair2->*member,
+                                     result->*member);
+            break;
           default:
             NS_ABORT_IF_FALSE(PR_FALSE, "unexpected unit");
             return PR_FALSE;
         }
       }
 
       aResultValue.SetAndAdoptCSSValuePairValue(result.forget(),
                                                 eUnit_CSSValuePair);
@@ -1649,26 +1753,31 @@ nsStyleAnimation::AddWeighted(nsCSSPrope
         static nsCSSValue nsCSSValuePairList::* const pairListValues[] = {
           &nsCSSValuePairList::mXValue,
           &nsCSSValuePairList::mYValue,
         };
         for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(pairListValues); ++i) {
           const nsCSSValue &v1 = list1->*(pairListValues[i]);
           const nsCSSValue &v2 = list2->*(pairListValues[i]);
           nsCSSValue &vr = item->*(pairListValues[i]);
-          if (v1.GetUnit() != v2.GetUnit()) {
+          nsCSSUnit unit =
+            GetCommonUnit(aProperty, v1.GetUnit(), v2.GetUnit());
+          if (unit == eCSSUnit_Null) {
             return PR_FALSE;
           }
-          switch (v1.GetUnit()) {
+          switch (unit) {
             case eCSSUnit_Pixel:
               AddCSSValuePixel(aCoeff1, v1, aCoeff2, v2, vr);
               break;
             case eCSSUnit_Percent:
               AddCSSValuePercent(aCoeff1, v1, aCoeff2, v2, vr);
               break;
+            case eCSSUnit_Calc:
+              AddCSSValueCanonicalCalc(aCoeff1, v1, aCoeff2, v2, vr);
+              break;
             default:
               if (v1 != v2) {
                 return PR_FALSE;
               }
               vr = v1;
               break;
           }
         }
@@ -1995,29 +2104,35 @@ StyleCoordToValue(const nsStyleCoord& aC
       break;
     }
     default:
       return PR_FALSE;
   }
   return PR_TRUE;
 }
 
-static void
+static PRBool
 StyleCoordToCSSValue(const nsStyleCoord& aCoord, nsCSSValue& aCSSValue)
 {
   switch (aCoord.GetUnit()) {
     case eStyleUnit_Coord:
       nscoordToCSSValue(aCoord.GetCoordValue(), aCSSValue);
       break;
     case eStyleUnit_Percent:
       aCSSValue.SetPercentValue(aCoord.GetPercentValue());
       break;
+    case eStyleUnit_Calc:
+      if (!SetCalcValue(aCoord.GetCalcValue(), aCSSValue))
+        return PR_FALSE;
+      break;
     default:
       NS_ABORT_IF_FALSE(PR_FALSE, "unexpected unit");
+      return PR_FALSE;
   }
+  return PR_TRUE;
 }
 
 /*
  * Assign |aOutput = aInput|, except with any non-pixel lengths
  * replaced with the equivalent in pixels.
  */
 static void
 SubstitutePixelValues(nsStyleContext* aStyleContext,
@@ -2155,23 +2270,23 @@ nsStyleAnimation::ExtractComputedValue(n
                                                       eUnit_CSSValuePair);
           break;
         }
 
         case eCSSProperty__moz_transform_origin: {
           const nsStyleDisplay *styleDisplay =
             static_cast<const nsStyleDisplay*>(styleStruct);
           nsCSSValuePair *pair = new nsCSSValuePair;
-          if (!pair) {
+          if (!pair ||
+              !StyleCoordToCSSValue(styleDisplay->mTransformOrigin[0],
+                                    pair->mXValue) ||
+              !StyleCoordToCSSValue(styleDisplay->mTransformOrigin[1],
+                                    pair->mYValue)) {
             return PR_FALSE;
           }
-          StyleCoordToCSSValue(styleDisplay->mTransformOrigin[0],
-                               pair->mXValue);
-          StyleCoordToCSSValue(styleDisplay->mTransformOrigin[1],
-                               pair->mYValue);
           aComputedValue.SetAndAdoptCSSValuePairValue(pair,
                                                       eUnit_CSSValuePair);
           break;
         }
 
         case eCSSProperty_stroke_dasharray: {
           const nsStyleSVG *svg = static_cast<const nsStyleSVG*>(styleStruct);
           NS_ABORT_IF_FALSE((svg->mStrokeDasharray != nsnull) ==
@@ -2474,21 +2589,21 @@ nsStyleAnimation::ExtractComputedValue(n
       const nsStyleCorners *corners = static_cast<const nsStyleCorners*>(
         StyleDataAtOffset(styleStruct, ssOffset));
       PRUint8 fullCorner = animType - eStyleAnimType_Corner_TopLeft;
       const nsStyleCoord &horiz =
         corners->Get(NS_FULL_TO_HALF_CORNER(fullCorner, PR_FALSE));
       const nsStyleCoord &vert =
         corners->Get(NS_FULL_TO_HALF_CORNER(fullCorner, PR_TRUE));
       nsCSSValuePair *pair = new nsCSSValuePair;
-      if (!pair) {
+      if (!pair ||
+          !StyleCoordToCSSValue(horiz, pair->mXValue) ||
+          !StyleCoordToCSSValue(vert, pair->mYValue)) {
         return PR_FALSE;
       }
-      StyleCoordToCSSValue(horiz, pair->mXValue);
-      StyleCoordToCSSValue(vert, pair->mYValue);
       aComputedValue.SetAndAdoptCSSValuePairValue(pair, eUnit_CSSValuePair);
       return PR_TRUE;
     }
     case eStyleAnimType_nscoord:
       aComputedValue.SetCoordValue(*static_cast<const nscoord*>(
         StyleDataAtOffset(styleStruct, ssOffset)));
       return PR_TRUE;
     case eStyleAnimType_EnumU8:
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -511,34 +511,50 @@ function test_radius_transition(prop) {
   div.style.setProperty(prop, "3px", "");
   is(cs.getPropertyValue(prop), "3px",
      "radius-valued property " + prop +
      ": computed value before transition");
   div.style.setProperty("-moz-transition-property", prop, "");
   div.style.setProperty(prop, "15px", "");
   is(cs.getPropertyValue(prop), "6px",
      "radius-valued property " + prop + ": interpolation of radius");
+  div.style.setProperty("-moz-transition-property", "none", "");
   div.style.setProperty(prop, "5%", "");
   is(cs.getPropertyValue(prop), "10px",
-     "radius-valued property " + prop + ": non-interpolability of unit change");
+     "radius-valued property " + prop + ": computed value before transition");
+  div.style.setProperty("-moz-transition-property", prop, "");
   div.style.setProperty(prop, "25%", "");
   is(cs.getPropertyValue(prop), "20px",
      "radius-valued property " + prop + ": interpolation of radius");
+  div.style.setProperty("-moz-transition-property", "none", "");
   div.style.setProperty(prop, "3px 8px", "");
   is(cs.getPropertyValue(prop), "3px 8px",
-     "radius-valued property " + prop + ": non-interpolability of unit change");
+     "radius-valued property " + prop + ": computed value before transition");
+  div.style.setProperty("-moz-transition-property", prop, "");
   div.style.setProperty(prop, "15px 12px", "");
   is(cs.getPropertyValue(prop), "6px 9px",
      "radius-valued property " + prop + ": interpolation of radius");
+  div.style.setProperty("-moz-transition-property", "none", "");
   div.style.setProperty(prop, "5% 15%", "");
   is(cs.getPropertyValue(prop), "10px 30px",
-     "radius-valued property " + prop + ": non-interpolability of unit change");
+     "radius-valued property " + prop + ": computed value before transition");
+  div.style.setProperty("-moz-transition-property", prop, "");
   div.style.setProperty(prop, "25%", "");
   is(cs.getPropertyValue(prop), "20px 35px",
      "radius-valued property " + prop + ": interpolation of radius");
+  div.style.setProperty("-moz-transition-property", "none", "");
+  div.style.setProperty(prop, "8% 12%", "");
+  is(cs.getPropertyValue(prop), "16px 24px",
+     "radius-valued property " + prop + ": computed value before transition");
+  div.style.setProperty("-moz-transition-property", prop, "");
+  div.style.setProperty(prop, "40px 20px", "");
+  is(cs.getPropertyValue(prop), "22px 23px",
+     "radius-valued property " + prop + ": interpolation of radius with mixed units");
+
+  test_length_percent_calc_transition(prop);
 
   div.style.removeProperty("width");
   div.style.removeProperty("height");
   div.style.removeProperty("border");
   div.style.removeProperty("padding");
 }
 
 function test_zindex_transition(prop) {
@@ -701,34 +717,42 @@ function test_background_position_transi
   div.style.setProperty("-moz-transition-property", prop, "");
   div.style.setProperty(prop, "bottom right", "");
   is(cs.getPropertyValue(prop), "62.5% 85%",
      "property " + prop + ": interpolation of percents");
   test_background_position_size_common(prop);
 }
 
 function test_background_position_size_common(prop) {
+  div.style.setProperty("-moz-transition-property", "none", "");
   div.style.setProperty(prop, "10px 40px", "");
   is(cs.getPropertyValue(prop), "10px 40px",
      "property " + prop + ": computed value before transition");
+  div.style.setProperty("-moz-transition-property", prop, "");
   div.style.setProperty(prop, "50px 0", "");
   is(cs.getPropertyValue(prop), "20px 30px",
      "property " + prop + ": interpolation of lengths");
   div.style.setProperty(prop, "10px 40px, 50px 50px, 30px 20px", "");
   is(cs.getPropertyValue(prop), "10px 40px, 50px 50px, 30px 20px",
      "property " + prop + ": computed value before transition");
   div.style.setProperty(prop, "50px 20px, 70px 50px, 30px 40px", "");
   is(cs.getPropertyValue(prop), "20px 35px, 55px 50px, 30px 25px",
      "property " + prop + ": interpolation of lists of lengths");
-  div.style.setProperty(prop, "10px 40%, 50% 50px, 30% 20%", "");
-  is(cs.getPropertyValue(prop), "10px 40%, 50% 50px, 30% 20%",
+  div.style.setProperty(prop, "10px 40%, 50% 50px, 30% 20%, 5px 10px", "");
+  is(cs.getPropertyValue(prop), "10px 40%, 50% 50px, 30% 20%, 5px 10px",
      "property " + prop + ": computed value before transition");
-  div.style.setProperty(prop, "50px 20%, 70% 50px, 30% 40%", "");
-  is(cs.getPropertyValue(prop), "20px 35%, 55% 50px, 30% 25%",
+  div.style.setProperty(prop, "50px 20%, 70% 50px, 30% 40%, 25px 50px", "");
+  is(cs.getPropertyValue(prop), "20px 35%, 55% 50px, 30% 25%, 10px 20px",
      "property " + prop + ": interpolation of lists of lengths and percents");
+  div.style.setProperty(prop, "20% 40%, 8px 12px", "");
+  is(cs.getPropertyValue(prop), "20% 40%, 8px 12px",
+     "property " + prop + ": computed value before transition");
+  div.style.setProperty(prop, "12px 20px, 40% 16%", "");
+  is(cs.getPropertyValue(prop), "-moz-calc(3px + 15%) -moz-calc(5px + 30%), -moz-calc(6px + 10%) -moz-calc(9px + 4%)",
+     "property " + prop + ": interpolation that computes to calc()");
 }
 
 function test_transform_transition(prop) {
   function c(v) {
     div.style.setProperty(prop, v, "");
     var result = cs.getPropertyValue(prop);
     div.style.removeProperty(prop);
     return result;