Add support for animation of 'background-position' and 'background-size'. (Bug 532307) r=bzbarsky
authorL. David Baron <dbaron@dbaron.org>
Mon, 21 Dec 2009 16:46:25 -0500
changeset 36527 eb947f317c42ec7168e44ace57714af65472cfe6
parent 36526 3c8484e98d6290800a1896ecbfc108b6c3d7e56e
child 36528 5bd926e01c2447ff1095be7b228915cec51aba6a
push id10857
push userdbaron@mozilla.com
push dateMon, 21 Dec 2009 21:46:59 +0000
treeherdermozilla-central@48175bb06cb1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs532307
milestone1.9.3a1pre
Add support for animation of 'background-position' and 'background-size'. (Bug 532307) r=bzbarsky
layout/style/nsCSSPropList.h
layout/style/nsStyleAnimation.cpp
layout/style/nsStyleAnimation.h
layout/style/test/test_transitions_per_property.html
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -516,17 +516,17 @@ CSS_PROP_BACKGROUND(
     BackgroundPosition,
     CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
     Color,
     mBackPosition,
     eCSSType_ValuePairList,
     kBackgroundPositionKTable,
     CSS_PROP_NO_OFFSET,
-    eStyleAnimType_None)
+    eStyleAnimType_Custom)
 CSS_PROP_BACKGROUND(
     background-repeat,
     background_repeat,
     BackgroundRepeat,
     CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
     Color,
     mBackRepeat,
@@ -540,17 +540,17 @@ CSS_PROP_BACKGROUND(
     MozBackgroundSize,
     CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
     Color,
     mBackSize,
     eCSSType_ValuePairList,
     kBackgroundSizeKTable,
     CSS_PROP_NO_OFFSET,
-    eStyleAnimType_None)
+    eStyleAnimType_Custom)
 CSS_PROP_DISPLAY(
     -moz-binding,
     binding,
     MozBinding,
     0,
     Display,
     mBinding,
     eCSSType_Value,
--- a/layout/style/nsStyleAnimation.cpp
+++ b/layout/style/nsStyleAnimation.cpp
@@ -397,16 +397,64 @@ nsStyleAnimation::ComputeDistance(nsCSSP
 
         shadow1 = shadow1->mNext;
         shadow2 = shadow2->mNext;
         NS_ABORT_IF_FALSE(!shadow1 == !shadow2, "lists should be same length");
       }
       aDistance = sqrt(squareDistance);
       break;
     }
+    case eUnit_CSSValuePairList: {
+      const nsCSSValuePairList *list1 = aStartValue.GetCSSValuePairListValue();
+      const nsCSSValuePairList *list2 = aEndValue.GetCSSValuePairListValue();
+      double squareDistance = 0.0f;
+      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()) {
+            success = PR_FALSE;
+            break; // to failure case
+          }
+          double diff = 0.0;
+          switch (v1.GetUnit()) {
+            case eCSSUnit_Pixel:
+              diff = v1.GetFloatValue() - v2.GetFloatValue();
+              break;
+            case eCSSUnit_Percent:
+              diff = v1.GetPercentValue() - v2.GetPercentValue();
+              break;
+            default:
+              if (v1 != v2) {
+                success = PR_FALSE;
+              }
+              break;
+          }
+          squareDistance += diff * diff;
+        }
+        if (!success) {
+          break; // to failure case
+        }
+
+        list1 = list1->mNext;
+        list2 = list2->mNext;
+      } while (list1 && list2);
+      if (list1 || list2) {
+        // We can't interpolate lists of different lengths.  (Also,
+        // failure cases above break to here.)
+        success = PR_FALSE;
+      } else {
+        aDistance = sqrt(squareDistance);
+      }
+      break;
+    }
     default:
       NS_NOTREACHED("Can't compute distance using the given common unit");
       success = PR_FALSE;
       break;
   }
   return success;
 }
 
