Bug 1245748 - Return the stored Keyframe objects from GetFrames, when available ; r?heycam draft
authorBrian Birtles <birtles@gmail.com>
Wed, 30 Mar 2016 08:59:08 +0900
changeset 345705 1f7fef05f60ae57a6a19d955f77604e905b5ce22
parent 345704 8325601739de2bf228dd3de38092985106cec00d
child 345706 3a6102002282b7a0ce84a96ccc1b3d8b18423097
push id14148
push userbbirtles@mozilla.com
push dateWed, 30 Mar 2016 04:59:38 +0000
reviewersheycam
bugs1245748
milestone48.0a1
Bug 1245748 - Return the stored Keyframe objects from GetFrames, when available ; r?heycam Before switching CSS animations over to using KeyframeEffectReadOnly::SetFrames we update the getFrames() API to return the set frame objects (when available) so that we can test that we are setting the correct frames. MozReview-Commit-ID: 4SpBRM7Ykyv
dom/animation/KeyframeEffect.cpp
testing/web-platform/tests/web-animations/keyframe-effect/constructor.html
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -1096,21 +1096,90 @@ KeyframeEffectReadOnly::GetProperties(
         propertyDetails.mValues.AppendElement(toValue, mozilla::fallible);
       }
     }
 
     aProperties.AppendElement(propertyDetails);
   }
 }
 
