Add support for animation of text-shadow and -moz-box-shadow (the first complex value types that we animate). (Bug 523196) r=dholbert sr=bzbarsky
authorL. David Baron <dbaron@dbaron.org>
Wed, 21 Oct 2009 06:53:46 -0400
changeset 34062 c548148943bd7f669c77a8893420bef86c786455
parent 34061 8cf1dd820f3cbfa8a737ea7ee8a269561ebb13c0
child 34063 573a37f0d13c045c4185f707ab8c67c5119f344c
push idunknown
push userunknown
push dateunknown
reviewersdholbert, bzbarsky
bugs523196
milestone1.9.3a1pre
Add support for animation of text-shadow and -moz-box-shadow (the first complex value types that we animate). (Bug 523196) r=dholbert sr=bzbarsky
layout/style/nsCSSPropList.h
layout/style/nsCSSProps.h
layout/style/nsStyleAnimation.cpp
layout/style/nsStyleAnimation.h
layout/style/nsTransitionManager.cpp
layout/style/test/test_transitions_per_property.html
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -1138,18 +1138,18 @@ CSS_PROP_BORDER(
     MozBoxShadow,
     CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
         CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
         CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
     Margin,
     mBoxShadow,
     eCSSType_ValueList,
     kBoxShadowTypeKTable,
-    CSS_PROP_NO_OFFSET,
-    eStyleAnimType_None)
+    offsetof(nsStyleBorder, mBoxShadow),
+    eStyleAnimType_Shadow)
 CSS_PROP_POSITION(
     -moz-box-sizing,
     box_sizing,
     MozBoxSizing,
     0,
     Position,
     mBoxSizing,
     eCSSType_Value,
@@ -2303,18 +2303,18 @@ CSS_PROP_TEXT(
     TextShadow,
     CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
         CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED,
     Text,
     mTextShadow,
     eCSSType_ValueList,
     nsnull,
-    CSS_PROP_NO_OFFSET,
-    eStyleAnimType_None)
+    offsetof(nsStyleText, mTextShadow),
+    eStyleAnimType_Shadow)
 CSS_PROP_TEXT(
     text-transform,
     text_transform,
     TextTransform,
     CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE,
     Text,
     mTextTransform,
     eCSSType_Value,
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -90,16 +90,19 @@ enum nsStyleAnimType {
   eStyleAnimType_float,
 
   // nscolor values
   eStyleAnimType_Color,
 
   // nsStyleSVGPaint values
   eStyleAnimType_PaintServer,
 
+  // nsRefPtr<nsCSSShadowArray> values
+  eStyleAnimType_Shadow,
+
   // property not animatable
   eStyleAnimType_None
 };
 
 class nsCSSProps {
 public:
   static void AddRefTable(void);
   static void ReleaseTable(void);
--- a/layout/style/nsStyleAnimation.cpp
+++ b/layout/style/nsStyleAnimation.cpp
@@ -46,16 +46,17 @@
 #include "nsString.h"
 #include "nsStyleContext.h"
 #include "nsStyleSet.h"
 #include "nsComputedDOMStyle.h"
 #include "nsICSSParser.h"
 #include "nsICSSLoader.h"
 #include "nsCSSDataBlock.h"
 #include "nsCSSDeclaration.h"
+#include "nsCSSStruct.h"
 #include "prlog.h"
 #include <math.h>
 
 // HELPER METHODS
 // --------------
 /*
  * Given two units, this method returns a common unit that they can both be
  * converted into, if possible.  This is intended to facilitate
@@ -147,16 +148,77 @@ nsStyleAnimation::ComputeDistance(const 
       double diffA = startA - endA;
       double diffR = startR - endR;
       double diffG = startG - endG;
       double diffB = startB - endB;
       aDistance = sqrt(diffA * diffA + diffR * diffR +
                        diffG * diffG + diffB * diffB);
       break;
     }
+    case eUnit_Shadow: {
+      // Call AddWeighted to make us lists of the same length.
+      Value normValue1, normValue2;
+      if (!AddWeighted(1.0, aStartValue, 0.0, aEndValue, normValue1) ||
+          !AddWeighted(0.0, aStartValue, 1.0, aEndValue, normValue2)) {
+        success = PR_FALSE;
+        break;
+      }
+
+      const nsCSSValueList *shadow1 = normValue1.GetCSSValueListValue();
+      const nsCSSValueList *shadow2 = normValue2.GetCSSValueListValue();
+
+      double distance = 0.0f;
+      while (shadow1 && shadow2) {
+        nsCSSValue::Array *array1 = shadow1->mValue.GetArrayValue();
+        nsCSSValue::Array *array2 = shadow2->mValue.GetArrayValue();
+        for (PRUint32 i = 0; i < 4; ++i) {
+          NS_ABORT_IF_FALSE(array1->Item(i).GetUnit() == eCSSUnit_Pixel,
+                            "unexpected unit");
+          NS_ABORT_IF_FALSE(array2->Item(i).GetUnit() == eCSSUnit_Pixel,
+                            "unexpected unit");
+          double diff = array1->Item(i).GetFloatValue() -
+                        array2->Item(i).GetFloatValue();
+          distance += diff * diff;
+        }
+
+        const nsCSSValue& color1 = array1->Item(4);
+        const nsCSSValue& color2 = array2->Item(4);
+        const nsCSSValue& inset1 = array1->Item(5);
+        const nsCSSValue& inset2 = array2->Item(5);
+        // There are only two possible states of the inset value:
+        //  (1) GetUnit() == eCSSUnit_Null
+        //  (2) GetUnit() == eCSSUnit_Enumerated &&
+        //      GetIntValue() == NS_STYLE_BOX_SHADOW_INSET
+        NS_ABORT_IF_FALSE(color1.GetUnit() == color2.GetUnit() &&
+                          inset1 == inset2,
+                          "AddWeighted should have failed");
+
+        if (color1.GetUnit() != eCSSUnit_Null) {
+          nsStyleAnimation::Value color1Value
+            (color1.GetColorValue(), nsStyleAnimation::Value::ColorConstructor);
+          nsStyleAnimation::Value color2Value
+            (color2.GetColorValue(), nsStyleAnimation::Value::ColorConstructor);
+          double colorDistance;
+
+        #ifdef DEBUG
+          PRBool ok =
+        #endif
+            nsStyleAnimation::ComputeDistance(color1Value, color2Value,
+                                              colorDistance);
+          NS_ABORT_IF_FALSE(ok, "should not fail");
+          distance += colorDistance * colorDistance;
+        }
+
+        shadow1 = shadow1->mNext;
+        shadow2 = shadow2->mNext;
+      }
+      NS_ABORT_IF_FALSE(!shadow1 && !shadow2, "lists of different lengths");
+      aDistance = sqrt(distance);
+      break;
+    }
     case eUnit_Null:
     case eUnit_None:
       success = PR_FALSE;
       break;
     default:
       NS_NOTREACHED("Can't compute distance using the given common unit");
       success = PR_FALSE;
       break;
@@ -170,16 +232,83 @@ inline PRUint8 ClampColor(double aColor)
 {
   if (aColor >= MAX_PACKED_COLOR_COMPONENT)
     return MAX_PACKED_COLOR_COMPONENT;
   if (aColor <= 0.0)
     return 0;
   return NSToIntRound(aColor);
 }
 
+static PRBool
+AddShadowItems(double aCoeff1, const nsCSSValue &aValue1,
+               double aCoeff2, const nsCSSValue &aValue2,
+               nsCSSValueList **&aResultTail)
+{
+  // X, Y, Radius, Spread, Color, Inset
+  NS_ABORT_IF_FALSE(aValue1.GetUnit() == eCSSUnit_Array,
+                    "wrong unit");
+  NS_ABORT_IF_FALSE(aValue2.GetUnit() == eCSSUnit_Array,
+                    "wrong unit");
+  nsCSSValue::Array *array1 = aValue1.GetArrayValue();
+  nsCSSValue::Array *array2 = aValue2.GetArrayValue();
+  nsRefPtr<nsCSSValue::Array> resultArray = nsCSSValue::Array::Create(6);
+  if (!resultArray) {
+    return PR_FALSE;
+  }
+
+  for (PRUint32 i = 0; i < 4; ++i) {
+    NS_ABORT_IF_FALSE(array1->Item(i).GetUnit() == eCSSUnit_Pixel,
+                      "unexpected unit");
+    NS_ABORT_IF_FALSE(array2->Item(i).GetUnit() == eCSSUnit_Pixel,
+                      "unexpected unit");
+    double pixel1 = array1->Item(i).GetFloatValue();
+    double pixel2 = array2->Item(i).GetFloatValue();
+    resultArray->Item(i).SetFloatValue(aCoeff1 * pixel1 + aCoeff2 * pixel2,
+                                       eCSSUnit_Pixel);
+  }
+
+  const nsCSSValue& color1 = array1->Item(4);
+  const nsCSSValue& color2 = array2->Item(4);
+  const nsCSSValue& inset1 = array1->Item(5);
+  const nsCSSValue& inset2 = array2->Item(5);
+  if (color1.GetUnit() != color2.GetUnit() ||
+      inset1.GetUnit() != inset2.GetUnit()) {
+    // We don't know how to animate between color and no-color, or
+    // between inset and not-inset.
+    return PR_FALSE;
+  }
+
+  if (color1.GetUnit() != eCSSUnit_Null) {
+    nsStyleAnimation::Value color1Value
+      (color1.GetColorValue(), nsStyleAnimation::Value::ColorConstructor);
+    nsStyleAnimation::Value color2Value
+      (color2.GetColorValue(), nsStyleAnimation::Value::ColorConstructor);
+    nsStyleAnimation::Value resultColorValue;
+  #ifdef DEBUG
+    PRBool ok =
+  #endif
+      nsStyleAnimation::AddWeighted(aCoeff1, color1Value, aCoeff2, color2Value,
+                                    resultColorValue);
+    NS_ABORT_IF_FALSE(ok, "should not fail");
+    resultArray->Item(4).SetColorValue(resultColorValue.GetColorValue());
+  }
+
+  NS_ABORT_IF_FALSE(inset1 == inset2, "should match");
+  resultArray->Item(5) = inset1;
+
+  nsCSSValueList *resultItem = new nsCSSValueList;
+  if (!resultItem) {
+    return PR_FALSE;
+  }
+  resultItem->mValue.SetArrayValue(resultArray, eCSSUnit_Array);
+  *aResultTail = resultItem;
+  aResultTail = &resultItem->mNext;
+  return PR_TRUE;
+}
+
 PRBool
 nsStyleAnimation::AddWeighted(double aCoeff1, const Value& aValue1,
                               double aCoeff2, const Value& aValue2,
                               Value& aResultValue)
 {
   Unit commonUnit = GetCommonUnit(aValue1.GetUnit(), aValue2.GetUnit());
   // Maybe need a followup method to convert the inputs into the common
   // unit-type, if they don't already match it. (Or would it make sense to do
@@ -237,16 +366,63 @@ nsStyleAnimation::AddWeighted(double aCo
         PRUint8 Rres = ClampColor((R1 * aCoeff1 + R2 * aCoeff2) * factor);
         PRUint8 Gres = ClampColor((G1 * aCoeff1 + G2 * aCoeff2) * factor);
         PRUint8 Bres = ClampColor((B1 * aCoeff1 + B2 * aCoeff2) * factor);
         resultColor = NS_RGBA(Rres, Gres, Bres, Ares);
       }
       aResultValue.SetColorValue(resultColor);
       break;
     }
+    case eUnit_Shadow: {
+      // This is implemented according to:
+      // http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types-
+      // and the third item in the summary of:
+      // http://lists.w3.org/Archives/Public/www-style/2009Jul/0050.html
+      const nsCSSValueList *shadow1 = aValue1.GetCSSValueListValue();
+      const nsCSSValueList *shadow2 = aValue2.GetCSSValueListValue();
+      nsAutoPtr<nsCSSValueList> result;
+      nsCSSValueList **resultTail = getter_Transfers(result);
+      while (shadow1 && shadow2) {
+        if (!AddShadowItems(aCoeff1, shadow1->mValue,
+                            aCoeff2, shadow2->mValue,
+                            resultTail)) {
+          return PR_FALSE;
+        }
+        shadow1 = shadow1->mNext;
+        shadow2 = shadow2->mNext;
+      }
+      if (shadow1 || shadow2) {
+        const nsCSSValueList *longShadow;
+        double longCoeff;
+        if (shadow1) {
+          longShadow = shadow1;
+          longCoeff = aCoeff1;
+        } else {
+          longShadow = shadow2;
+          longCoeff = aCoeff2;
+        }
+
+        while (longShadow) {
+          // Passing coefficients that add to less than 1 produces the
+          // desired result of interpolating "0 0 0 transparent" with
+          // the current shadow.
+          if (!AddShadowItems(longCoeff, longShadow->mValue,
+                              0.0, longShadow->mValue,
+                              resultTail)) {
+            return PR_FALSE;
+            break;
+          }
+
+          longShadow = longShadow->mNext;
+        }
+      }
+      aResultValue.SetCSSValueListValue(result.forget(), eUnit_Shadow,
+                                        PR_TRUE);
+      break;
+    }
     case eUnit_Null:
     case eUnit_None:
       success = PR_FALSE;
       break;
     default:
       NS_NOTREACHED("Can't interpolate using the given common unit");
       success = PR_FALSE;
       break;
@@ -397,16 +573,22 @@ nsStyleAnimation::UncomputeValue(nsCSSPr
           SetBothValuesTo(val);
       } else {
         NS_ABORT_IF_FALSE(nsCSSProps::kTypeTable[aProperty] == eCSSType_Value,
                           "type mismatch");
         static_cast<nsCSSValue*>(aSpecifiedValue)->
           SetColorValue(aComputedValue.GetColorValue());
       }
       break;
+    case eUnit_Shadow:
+      NS_ABORT_IF_FALSE(nsCSSProps::kTypeTable[aProperty] ==
+                          eCSSType_ValueList, "type mismatch");
+      *static_cast<nsCSSValueList**>(aSpecifiedValue) =
+        aComputedValue.GetCSSValueListValue();
+      break;
     default:
       return PR_FALSE;
   }
   return PR_TRUE;
 }
 
 PRBool
 nsStyleAnimation::UncomputeValue(nsCSSProperty aProperty,
@@ -625,16 +807,64 @@ nsStyleAnimation::ExtractComputedValue(n
         return PR_TRUE;
       }
       if (paint.mType == eStyleSVGPaintType_None) {
         aComputedValue.SetNoneValue();
         return PR_TRUE;
       }
       return PR_FALSE;
     }
+    case eStyleAnimType_Shadow: {
+      const nsCSSShadowArray *shadowArray =
+        *static_cast<const nsRefPtr<nsCSSShadowArray>*>(
+          StyleDataAtOffset(styleStruct, ssOffset));
+      if (!shadowArray) {
+        aComputedValue.SetCSSValueListValue(nsnull, eUnit_Shadow, PR_TRUE);
+        return PR_TRUE;
+      }
+      nsAutoPtr<nsCSSValueList> result;
+      nsCSSValueList **resultTail = getter_Transfers(result);
+      for (PRUint32 i = 0, i_end = shadowArray->Length(); i < i_end; ++i) {
+        const nsCSSShadowItem *shadow = shadowArray->ShadowAt(i);
+        // X, Y, Radius, Spread, Color, Inset
+        nsRefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(6);
+        arr->Item(0).SetFloatValue(
+          nsPresContext::AppUnitsToFloatCSSPixels(shadow->mXOffset),
+          eCSSUnit_Pixel);
+        arr->Item(1).SetFloatValue(
+          nsPresContext::AppUnitsToFloatCSSPixels(shadow->mYOffset),
+          eCSSUnit_Pixel);
+        arr->Item(2).SetFloatValue(
+          nsPresContext::AppUnitsToFloatCSSPixels(shadow->mRadius),
+          eCSSUnit_Pixel);
+        // NOTE: This code sometimes stores mSpread: 0 even when
+        // the parser would be required to leave it null.
+        arr->Item(3).SetFloatValue(
+          nsPresContext::AppUnitsToFloatCSSPixels(shadow->mSpread),
+          eCSSUnit_Pixel);
+        if (shadow->mHasColor) {
+          arr->Item(4).SetColorValue(shadow->mColor);
+        }
+        if (shadow->mInset) {
+          arr->Item(5).SetIntValue(NS_STYLE_BOX_SHADOW_INSET,
+                                   eCSSUnit_Enumerated);
+        }
+
+        nsCSSValueList *resultItem = new nsCSSValueList;
+        if (!resultItem) {
+          return PR_FALSE;
+        }
+        resultItem->mValue.SetArrayValue(arr, eCSSUnit_Array);
+        *resultTail = resultItem;
+        resultTail = &resultItem->mNext;
+      }
+      aComputedValue.SetCSSValueListValue(result.forget(), eUnit_Shadow,
+                                          PR_TRUE);
+      return PR_TRUE;
+    }
     case eStyleAnimType_None:
       NS_NOTREACHED("shouldn't use on non-animatable properties");
   }
   return PR_FALSE;
 }
 
 nsStyleAnimation::Value::Value(nscoord aLength, CoordConstructorType)
 {
@@ -677,16 +907,19 @@ nsStyleAnimation::Value::operator=(const
       break;
     case eUnit_Percent:
     case eUnit_Float:
       mValue.mFloat = aOther.mValue.mFloat;
       break;
     case eUnit_Color:
       mValue.mColor = aOther.mValue.mColor;
       break;
+    case eUnit_Shadow:
+      mValue.mCSSValueList = aOther.mValue.mCSSValueList
+                               ? aOther.mValue.mCSSValueList->Clone() : nsnull;
   }
 
   return *this;
 }
 
 void
 nsStyleAnimation::Value::SetNormalValue()
 {
@@ -736,18 +969,36 @@ void
 nsStyleAnimation::Value::SetColorValue(nscolor aColor)
 {
   FreeValue();
   mUnit = eUnit_Color;
   mValue.mColor = aColor;
 }
 
 void
+nsStyleAnimation::Value::SetCSSValueListValue(nsCSSValueList *aValueList,
+                                              Unit aUnit,
+                                              PRBool aTakeOwnership)
+{
+  FreeValue();
+  NS_ASSERTION(IsCSSValueListUnit(aUnit), "bad unit");
+  mUnit = aUnit;
+  if (aTakeOwnership) {
+    mValue.mCSSValueList = aValueList;
+  } else {
+    mValue.mCSSValueList = aValueList ? aValueList->Clone() : nsnull;
+  }
+}
+
+void
 nsStyleAnimation::Value::FreeValue()
 {
+  if (IsCSSValueListUnit(mUnit)) {
+    delete mValue.mCSSValueList;
+  }
 }
 
 PRBool
 nsStyleAnimation::Value::operator==(const Value& aOther) const
 {
   if (mUnit != aOther.mUnit) {
     return PR_FALSE;
   }
@@ -760,14 +1011,17 @@ nsStyleAnimation::Value::operator==(cons
       return PR_TRUE;
     case eUnit_Coord:
       return mValue.mCoord == aOther.mValue.mCoord;
     case eUnit_Percent:
     case eUnit_Float:
       return mValue.mFloat == aOther.mValue.mFloat;
     case eUnit_Color:
       return mValue.mColor == aOther.mValue.mColor;
+    case eUnit_Shadow:
+      return nsCSSValueList::Equal(mValue.mCSSValueList,
+                                   aOther.mValue.mCSSValueList);
   }
 
   NS_NOTREACHED("incomplete case");
   return PR_FALSE;
 }
 
--- a/layout/style/nsStyleAnimation.h
+++ b/layout/style/nsStyleAnimation.h
@@ -47,16 +47,17 @@
 #include "nsCSSProperty.h"
 #include "nsCoord.h"
 #include "nsColor.h"
 
 class nsCSSDeclaration;
 class nsIContent;
 class nsPresContext;
 class nsStyleContext;
+struct nsCSSValueList;
 
 /**
  * Utility class to handle animated style values
  */
 class nsStyleAnimation {
 public:
   class Value;
 
@@ -168,17 +169,20 @@ public:
 
   /**
    * Creates a specified value for the given computed value.
    *
    * The first form fills in one of the nsCSSType types into the void*;
    * for some types this means that the void* is pointing to memory
    * owned by the nsStyleAnimation::Value.  (For all complex types, the
    * nsStyleAnimation::Value owns the necessary objects so that the
-   * caller does not need to do anything to free them.)
+   * caller does not need to do anything to free them.  However, this
+   * means that callers using the void* variant must keep
+   * |aComputedValue| alive longer than the structure into which they've
+   * filled the value.)
    *
    * @param aProperty      The property whose value we're uncomputing.
    * @param aPresContext   The presentation context for the document in
    *                       which we're working.
    * @param aComputedValue The computed value to be converted.
    * @param [out] aSpecifiedValue The resulting specified value.
    * @return PR_TRUE on success, PR_FALSE on failure.
    */
@@ -210,26 +214,28 @@ public:
   enum Unit {
     eUnit_Null, // not initialized
     eUnit_Normal,
     eUnit_Auto,
     eUnit_None,
     eUnit_Coord,
     eUnit_Percent,
     eUnit_Float,
-    eUnit_Color
+    eUnit_Color,
+    eUnit_Shadow  // nsCSSValueList* (may be null)
   };
 
   class Value {
   private:
     Unit mUnit;
     union {
       nscoord mCoord;
       float mFloat;
       nscolor mColor;
+      nsCSSValueList* mCSSValueList;
     } mValue;
   public:
     Unit GetUnit() const {
       NS_ASSERTION(mUnit != eUnit_Null, "uninitialized");
       return mUnit;
     }
 
     // Accessor to let us verify assumptions about presence of null unit,
@@ -249,16 +255,20 @@ public:
     float GetFloatValue() const {
       NS_ASSERTION(mUnit == eUnit_Float, "unit mismatch");
       return mValue.mFloat;
     }
     nscolor GetColorValue() const {
       NS_ASSERTION(mUnit == eUnit_Color, "unit mismatch");
       return mValue.mColor;
     }
+    nsCSSValueList* GetCSSValueListValue() const {
+      NS_ASSERTION(IsCSSValueListUnit(mUnit), "unit mismatch");
+      return mValue.mCSSValueList;
+    }
 
     explicit Value(Unit aUnit = eUnit_Null) : mUnit(aUnit) {
       NS_ASSERTION(aUnit == eUnit_Null || aUnit == eUnit_Normal ||
                    aUnit == eUnit_Auto || aUnit == eUnit_None,
                    "must be valueless unit");
     }
     Value(const Value& aOther) : mUnit(eUnit_Null) { *this = aOther; }
     enum CoordConstructorType { CoordConstructor };
@@ -274,21 +284,27 @@ public:
 
     void SetNormalValue();
     void SetAutoValue();
     void SetNoneValue();
     void SetCoordValue(nscoord aCoord);
     void SetPercentValue(float aPercent);
     void SetFloatValue(float aFloat);
     void SetColorValue(nscolor aColor);
+    void SetCSSValueListValue(nsCSSValueList *aValue, Unit aUnit,
+                              PRBool aTakeOwnership);
 
     Value& operator=(const Value& aOther);
 
     PRBool operator==(const Value& aOther) const;
     PRBool operator!=(const Value& aOther) const
       { return !(*this == aOther); }
 
   private:
     void FreeValue();
+
+    static PRBool IsCSSValueListUnit(Unit aUnit) {
+      return aUnit == eUnit_Shadow;
+    }
   };
 };
 
 #endif
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -59,17 +59,21 @@ using mozilla::TimeDuration;
 
 /*****************************************************************************
  * Per-Element data                                                          *
  *****************************************************************************/
 
 struct ElementPropertyTransition
 {
   nsCSSProperty mProperty;
-  nsStyleAnimation::Value mStartValue, mEndValue;
+  // We need to have mCurrentValue as a member of this structure because
+  // the result of the calls to |Interpolate| might hold data that this
+  // object's owning style rule needs to keep alive (after calling
+  // UncomputeValue on it in MapRuleInfoInto).
+  nsStyleAnimation::Value mStartValue, mEndValue, mCurrentValue;
   TimeStamp mStartTime; // actual start plus transition delay
 
   // data from the relevant nsTransition
   TimeDuration mDuration;
   nsSMILKeySpline mTimingFunction;
 };
 
 /**
@@ -222,31 +226,30 @@ ElementTransitionsStyleRule::MapRuleInfo
         (RefreshTime() - pt.mStartTime).ToSeconds() / pt.mDuration.ToSeconds();
       if (timePortion < 0.0)
         timePortion = 0.0; // use start value during transition-delay
       if (timePortion > 1.0)
         timePortion = 1.0; // we might be behind on flushing
 
       double valuePortion =
         pt.mTimingFunction.GetSplineValue(timePortion);
-      nsStyleAnimation::Value value;
 #ifdef DEBUG
       PRBool ok =
 #endif
         nsStyleAnimation::Interpolate(pt.mStartValue, pt.mEndValue,
-                                      valuePortion, value);
+                                      valuePortion, pt.mCurrentValue);
       NS_ABORT_IF_FALSE(ok, "could not interpolate values");
 
       void *prop =
         nsCSSExpandedDataBlock::RuleDataPropertyAt(aRuleData, pt.mProperty);
 #ifdef DEBUG
       ok =
 #endif
         nsStyleAnimation::UncomputeValue(pt.mProperty, aRuleData->mPresContext,
-                                         value, prop);
+                                         pt.mCurrentValue, prop);
       NS_ABORT_IF_FALSE(ok, "could not store computed value");
     }
   }
 
   return NS_OK;
 }
 
 #ifdef DEBUG
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -42,16 +42,17 @@ function has_num(str)
 }
 
 function any_unit_to_num(str)
 {
     return Number(String(str).match(/^([\d.]+)/)[1]);
 }
 
 var supported_properties = {
+    "-moz-box-shadow": [ test_shadow_transition ],
     "-moz-column-gap": [ test_length_transition ],
     "-moz-column-rule-color": [ test_color_transition ],
     "-moz-column-width": [ test_length_transition ],
     "background-color": [ test_color_transition ],
     "border-bottom-color": [ test_color_transition ],
     "border-bottom-width": [ test_length_transition ],
     "border-left-color": [ test_color_transition ],
     "border-left-width": [ test_length_transition ],
@@ -94,16 +95,17 @@ var supported_properties = {
     "stop-color": [ test_color_transition ],
     "stop-opacity" : [ test_float_zeroToOne_transition ],
     "stroke": [ test_color_transition ],
     "stroke-dashoffset": [ test_length_transition, test_percent_transition ],
     "stroke-miterlimit": [ test_float_aboveOne_transition ],
     "stroke-opacity" : [ test_float_zeroToOne_transition ],
     "stroke-width": [ test_length_transition, test_percent_transition ],
     "text-indent": [ test_length_transition, test_percent_transition ],
+    "text-shadow": [ test_shadow_transition ],
     "top": [ test_length_transition, test_percent_transition ],
     "vertical-align": [ test_length_transition, test_percent_transition ],
     "width": [ test_length_transition, test_percent_transition ],
     "word-spacing": [ test_length_transition ],
 };
 
 var div = document.getElementById("display");
 var cs = getComputedStyle(div, "");
@@ -270,12 +272,55 @@ function test_color_transition(prop) {
   is(cs.getPropertyValue(prop), "rgb(255, 28, 0)",
      "color-valued property " + prop + ": computed value before transition");
   div.style.setProperty("-moz-transition-property", prop, "");
   div.style.setProperty(prop, "rgb(77, 84, 128)", "");
   is(cs.getPropertyValue(prop), "rgb(166, 56, 64)",
      "color-valued property " + prop + ": interpolation of colors");
 }
 
+function test_shadow_transition(prop) {
+  var spreadStr = (prop == "-moz-box-shadow") ? " 0px" : "";
+
+  div.style.setProperty("-moz-transition-property", "none", "");
+  div.style.setProperty(prop, "none", "");
+  is(cs.getPropertyValue(prop), "none",
+     "shadow-valued property " + prop + ": computed value before transition");
+  div.style.setProperty("-moz-transition-property", prop, "");
+  div.style.setProperty(prop, "4px 8px 3px red", "");
+  is(cs.getPropertyValue(prop), "rgba(255, 0, 0, 0.5) 2px 4px 1.5px" + spreadStr,
+     "shadow-valued property " + prop + ": interpolation of shadows");
+
+  div.style.setProperty("-moz-transition-property", "none", "");
+  div.style.setProperty(prop, "green 4px 4px, 2px 2px blue", "");
+  is(cs.getPropertyValue(prop), "rgb(0, 128, 0) 4px 4px 0px" + spreadStr + ", rgb(0, 0, 255) 2px 2px 0px" + spreadStr,
+     "shadow-valued property " + prop + ": computed value before transition");
+  div.style.setProperty("-moz-transition-property", prop, "");
+  div.style.setProperty(prop, "8px 8px 8px red", "");
+  is(cs.getPropertyValue(prop), "rgb(128, 64, 0) 6px 6px 4px" + spreadStr + ", rgba(0, 0, 255, 0.5) 1px 1px 0px" + spreadStr,
+     "shadow-valued property " + prop + ": interpolation of shadows");
+
+  if (prop == "-moz-box-shadow") {
+    div.style.setProperty(prop, "8px 8px 8px red inset", "");
+    is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 0px inset",
+       "shadow-valued property " + prop + ": non-interpolable cases");
+    div.style.setProperty(prop, "8px 8px 8px 8px red inset", "");
+    is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 4px inset",
+       "shadow-valued property " + prop + ": interpolation of spread");
+    // Leave in same state whether in the |if| or not.
+    div.style.setProperty(prop, "8px 8px 8px red", "");
+    is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 0px",
+       "shadow-valued property " + prop + ": non-interpolable cases");
+  }
+
+  var defaultColor = cs.getPropertyValue("color") + " ";
+  div.style.setProperty(prop, "2px 2px 2px", "");
+  is(cs.getPropertyValue(prop), defaultColor + "2px 2px 2px" + spreadStr,
+     "shadow-valued property " + prop + ": non-interpolable cases");
+  div.style.setProperty(prop, "4px 8px 6px", "");
+  is(cs.getPropertyValue(prop), defaultColor + "3px 5px 4px" + spreadStr,
+     "shadow-valued property " + prop + ": interpolation without color");
+}
+
 </script>
 </pre>
 </body>
 </html>