@@ -798,16 +846,76 @@ nsStyleAnimation::AddWeighted(nsCSSPrope
           }
 
           longShadow = longShadow->mNext;
         }
       }
       aResultValue.SetAndAdoptCSSValueListValue(result.forget(), eUnit_Shadow);
       break;
     }
+    case eUnit_CSSValuePairList: {
+      const nsCSSValuePairList *list1 = aValue1.GetCSSValuePairListValue();
+      const nsCSSValuePairList *list2 = aValue2.GetCSSValuePairListValue();
+      nsAutoPtr<nsCSSValuePairList> result;
+      nsCSSValuePairList **resultTail = getter_Transfers(result);
+      do {
+        nsCSSValuePairList *item = new nsCSSValuePairList;
+        if (!item) {
+          break; // to failure case
+        }
+        *resultTail = item;
+        resultTail = &item->mNext;
+
+        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()) {
+            success = PR_FALSE;
+            break; // to failure case
+          }
+          switch (v1.GetUnit()) {
+            case eCSSUnit_Pixel:
+              vr.SetFloatValue(aCoeff1 * v1.GetFloatValue() +
+                               aCoeff2 * v2.GetFloatValue(),
+                               eCSSUnit_Pixel);
+              break;
+            case eCSSUnit_Percent:
+              vr.SetPercentValue(aCoeff1 * v1.GetPercentValue() +
+                                 aCoeff2 * v2.GetPercentValue());
+              break;
+            default:
+              if (v1 == v2) {
+                vr = v1;
+              } else {
+                success = PR_FALSE;
+              }
+              break;
+          }
+        }
+        if (!success) {
+          break; // to failure case
+        }
+
+        list1 = list1->mNext;
+        list2 = list2->mNext;
+      } while (list1 && list2);
+      if (list1 || list2) {
+        // We can't interpolate lists of different lengths.  (Also,
+        // failure cases above break to here.)
+        success = PR_FALSE;
+      } else {
+        aResultValue.SetAndAdoptCSSValuePairListValue(result.forget());
+      }
+      break;
+    }
     default:
       NS_NOTREACHED("Can't interpolate using the given common unit");
       success = PR_FALSE;
       break;
   }
   return success;
 }
 
@@ -1047,16 +1155,22 @@ nsStyleAnimation::UncomputeValue(nsCSSPr
       break;
     case eUnit_Dasharray:
     case eUnit_Shadow:
       NS_ABORT_IF_FALSE(nsCSSProps::kTypeTable[aProperty] ==
                           eCSSType_ValueList, "type mismatch");
       *static_cast<nsCSSValueList**>(aSpecifiedValue) =
         aComputedValue.GetCSSValueListValue();
       break;
+    case eUnit_CSSValuePairList:
+      NS_ABORT_IF_FALSE(nsCSSProps::kTypeTable[aProperty] ==
+                          eCSSType_ValuePairList, "type mismatch");
+      *static_cast<nsCSSValuePairList**>(aSpecifiedValue) =
+        aComputedValue.GetCSSValuePairListValue();
+      break;
     default:
       return PR_FALSE;
   }
   return PR_TRUE;
 }
 
 PRBool
 nsStyleAnimation::UncomputeValue(nsCSSProperty aProperty,
@@ -1424,16 +1538,102 @@ nsStyleAnimation::ExtractComputedValue(n
             vrect->mLeft.SetAutoValue();
           } else {
             nscoordToCSSValue(srect.x, vrect->mLeft);
           }
           aComputedValue.SetAndAdoptCSSRectValue(vrect, eUnit_CSSRect);
           break;
         }
 