+// TODO: This will eventually become the new GetFrames
+static void
+GetFramesFromFrames(JSContext*& aCx,
+                    const nsTArray<Keyframe>& aFrames,
+                    nsTArray<JSObject*>& aResult,
+                    ErrorResult& aRv)
+{
+  MOZ_ASSERT(aResult.IsEmpty());
+  MOZ_ASSERT(!aRv.Failed());
+
+  if (!aResult.SetCapacity(aFrames.Length(), mozilla::fallible)) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+
+  for (const Keyframe& keyframe : aFrames) {
+    // Set up a dictionary object for the explicit members
+    BaseComputedKeyframe keyframeDict;
+    if (keyframe.mOffset) {
+      keyframeDict.mOffset.SetValue(keyframe.mOffset.value());
+    }
+    keyframeDict.mComputedOffset.Construct(keyframe.mComputedOffset);
+    if (keyframe.mTimingFunction) {
+      keyframeDict.mEasing.Truncate();
+      keyframe.mTimingFunction.ref().AppendToString(keyframeDict.mEasing);
+    } // else if null, leave easing as its default "linear".
+
+    JS::Rooted<JS::Value> keyframeJSValue(aCx);
+    if (!ToJSValue(aCx, keyframeDict, &keyframeJSValue)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
+    }
+
+    JS::Rooted<JSObject*> keyframeObject(aCx, &keyframeJSValue.toObject());
+    for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) {
+
+      const char* name = nsCSSProps::PropertyIDLName(propertyValue.mProperty);
+
+      // nsCSSValue::AppendToString does not accept shorthands properties but
+      // works with token stream values if we pass eCSSProperty_UNKNOWN as
+      // the property.
+      nsCSSProperty propertyForSerializing =
+        nsCSSProps::IsShorthand(propertyValue.mProperty)
+        ? eCSSProperty_UNKNOWN
+        : propertyValue.mProperty;
+
+      nsAutoString stringValue;
+      propertyValue.mValue.AppendToString(
+        propertyForSerializing, stringValue, nsCSSValue::eNormalized);
+
+      JS::Rooted<JS::Value> value(aCx);
+      if (!ToJSValue(aCx, stringValue, &value) ||
+          !JS_DefineProperty(aCx, keyframeObject, name, value,
+                             JSPROP_ENUMERATE)) {
+        aRv.Throw(NS_ERROR_FAILURE);
+        return;
+      }
+    }
+
+    aResult.AppendElement(keyframeObject);
+  }
+}
+
 void
 KeyframeEffectReadOnly::GetFrames(JSContext*& aCx,
                                   nsTArray<JSObject*>& aResult,
                                   ErrorResult& aRv)
 {
+  // Use the specified frames if we have any
+  if (!mFrames.IsEmpty()) {
+    GetFramesFromFrames(aCx, mFrames, aResult, aRv);
+    return;
+  }
+
   nsTArray<OrderedKeyframeValueEntry> entries;
 
   for (const AnimationProperty& property : mProperties) {
     for (size_t i = 0, n = property.mSegments.Length(); i < n; i++) {
       const AnimationPropertySegment& segment = property.mSegments[i];
 
       // We append the mFromValue for each segment.  If the mToValue
       // differs from the following segment's mFromValue, or if we're on
--- a/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html
+++ b/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html
@@ -33,27 +33,16 @@ function assert_frame_lists_equal(a, b) 
   assert_equals(a.length, b.length, "number of frames");
   for (var i = 0; i < Math.min(a.length, b.length); i++) {
     assert_frames_equal(a[i], b[i], "ComputedKeyframe #" + i);
   }
 }
 
 var gEmptyKeyframeListTests = [
   [],
-  [{}],
-  [{ easing: "ease-in" }],
-  [{ unknown: "unknown" }, { unknown: "unknown" }],
-  [{ color: "invalid" }, { color: "invalid" }],
-  { easing: "ease-in" },
-  { unknown: "unknown" },
-  { unknown: [] },
-  { unknown: ["unknown"] },
-  { unknown: ["unknown", "unknown"] },
-  { animationName: ["none", "abc"] },
-  { color: [] },
   null,
   undefined,
 ];
 
 test(function(t) {
   gEmptyKeyframeListTests.forEach(function(frames) {
     assert_equals(new KeyframeEffectReadOnly(target, frames).getFrames().length,
                   0, "number of frames for " + JSON.stringify(frames));
@@ -174,124 +163,126 @@ test(function(t) {
                                  });
   });
 }, "composite values are parsed correctly when passed to the " +
    "KeyframeEffectReadOnly constructor in KeyframeTimingOptions");
 
 var gPropertyIndexedKeyframesTests = [
   { desc:   "a one property two value property-indexed keyframes specification",
     input:  { left: ["10px", "20px"] },
-    output: [{ offset: 0, computedOffset: 0, easing: "linear", left: "10px" },
-             { offset: 1, computedOffset: 1, easing: "linear", left: "20px" }]
-  },
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
+               left: "10px" },
+             { offset: null, computedOffset: 1, easing: "linear",
+               left: "20px" }] },
   { desc:   "a one shorthand property two value property-indexed keyframes"
             + " specification",
     input:  { margin: ["10px", "10px 20px 30px 40px"] },
-    output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               marginTop: "10px", marginRight: "10px",
-               marginBottom: "10px", marginLeft: "10px" },
-             { offset: 1, computedOffset: 1, easing: "linear",
-               marginTop: "10px", marginRight: "20px",
-               marginBottom: "30px", marginLeft: "40px" }] },
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
+               margin: "10px" },
+             { offset: null, computedOffset: 1, easing: "linear",
+               margin: "10px 20px 30px 40px" }] },
   { desc:   "a two property (one shorthand and one of its longhand components)"
             + " two value property-indexed keyframes specification",
     input:  { marginTop: ["50px", "60px"],
               margin: ["10px", "10px 20px 30px 40px"] },
-    output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               marginTop: "50px", marginRight: "10px",
-               marginBottom: "10px", marginLeft: "10px" },
-             { offset: 1, computedOffset: 1, easing: "linear",
-               marginTop: "60px", marginRight: "20px",
-               marginBottom: "30px", marginLeft: "40px" }] },
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
+               marginTop: "50px", margin: "10px" },
+             { offset: null, computedOffset: 1, easing: "linear",
+               marginTop: "60px", margin: "10px 20px 30px 40px" }] },
   { desc:   "a two property two value property-indexed keyframes specification",
     input:  { left: ["10px", "20px"],
               top: ["30px", "40px"] },
-    output: [{ offset: 0, computedOffset: 0, easing: "linear",
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
                left: "10px", top: "30px" },
-             { offset: 1, computedOffset: 1, easing: "linear",
+             { offset: null, computedOffset: 1, easing: "linear",
                left: "20px", top: "40px" }] },
   { desc:   "a two property property-indexed keyframes specification with"
             + " different numbers of values",
     input:  { left: ["10px", "20px", "30px"],
               top: ["40px", "50px"] },
-    output: [{ offset: 0.0, computedOffset: 0.0, easing: "linear",
+    output: [{ offset: null, computedOffset: 0.0, easing: "linear",
                left: "10px", top: "40px" },
-             { offset: 0.5, computedOffset: 0.5, easing: "linear",
+             { offset: null, computedOffset: 0.5, easing: "linear",
                left: "20px" },
-             { offset: 1.0, computedOffset: 1.0, easing: "linear",
+             { offset: null, computedOffset: 1.0, easing: "linear",
                left: "30px", top: "50px" }] },
   { desc:   "a property-indexed keyframes specification with an invalid value",
     input:  { left: ["10px", "20px", "30px", "40px", "50px"],
               top:  ["15px", "25px", "invalid", "45px", "55px"] },
-    output: [{ offset: 0.00, computedOffset: 0.00, easing: "linear",
+    output: [{ offset: null, computedOffset: 0.00, easing: "linear",
                left: "10px", top: "15px" },
-             { offset: 0.25, computedOffset: 0.25, easing: "linear",
+             { offset: null, computedOffset: 0.25, easing: "linear",
                left: "20px", top: "25px" },
-             { offset: 0.50, computedOffset: 0.50, easing: "linear",
-               left: "30px" },
-             { offset: 0.75, computedOffset: 0.75, easing: "linear",
+             { offset: null, computedOffset: 0.50, easing: "linear",
+               left: "30px", top: "invalid" },
+             { offset: null, computedOffset: 0.75, easing: "linear",
                left: "40px", top: "45px" },
-             { offset: 1.00, computedOffset: 1.00, easing: "linear",
+             { offset: null, computedOffset: 1.00, easing: "linear",
                left: "50px", top: "55px" }] },
   { desc:   "a one property two value property-indexed keyframes specification"
             + " that needs to stringify its values",
     input:  { opacity: [0, 1] },
-    output: [{ offset: 0, computedOffset: 0, easing: "linear", opacity: "0" },
-             { offset: 1, computedOffset: 1, easing: "linear", opacity: "1" }]
-  },
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
+               opacity: "0" },
+             { offset: null, computedOffset: 1, easing: "linear",
+               opacity: "1" }] },
   { desc:   "a one property one value property-indexed keyframes specification",
     input:  { left: ["10px"] },
-    output: [{ offset: 0, computedOffset: 0, easing: "linear" },
-             { offset: 1, computedOffset: 1, easing: "linear", left: "10px" }]
-  },
+    output: [{ offset: null, computedOffset: 1, easing: "linear",
+               left: "10px" }] },
   { desc:   "a one property one non-array value property-indexed keyframes"
             + " specification",
     input:  { left: "10px" },
-    output: [{ offset: 0, computedOffset: 0, easing: "linear" },
-             { offset: 1, computedOffset: 1, easing: "linear", left: "10px" }] },
+    output: [{ offset: null, computedOffset: 1, easing: "linear",
+               left: "10px" }] },
   { desc:   "a one property two value property-indexed keyframes specification"
             + " where the first value is invalid",
     input:  { left: ["invalid", "10px"] },
-    output: [{ offset: 0, computedOffset: 0, easing: "linear" },
-             { offset: 1, computedOffset: 1, easing: "linear", left: "10px" }] },
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
+               left: "invalid" },
+             { offset: null, computedOffset: 1, easing: "linear",
+               left: "10px" }] },
   { desc:   "a one property two value property-indexed keyframes specification"
             + " where the second value is invalid",
     input:  { left: ["10px", "invalid"] },
-    output: [{ offset: 0, computedOffset: 0, easing: "linear", left: "10px" },
-             { offset: 1, computedOffset: 1, easing: "linear" }] },
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
+               left: "10px" },
+             { offset: null, computedOffset: 1, easing: "linear",
+               left: "invalid" }] },
   { desc:   "a two property property-indexed keyframes specification where one"
             + " property is missing from the first keyframe",
     input:  [{ offset: 0, left: "10px" },
              { offset: 1, left: "20px", top: "30px" }],
     output: [{ offset: 0, computedOffset: 0, easing: "linear", left: "10px" },
              { offset: 1, computedOffset: 1, easing: "linear",
                left: "20px", top: "30px" }] },
   { desc:   "a two property property-indexed keyframes specification where one"
             + " property is missing from the last keyframe",
     input:  [{ offset: 0, left: "10px", top: "20px" },
              { offset: 1, left: "30px" }],
     output: [{ offset: 0, computedOffset: 0, easing: "linear",
                left: "10px" , top: "20px" },
-             { offset: 1, computedOffset: 1, easing: "linear", left: "30px" }] },
+             { offset: 1, computedOffset: 1, easing: "linear",
+               left: "30px" }] },
   { desc:   "a property-indexed keyframes specification with repeated values"
             + " at offset 0 with different easings",
     input:  [{ offset: 0.0, left: "100px", easing: "ease" },
              { offset: 0.0, left: "200px", easing: "ease" },
              { offset: 0.5, left: "300px", easing: "linear" },
              { offset: 1.0, left: "400px", easing: "ease-out" },
              { offset: 1.0, left: "500px", easing: "step-end" }],
     output: [{ offset: 0.0, computedOffset: 0.0, easing: "ease",
                left: "100px" },
              { offset: 0.0, computedOffset: 0.0, easing: "ease",
                left: "200px" },
              { offset: 0.5, computedOffset: 0.5, easing: "linear",
                left: "300px" },
              { offset: 1.0, computedOffset: 1.0, easing: "ease-out",
                left: "400px" },
-             { offset: 1.0, computedOffset: 1.0, easing: "linear",
+             { offset: 1.0, computedOffset: 1.0, easing: "step-end",
                left: "500px" }] },
 ];
 
 gPropertyIndexedKeyframesTests.forEach(function(subtest) {
   test(function(t) {
     var effect = new KeyframeEffectReadOnly(target, subtest.input);
     assert_frame_lists_equal(effect.getFrames(), subtest.output);
   }, "a KeyframeEffectReadOnly can be constructed with " + subtest.desc);
@@ -337,199 +328,198 @@ var gKeyframeSequenceTests = [
     output: [{ offset: 0, computedOffset: 0, easing: "linear",
                left: "10px", top: "30px" },
              { offset: 1, computedOffset: 1, easing: "linear",
                left: "20px", top: "40px" }] },
   { desc:   "a one shorthand property two keyframe sequence",
     input:  [{ offset: 0, margin: "10px" },
              { offset: 1, margin: "20px 30px 40px 50px" }],
     output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               marginTop: "10px", marginRight: "10px",
-               marginBottom: "10px", marginLeft: "10px" },
+               margin: "10px" },
              { offset: 1, computedOffset: 1, easing: "linear",
-               marginTop: "20px", marginRight: "30px",
-               marginBottom: "40px", marginLeft: "50px" }] },
+               margin: "20px 30px 40px 50px" }] },
   { desc:   "a two property (a shorthand and one of its component longhands)"
             + " two keyframe sequence",
     input:  [{ offset: 0, margin: "10px", marginTop: "20px" },
              { offset: 1, marginTop: "70px", margin: "30px 40px 50px 60px" }],
     output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               marginTop: "20px", marginRight: "10px",
-               marginBottom: "10px", marginLeft: "10px" },
+               margin: "10px", marginTop: "20px" },
              { offset: 1, computedOffset: 1, easing: "linear",
-               marginTop: "70px", marginRight: "40px",
-               marginBottom: "50px", marginLeft: "60px" }] },
+               marginTop: "70px", margin: "30px 40px 50px 60px" }] },
   { desc:   "a keyframe sequence with duplicate values for a given interior"
             + " offset",
     input:  [{ offset: 0.0, left: "10px" },
              { offset: 0.5, left: "20px" },
              { offset: 0.5, left: "30px" },
              { offset: 0.5, left: "40px" },
              { offset: 1.0, left: "50px" }],
     output: [{ offset: 0.0, computedOffset: 0.0, easing: "linear",
                left: "10px" },
              { offset: 0.5, computedOffset: 0.5, easing: "linear",
                left: "20px" },
              { offset: 0.5, computedOffset: 0.5, easing: "linear",
+               left: "30px" },
+             { offset: 0.5, computedOffset: 0.5, easing: "linear",
                left: "40px" },
              { offset: 1.0, computedOffset: 1.0, easing: "linear",
                left: "50px" }] },
   { desc:   "a keyframe sequence with duplicate values for offsets 0 and 1",
     input:  [{ offset: 0, left: "10px" },
              { offset: 0, left: "20px" },
              { offset: 0, left: "30px" },
              { offset: 1, left: "40px" },
              { offset: 1, left: "50px" },
              { offset: 1, left: "60px" }],
     output: [{ offset: 0, computedOffset: 0, easing: "linear", left: "10px" },
+             { offset: 0, computedOffset: 0, easing: "linear", left: "20px" },
              { offset: 0, computedOffset: 0, easing: "linear", left: "30px" },
              { offset: 1, computedOffset: 1, easing: "linear", left: "40px" },
+             { offset: 1, computedOffset: 1, easing: "linear", left: "50px" },
              { offset: 1, computedOffset: 1, easing: "linear", left: "60px" }]
   },
   { desc:   "a two property four keyframe sequence",
     input:  [{ offset: 0, left: "10px" },
              { offset: 0, top: "20px" },
              { offset: 1, top: "30px" },
              { offset: 1, left: "40px" }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               left: "10px", top: "20px" },
-             { offset: 1, computedOffset: 1, easing: "linear",
-               left: "40px", top: "30px" }] },
+    output: [{ offset: 0, computedOffset: 0, easing: "linear", left: "10px" },
+             { offset: 0, computedOffset: 0, easing: "linear", top: "20px" },
+             { offset: 1, computedOffset: 1, easing: "linear", top: "30px" },
+             { offset: 1, computedOffset: 1, easing: "linear", left: "40px" }]
+  },
   { desc:   "a one property keyframe sequence with some omitted offsets",
     input:  [{ offset: 0.00, left: "10px" },
              { offset: 0.25, left: "20px" },
              { left: "30px" },
              { left: "40px" },
              { offset: 1.00, left: "50px" }],
     output: [{ offset: 0.00, computedOffset: 0.00, easing: "linear",
                left: "10px" },
              { offset: 0.25, computedOffset: 0.25, easing: "linear",
                left: "20px" },
-             { offset: 0.50, computedOffset: 0.50, easing: "linear",
+             { offset: null, computedOffset: 0.50, easing: "linear",
                left: "30px" },
-             { offset: 0.75, computedOffset: 0.75, easing: "linear",
+             { offset: null, computedOffset: 0.75, easing: "linear",
                left: "40px" },
              { offset: 1.00, computedOffset: 1.00, easing: "linear",
                left: "50px" }] },
   { desc:   "a two property keyframe sequence with some omitted offsets",
     input:  [{ offset: 0.00, left: "10px", top: "20px" },
              { offset: 0.25, left: "30px" },
              { left: "40px" },
              { left: "50px", top: "60px" },
              { offset: 1.00, left: "70px", top: "80px" }],
     output: [{ offset: 0.00, computedOffset: 0.00, easing: "linear",
                left: "10px", top: "20px" },
              { offset: 0.25, computedOffset: 0.25, easing: "linear",
                left: "30px" },
-             { offset: 0.50, computedOffset: 0.50, easing: "linear",
+             { offset: null, computedOffset: 0.50, easing: "linear",
                left: "40px" },
-             { offset: 0.75, computedOffset: 0.75, easing: "linear",
+             { offset: null, computedOffset: 0.75, easing: "linear",
                left: "50px", top: "60px" },
              { offset: 1.00, computedOffset: 1.00, easing: "linear",
                left: "70px", top: "80px" }] },
   { desc:   "a one property keyframe sequence with all omitted offsets",
     input:  [{ left: "10px" },
              { left: "20px" },
              { left: "30px" },
              { left: "40px" },
              { left: "50px" }],
-    output: [{ offset: 0.00, computedOffset: 0.00, easing: "linear",
+    output: [{ offset: null, computedOffset: 0.00, easing: "linear",
                left: "10px" },
-             { offset: 0.25, computedOffset: 0.25, easing: "linear",
+             { offset: null, computedOffset: 0.25, easing: "linear",
                left: "20px" },
-             { offset: 0.50, computedOffset: 0.50, easing: "linear",
+             { offset: null, computedOffset: 0.50, easing: "linear",
                left: "30px" },
-             { offset: 0.75, computedOffset: 0.75, easing: "linear",
+             { offset: null, computedOffset: 0.75, easing: "linear",
                left: "40px" },
-             { offset: 1.00, computedOffset: 1.00, easing: "linear",
+             { offset: null, computedOffset: 1.00, easing: "linear",
                left: "50px" }] },
   { desc:   "a keyframe sequence with different easing values, but the same"
             + " easing value for a given offset",
     input:  [{ offset: 0.0, easing: "ease",     left: "10px"},
              { offset: 0.0, easing: "ease",     top: "20px"},
              { offset: 0.5, easing: "linear",   left: "30px" },
              { offset: 0.5, easing: "linear",   top: "40px" },
              { offset: 1.0, easing: "step-end", left: "50px" },
              { offset: 1.0, easing: "step-end", top: "60px" }],
     output: [{ offset: 0.0, computedOffset: 0.0, easing: "ease",
-               left: "10px", top: "20px" },
+               left: "10px" },
+             { offset: 0.0, computedOffset: 0.0, easing: "ease",
+               top: "20px" },
+             { offset: 0.5, computedOffset: 0.5, easing: "linear",
+               left: "30px" },
              { offset: 0.5, computedOffset: 0.5, easing: "linear",
-               left: "30px", top: "40px" },
-             { offset: 1.0, computedOffset: 1.0, easing: "linear",
-               left: "50px", top: "60px" }] },
+               top: "40px" },
+             { offset: 1.0, computedOffset: 1.0, easing: "step-end",
+               left: "50px" },
+             { offset: 1.0, computedOffset: 1.0, easing: "step-end",
+               top: "60px" }] },
   { desc:   "a keyframe sequence with different composite values, but the"
             + " same composite value for a given offset",
     input:  [{ offset: 0.0, composite: "replace", left: "10px" },
              { offset: 0.0, composite: "replace", top: "20px" },
              { offset: 0.5, composite: "add",     left: "30px" },
              { offset: 0.5, composite: "add",     top: "40px" },
              { offset: 1.0, composite: "replace", left: "50px" },
              { offset: 1.0, composite: "replace", top: "60px" }],
     output: [{ offset: 0.0, computedOffset: 0.0, easing: "linear",
-               composite: "replace", left: "10px", top: "20px" },
-             { offset: 0.5, computedOffset: 0.5, easing: "linear",
-               composite: "add",     left: "30px", top: "40px" },
+               composite: "replace", left: "10px" },
+             { offset: 0.0, computedOffset: 0.0, easing: "linear",
+               composite: "replace", top: "20px" },
+             { offset: 0.5, computedOffset: 0.0, easing: "linear",
+               composite: "add", left: "30px" },
+             { offset: 0.5, computedOffset: 0.0, easing: "linear",
+               composite: "add", top: "40px" },
              { offset: 1.0, computedOffset: 1.0, easing: "linear",
-               composite: "replace", left: "50px", top: "60px" }] },
+               composite: "replace", left: "50px" },
+             { offset: 1.0, computedOffset: 1.0, easing: "linear",
+               composite: "replace", top: "60px" }] },
   { desc:   "a one property two keyframe sequence that needs to stringify"
             + " its values",
     input:  [{ offset: 0, opacity: 0 },
              { offset: 1, opacity: 1 }],
     output: [{ offset: 0, computedOffset: 0, easing: "linear", opacity: "0" },
              { offset: 1, computedOffset: 1, easing: "linear", opacity: "1" }]
   },
   { desc:   "a keyframe sequence where shorthand precedes longhand",
     input:  [{ offset: 0, margin: "10px", marginRight: "20px" },
              { offset: 1, margin: "30px" }],
     output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               marginBottom: "10px", marginLeft: "10px",
-               marginRight: "20px", marginTop: "10px" },
+               margin: "10px", marginRight: "20px" },
              { offset: 1, computedOffset: 1, easing: "linear",
-               marginBottom: "30px", marginLeft: "30px",
-               marginRight: "30px", marginTop: "30px" }] },
+               margin: "30px" }] },
   { desc:   "a keyframe sequence where longhand precedes shorthand",
     input:  [{ offset: 0, marginRight: "20px", margin: "10px" },
              { offset: 1, margin: "30px" }],
     output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               marginBottom: "10px", marginLeft: "10px",
-               marginRight: "20px", marginTop: "10px" },
+               marginRight: "20px", margin: "10px" },
              { offset: 1, computedOffset: 1, easing: "linear",
-               marginBottom: "30px", marginLeft: "30px",
-               marginRight: "30px", marginTop: "30px" }] },
+               margin: "30px" }] },
   { desc:   "a keyframe sequence where lesser shorthand precedes greater"
             + " shorthand",
     input:  [{ offset: 0,
                borderLeft: "1px solid rgb(1, 2, 3)",
                border: "2px dotted rgb(4, 5, 6)" },
              { offset: 1, border: "3px dashed rgb(7, 8, 9)" }],
     output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               borderBottomColor: "rgb(4, 5, 6)", borderBottomWidth: "2px",
-               borderLeftColor:   "rgb(1, 2, 3)", borderLeftWidth:   "1px",
-               borderRightColor:  "rgb(4, 5, 6)", borderRightWidth:  "2px",
-               borderTopColor:    "rgb(4, 5, 6)", borderTopWidth:    "2px" },
+               borderLeft: "1px solid rgb(1, 2, 3)",
+               border: "2px dotted rgb(4, 5, 6)" },
              { offset: 1, computedOffset: 1, easing: "linear",
-               borderBottomColor: "rgb(7, 8, 9)", borderBottomWidth: "3px",
-               borderLeftColor:   "rgb(7, 8, 9)", borderLeftWidth:   "3px",
-               borderRightColor:  "rgb(7, 8, 9)", borderRightWidth:  "3px",
-               borderTopColor:    "rgb(7, 8, 9)", borderTopWidth:    "3px" }] },
+               border: "3px dashed rgb(7, 8, 9)" }] },
   { desc:   "a keyframe sequence where greater shorthand precedes lesser"
             + " shorthand",
     input:  [{ offset: 0, border: "2px dotted rgb(4, 5, 6)",
                borderLeft: "1px solid rgb(1, 2, 3)" },
              { offset: 1, border: "3px dashed rgb(7, 8, 9)" }],
     output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               borderBottomColor: "rgb(4, 5, 6)", borderBottomWidth: "2px",
-               borderLeftColor:   "rgb(1, 2, 3)", borderLeftWidth:   "1px",
-               borderRightColor:  "rgb(4, 5, 6)", borderRightWidth:  "2px",
-               borderTopColor:    "rgb(4, 5, 6)", borderTopWidth:    "2px" },
+               border: "2px dotted rgb(4, 5, 6)",
+               borderLeft: "1px solid rgb(1, 2, 3)" },
              { offset: 1, computedOffset: 1, easing: "linear",
-               borderBottomColor: "rgb(7, 8, 9)", borderBottomWidth: "3px",
-               borderLeftColor:   "rgb(7, 8, 9)", borderLeftWidth:   "3px",
-               borderRightColor:  "rgb(7, 8, 9)", borderRightWidth:  "3px",
-               borderTopColor:    "rgb(7, 8, 9)", borderTopWidth:    "3px" }] },
+               border: "3px dashed rgb(7, 8, 9)" }] },
 ];
 
 gKeyframeSequenceTests.forEach(function(subtest) {
   test(function(t) {
     var effect = new KeyframeEffectReadOnly(target, subtest.input);
     assert_frame_lists_equal(effect.getFrames(), subtest.output);
   }, "a KeyframeEffectReadOnly can be constructed with " + subtest.desc);