+        case eCSSProperty_background_position: {
+          const nsStyleBackground *bg =
+            static_cast<const nsStyleBackground*>(styleStruct);
+          nsCSSValuePairList *result = nsnull;
+          nsCSSValuePairList **resultTail = &result;
+          NS_ABORT_IF_FALSE(bg->mPositionCount > 0, "unexpected count");
+          for (PRUint32 i = 0, i_end = bg->mPositionCount; i != i_end; ++i) {
+            nsCSSValuePairList *item = new nsCSSValuePairList;
+            if (!item) {
+              delete result;
+              return PR_FALSE;
+            }
+            *resultTail = item;
+            resultTail = &item->mNext;
+            
+            const nsStyleBackground::Position &pos = bg->mLayers[i].mPosition;
+            if (pos.mXIsPercent) {
+              item->mXValue.SetPercentValue(pos.mXPosition.mFloat);
+            } else {
+              nscoordToCSSValue(pos.mXPosition.mCoord, item->mXValue);
+            }
+            if (pos.mYIsPercent) {
+              item->mYValue.SetPercentValue(pos.mYPosition.mFloat);
+            } else {
+              nscoordToCSSValue(pos.mYPosition.mCoord, item->mYValue);
+            }
+          }
+
+          aComputedValue.SetAndAdoptCSSValuePairListValue(result);
+          break;
+        }
+
+        case eCSSProperty__moz_background_size: {
+          const nsStyleBackground *bg =
+            static_cast<const nsStyleBackground*>(styleStruct);
+          nsCSSValuePairList *result = nsnull;
+          nsCSSValuePairList **resultTail = &result;
+          NS_ABORT_IF_FALSE(bg->mSizeCount > 0, "unexpected count");
+          for (PRUint32 i = 0, i_end = bg->mSizeCount; i != i_end; ++i) {
+            nsCSSValuePairList *item = new nsCSSValuePairList;
+            if (!item) {
+              delete result;
+              return PR_FALSE;
+            }
+            *resultTail = item;
+            resultTail = &item->mNext;
+            
+            const nsStyleBackground::Size &size = bg->mLayers[i].mSize;
+            switch (size.mWidthType) {
+              case nsStyleBackground::Size::eContain:
+              case nsStyleBackground::Size::eCover:
+                item->mXValue.SetIntValue(size.mWidthType,
+                                          eCSSUnit_Enumerated);
+                break;
+              case nsStyleBackground::Size::ePercentage:
+                item->mXValue.SetPercentValue(size.mWidth.mFloat);
+                break;
+              case nsStyleBackground::Size::eAuto:
+                item->mXValue.SetAutoValue();
+                break;
+              case nsStyleBackground::Size::eLength:
+                nscoordToCSSValue(size.mWidth.mCoord, item->mXValue);
+                break;
+            }
+
+            switch (size.mHeightType) {
+              case nsStyleBackground::Size::eContain:
+              case nsStyleBackground::Size::eCover:
+                // leave it null
+                break;
+              case nsStyleBackground::Size::ePercentage:
+                item->mYValue.SetPercentValue(size.mHeight.mFloat);
+                break;
+              case nsStyleBackground::Size::eAuto:
+                item->mYValue.SetAutoValue();
+                break;
+              case nsStyleBackground::Size::eLength:
+                nscoordToCSSValue(size.mHeight.mCoord, item->mYValue);
+                break;
+            }
+          }
+
+          aComputedValue.SetAndAdoptCSSValuePairListValue(result);
+          break;
+        }
+
         default:
           NS_ABORT_IF_FALSE(PR_FALSE, "missing property implementation");
           return PR_FALSE;
       };
       return PR_TRUE;
     case eStyleAnimType_Coord:
       return StyleCoordToValue(*static_cast<const nsStyleCoord*>(
         StyleDataAtOffset(styleStruct, ssOffset)), aComputedValue);
@@ -1649,16 +1849,24 @@ nsStyleAnimation::Value::operator=(const
         mValue.mCSSValueList = aOther.mValue.mCSSValueList->Clone();
         if (!mValue.mCSSValueList) {
           mUnit = eUnit_Null;
         }
       } else {
         mValue.mCSSValueList = nsnull;
       }
       break;
+    case eUnit_CSSValuePairList:
+      NS_ABORT_IF_FALSE(aOther.mValue.mCSSValuePairList,
+                        "value pair lists may not be null");
+      mValue.mCSSValuePairList = aOther.mValue.mCSSValuePairList->Clone();
+      if (!mValue.mCSSValuePairList) {
+        mUnit = eUnit_Null;
+      }
+      break;
     case eUnit_UnparsedString:
       NS_ABORT_IF_FALSE(aOther.mValue.mString, "expecting non-null string");
       mValue.mString = aOther.mValue.mString;
       mValue.mString->AddRef();
       break;
   }
 
   return *this;
@@ -1768,24 +1976,36 @@ nsStyleAnimation::Value::SetAndAdoptCSSV
   NS_ABORT_IF_FALSE(IsCSSValueListUnit(aUnit), "bad unit");
   NS_ABORT_IF_FALSE(aUnit != eUnit_Dasharray || aValueList != nsnull,
                     "dasharrays may not be null");
   mUnit = aUnit;
   mValue.mCSSValueList = aValueList; // take ownership
 }
 
 void
+nsStyleAnimation::Value::SetAndAdoptCSSValuePairListValue(
+                           nsCSSValuePairList *aValuePairList)
+{
+  FreeValue();
+  NS_ABORT_IF_FALSE(aValuePairList, "may not be null");
+  mUnit = eUnit_CSSValuePairList;
+  mValue.mCSSValuePairList = aValuePairList; // take ownership
+}
+
+void
 nsStyleAnimation::Value::FreeValue()
 {
   if (IsCSSValueListUnit(mUnit)) {
     delete mValue.mCSSValueList;
   } else if (IsCSSValuePairUnit(mUnit)) {
     delete mValue.mCSSValuePair;
   } else if (IsCSSRectUnit(mUnit)) {
     delete mValue.mCSSRect;
+  } else if (IsCSSValuePairListUnit(mUnit)) {
+    delete mValue.mCSSValuePairList;
   } else if (IsStringUnit(mUnit)) {
     NS_ABORT_IF_FALSE(mValue.mString, "expecting non-null string");
     mValue.mString->Release();
   }
 }
 
 PRBool
 nsStyleAnimation::Value::operator==(const Value& aOther) const
@@ -1814,16 +2034,19 @@ nsStyleAnimation::Value::operator==(cons
     case eUnit_CSSValuePair:
       return *mValue.mCSSValuePair == *aOther.mValue.mCSSValuePair;
     case eUnit_CSSRect:
       return *mValue.mCSSRect == *aOther.mValue.mCSSRect;
     case eUnit_Dasharray:
     case eUnit_Shadow:
       return nsCSSValueList::Equal(mValue.mCSSValueList,
                                    aOther.mValue.mCSSValueList);
+    case eUnit_CSSValuePairList:
+      return nsCSSValuePairList::Equal(mValue.mCSSValuePairList,
+                                       aOther.mValue.mCSSValuePairList);
     case eUnit_UnparsedString:
       return (NS_strcmp(GetStringBufferValue(),
                         aOther.GetStringBufferValue()) == 0);
   }
 
   NS_NOTREACHED("incomplete case");
   return PR_FALSE;
 }
--- a/layout/style/nsStyleAnimation.h
+++ b/layout/style/nsStyleAnimation.h
@@ -51,16 +51,17 @@
 #include "nsColor.h"
 
 class nsCSSDeclaration;
 class nsIContent;
 class nsPresContext;
 class nsStyleContext;
 struct nsCSSValueList;
 struct nsCSSValuePair;
+struct nsCSSValuePairList;
 struct nsCSSRect;
 
 /**
  * Utility class to handle animated style values
  */
 class nsStyleAnimation {
 public:
   class Value;
@@ -235,30 +236,32 @@ public:
     eUnit_Coord,
     eUnit_Percent,
     eUnit_Float,
     eUnit_Color,
     eUnit_CSSValuePair, // nsCSSValuePair* (never null)
     eUnit_CSSRect, // nsCSSRect* (never null)
     eUnit_Dasharray, // nsCSSValueList* (never null)
     eUnit_Shadow, // nsCSSValueList* (may be null)
+    eUnit_CSSValuePairList, // nsCSSValuePairList* (never null)
     eUnit_UnparsedString // nsStringBuffer* (never null)
   };
 
   class Value {
   private:
     Unit mUnit;
     union {
       PRInt32 mInt;
       nscoord mCoord;
       float mFloat;
       nscolor mColor;
       nsCSSValuePair* mCSSValuePair;
       nsCSSRect* mCSSRect;
       nsCSSValueList* mCSSValueList;
+      nsCSSValuePairList* mCSSValuePairList;
       nsStringBuffer* mString;
     } mValue;
   public:
     Unit GetUnit() const {
       NS_ASSERTION(mUnit != eUnit_Null, "uninitialized");
       return mUnit;
     }
 
@@ -295,16 +298,20 @@ public:
     nsCSSRect* GetCSSRectValue() const {
       NS_ASSERTION(IsCSSRectUnit(mUnit), "unit mismatch");
       return mValue.mCSSRect;
     }
     nsCSSValueList* GetCSSValueListValue() const {
       NS_ASSERTION(IsCSSValueListUnit(mUnit), "unit mismatch");
       return mValue.mCSSValueList;
     }
+    nsCSSValuePairList* GetCSSValuePairListValue() const {
+      NS_ASSERTION(IsCSSValuePairListUnit(mUnit), "unit mismatch");
+      return mValue.mCSSValuePairList;
+    }
     const PRUnichar* GetStringBufferValue() const {
       NS_ASSERTION(IsStringUnit(mUnit), "unit mismatch");
       return GetBufferValue(mValue.mString);
     }
 
     void GetStringValue(nsAString& aBuffer) const {
       NS_ASSERTION(IsStringUnit(mUnit), "unit mismatch");
       aBuffer.Truncate();
@@ -338,19 +345,20 @@ public:
     void SetCoordValue(nscoord aCoord);
     void SetPercentValue(float aPercent);
     void SetFloatValue(float aFloat);
     void SetColorValue(nscolor aColor);
     void SetUnparsedStringValue(const nsString& aString);
 
     // These setters take ownership of |aValue|, and are therefore named
     // "SetAndAdopt*".
-    void SetAndAdoptCSSValueListValue(nsCSSValueList *aValue, Unit aUnit);
     void SetAndAdoptCSSValuePairValue(nsCSSValuePair *aValue, Unit aUnit);
     void SetAndAdoptCSSRectValue(nsCSSRect *aValue, Unit aUnit);
+    void SetAndAdoptCSSValueListValue(nsCSSValueList *aValue, Unit aUnit);
+    void SetAndAdoptCSSValuePairListValue(nsCSSValuePairList *aValue);
 
     Value& operator=(const Value& aOther);
 
     PRBool operator==(const Value& aOther) const;
     PRBool operator!=(const Value& aOther) const
       { return !(*this == aOther); }
 
   private:
@@ -368,15 +376,18 @@ public:
       return aUnit == eUnit_CSSValuePair;
     }
     static PRBool IsCSSRectUnit(Unit aUnit) {
       return aUnit == eUnit_CSSRect;
     }
     static PRBool IsCSSValueListUnit(Unit aUnit) {
       return aUnit == eUnit_Dasharray || aUnit == eUnit_Shadow;
     }
+    static PRBool IsCSSValuePairListUnit(Unit aUnit) {
+      return aUnit == eUnit_CSSValuePairList;
+    }
     static PRBool IsStringUnit(Unit aUnit) {
       return aUnit == eUnit_UnparsedString;
     }
   };
 };
 
 #endif
--- 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-background-size": [ test_background_size_transition ],
     "-moz-border-radius-bottomleft": [ test_radius_transition ],
     "-moz-border-radius-bottomright": [ test_radius_transition ],
     "-moz-border-radius-topleft": [ test_radius_transition ],
     "-moz-border-radius-topright": [ test_radius_transition ],
     "-moz-box-flex": [ test_float_zeroToOne_transition,
                        test_float_aboveOne_transition ],
     "-moz-box-shadow": [ test_shadow_transition ],
     "-moz-column-count": [ test_pos_integer_or_auto_transition ],
@@ -62,16 +63,17 @@ var supported_properties = {
     "-moz-image-region": [ test_rect_transition ],
     "-moz-outline-radius-bottomleft": [ test_radius_transition ],
     "-moz-outline-radius-bottomright": [ test_radius_transition ],
     "-moz-outline-radius-topleft": [ test_radius_transition ],
     "-moz-outline-radius-topright": [ test_radius_transition ],
     "-moz-transform-origin": [ test_length_pair_transition,
                                test_length_percent_pair_transition ],
     "background-color": [ test_color_transition ],
+    "background-position": [ test_background_position_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 ],
     "border-right-color": [ test_color_transition ],
     "border-right-width": [ test_length_transition ],
     "border-spacing": [ test_length_pair_transition ],
     "border-top-color": [ test_color_transition ],
@@ -555,12 +557,60 @@ function test_visibility_transition(prop
   is(cs.getPropertyValue(prop), "visible",
      "visibility property " + prop + ": computed value before transition");
   div.style.setProperty("-moz-transition-property", prop, "");
   div.style.setProperty(prop, "hidden", "");
   is(cs.getPropertyValue(prop), "visible",
      "visibility property " + prop + ": interpolation of visibility");
 }
 
+function test_background_size_transition(prop) {
+  div.style.setProperty("-moz-transition-property", "none", "");
+  div.style.setProperty(prop, "50% 80%", "");
+  is(cs.getPropertyValue(prop), "50% 80%",
+     "property " + prop + ": computed value before transition");
+  div.style.setProperty("-moz-transition-property", prop, "");
+  div.style.setProperty(prop, "100% 100%", "");
+  is(cs.getPropertyValue(prop), "75% 90%",
+     "property " + prop + ": interpolation of percents");
+  div.style.setProperty(prop, "contain", "");
+  is(cs.getPropertyValue(prop), "contain",
+     "property " + prop + ": can't interpolate 'contain'");
+  test_background_position_size_common(prop);
+}
+
+function test_background_position_transition(prop) {
+  div.style.setProperty("-moz-transition-property", "none", "");
+  div.style.setProperty(prop, "center 80%", "");
+  is(cs.getPropertyValue(prop), "50% 80%",
+     "property " + prop + ": computed value before transition");
+  div.style.setProperty("-moz-transition-property", prop, "");
+  div.style.setProperty(prop, "bottom right", "");
+  is(cs.getPropertyValue(prop), "75% 90%",
+     "property " + prop + ": interpolation of percents");
+  test_background_position_size_common(prop);
+}
+
+function test_background_position_size_common(prop) {
+  div.style.setProperty(prop, "10px 40px", "");
+  is(cs.getPropertyValue(prop), "10px 40px",
+     "property " + prop + ": computed value before transition");
+  div.style.setProperty(prop, "50px 0", "");
+  is(cs.getPropertyValue(prop), "30px 20px",
+     "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), "30px 30px, 60px 50px, 30px 30px",
+     "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%",
+     "property " + prop + ": computed value before transition");
+  div.style.setProperty(prop, "50px 20%, 70% 50px, 30% 40%", "");
+  is(cs.getPropertyValue(prop), "30px 30%, 60% 50px, 30% 30%",
+     "property " + prop + ": interpolation of lists of lengths and percents");
+}
+
 </script>
 </pre>
 </body>
 </html>