Bug 1305325 - Part 6: Handle missing keyframe whose offset 0 or 1 on the main thread. r?birtles draft
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Sun, 04 Dec 2016 08:07:40 +0900
changeset 447341 c0ebc18dc03a72fac3cd40d9a4c2c4466fc477c3
parent 447340 8fb7a199bc753f84bd245d56cdf93f48d880486f
child 447342 604abb36f3552b34ab9b0e83d9b53dab96a8b195
push id38039
push userhiikezoe@mozilla-japan.org
push dateSat, 03 Dec 2016 23:26:35 +0000
reviewersbirtles
bugs1305325
milestone53.0a1
Bug 1305325 - Part 6: Handle missing keyframe whose offset 0 or 1 on the main thread. r?birtles MozReview-Commit-ID: 5WMiTJQKfZd
dom/animation/KeyframeEffectParams.h
dom/animation/KeyframeEffectReadOnly.cpp
dom/animation/KeyframeEffectReadOnly.h
dom/animation/KeyframeUtils.cpp
dom/animation/KeyframeUtils.h
dom/animation/test/chrome.ini
dom/animation/test/chrome/test_animation_properties.html
dom/animation/test/chrome/test_restyles.html
dom/animation/test/chrome/test_simulate_compute_values_failure.html
layout/base/nsLayoutUtils.cpp
testing/web-platform/meta/web-animations/interfaces/Animatable/animate.html.ini
testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/constructor.html.ini
testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/setKeyframes.html.ini
--- a/dom/animation/KeyframeEffectParams.h
+++ b/dom/animation/KeyframeEffectParams.h
@@ -53,16 +53,16 @@ struct KeyframeEffectParams
   static void ParseSpacing(const nsAString& aSpacing,
                            SpacingMode& aSpacingMode,
                            nsCSSPropertyID& aPacedProperty,
                            nsAString& aInvalidPacedProperty,
                            ErrorResult& aRv);
 
   dom::IterationCompositeOperation mIterationComposite =
     dom::IterationCompositeOperation::Replace;
-  // FIXME: Bug 1216844: Add CompositeOperation
+  dom::CompositeOperation mComposite = dom::CompositeOperation::Replace;
   SpacingMode mSpacingMode = SpacingMode::distribute;
   nsCSSPropertyID mPacedProperty = eCSSProperty_UNKNOWN;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_KeyframeEffectParams_h
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -300,16 +300,80 @@ KeyframeEffectReadOnly::UpdateProperties
     CalculateCumulativeChangeHint(aStyleContext);
   }
 
   MarkCascadeNeedsUpdate();
 
   RequestRestyle(EffectCompositor::RestyleType::Layer);
 }
 
+StyleAnimationValue
+KeyframeEffectReadOnly::CompositeValue(
+  nsCSSPropertyID aProperty,
+  const RefPtr<AnimValuesStyleRule>& aAnimationRule,
+  const StyleAnimationValue& aValueToComposite,
+  CompositeOperation aCompositeOperation)
+{
+  MOZ_ASSERT(mTarget, "CompositeValue should be called with target element");
+
+  StyleAnimationValue result = aValueToComposite;
+
+  if (aCompositeOperation == CompositeOperation::Replace) {
+    MOZ_ASSERT(!aValueToComposite.IsNull(),
+      "Input value should be valid in case of replace composite");
+    // Just copy the input value in case of 'Replace'.
+    return result;
+  }
+
+  // FIXME: Bug 1311257: Get the base value for the servo backend.
+  if (mDocument->IsStyledByServo()) {
+    return result;
+  }
+
+  MOZ_ASSERT(!aValueToComposite.IsNull() ||
+             aCompositeOperation == CompositeOperation::Add,
+             "InputValue should be null only if additive composite");
+
+  if (aAnimationRule->HasValue(aProperty)) {
+    // If we have already composed style for the property, we use the style
+    // as the underlying style.
+    DebugOnly<bool> success = aAnimationRule->GetValue(aProperty, result);
+    MOZ_ASSERT(success, "AnimValuesStyleRule::GetValue should not fail");
+  } else {
+    // If we are composing with composite operation that is not 'replace'
+    // and we have not composed style for the property yet, we have to get
+    // the base style for the property.
+    RefPtr<nsStyleContext> styleContext = GetTargetStyleContext();
+    result = EffectCompositor::GetBaseStyle(aProperty,
+                                            styleContext,
+                                            *mTarget->mElement);
+  }
+
+  switch (aCompositeOperation) {
+    case dom::CompositeOperation::Add:
+      // So far nothing to do since we come to here only in case of missing
+      // keyframe, that means we just use the base value as the composited
+      // value.
+      // FIXME: Bug 1311620: Once we implement additive composition, we need to
+      // use it here only if |aValueToCompose| is not null.
+      return result;
+    case dom::CompositeOperation::Accumulate:
+      // FIXME: Bug 1291468: Implement accumulate operation.
+      MOZ_ASSERT_UNREACHABLE("Not implemented yet");
+      break;
+    case dom::CompositeOperation::Replace:
+      MOZ_ASSERT_UNREACHABLE("Replace should have already handled");
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown compisite operation type");
+      break;
+  }
+  return result;
+}
+
 void
 KeyframeEffectReadOnly::ComposeStyle(
   RefPtr<AnimValuesStyleRule>& aStyleRule,
   const nsCSSPropertyIDSet& aPropertiesToSkip)
 {
   ComputedTiming computedTiming = GetComputedTiming();
   mProgressOnLastCompose = computedTiming.mProgress;
   mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration;
@@ -353,18 +417,25 @@ KeyframeEffectReadOnly::ComposeStyle(
                  prop.mSegments.Length(),
                "out of array bounds");
 
     if (!aStyleRule) {
       // Allocate the style rule now that we know we have animation data.
       aStyleRule = new AnimValuesStyleRule();
     }
 
-    StyleAnimationValue fromValue = segment->mFromValue;
-    StyleAnimationValue toValue = segment->mToValue;
+    StyleAnimationValue fromValue =
+      CompositeValue(prop.mProperty, aStyleRule,
+                     segment->mFromValue,
+                     segment->mFromComposite);
+    StyleAnimationValue toValue =
+      CompositeValue(prop.mProperty, aStyleRule,
+                     segment->mToValue,
+                     segment->mToComposite);
+
     // Iteration composition for accumulate
     if (mEffectOptions.mIterationComposite ==
           IterationCompositeOperation::Accumulate &&
         computedTiming.mCurrentIteration > 0) {
       const AnimationPropertySegment& lastSegment =
         prop.mSegments.LastElement();
       // FIXME: Bug 1293492: Add a utility function to calculate both of
       // below StyleAnimationValues.
@@ -634,19 +705,21 @@ KeyframeEffectReadOnly::BuildProperties(
                                              aStyleContext);
 
   if (mEffectOptions.mSpacingMode == SpacingMode::paced) {
     KeyframeUtils::ApplySpacing(keyframesCopy, SpacingMode::paced,
                                 mEffectOptions.mPacedProperty,
                                 computedValues, aStyleContext);
   }
 
-  result = KeyframeUtils::GetAnimationPropertiesFromKeyframes(keyframesCopy,
-                                                              computedValues,
-                                                              aStyleContext);
+  result =
+    KeyframeUtils::GetAnimationPropertiesFromKeyframes(keyframesCopy,
+                                                       computedValues,
+                                                       mEffectOptions.mComposite,
+                                                       aStyleContext);
 
 #ifdef DEBUG
   MOZ_ASSERT(SpecifiedKeyframeArraysAreEqual(mKeyframes, keyframesCopy),
              "Apart from the computed offset members, the keyframes array"
              " should not be modified");
 #endif
 
   mKeyframes.SwapElements(keyframesCopy);
@@ -791,16 +864,17 @@ KeyframeEffectReadOnly::GetTarget(
   }
 }
 
 static void
 CreatePropertyValue(nsCSSPropertyID aProperty,
                     float aOffset,
                     const Maybe<ComputedTimingFunction>& aTimingFunction,
                     const StyleAnimationValue& aValue,
+                    dom::CompositeOperation aComposite,
                     AnimationPropertyValueDetails& aResult)
 {
   aResult.mOffset = aOffset;
 
   if (!aValue.IsNull()) {
     nsString stringValue;
     DebugOnly<bool> uncomputeResult =
       StyleAnimationValue::UncomputeValue(aProperty, aValue, stringValue);
@@ -810,17 +884,17 @@ CreatePropertyValue(nsCSSPropertyID aPro
 
   if (aTimingFunction) {
     aResult.mEasing.Construct();
     aTimingFunction->AppendToString(aResult.mEasing.Value());
   } else {
     aResult.mEasing.Construct(NS_LITERAL_STRING("linear"));
   }
 
-  aResult.mComposite = CompositeOperation::Replace;
+  aResult.mComposite = aComposite;
 }
 
 void
 KeyframeEffectReadOnly::GetProperties(
     nsTArray<AnimationPropertyDetails>& aProperties,
     ErrorResult& aRv) const
 {
   for (const AnimationProperty& property : mProperties) {
@@ -845,17 +919,17 @@ KeyframeEffectReadOnly::GetProperties(
          segmentIdx < segmentLen;
          segmentIdx++)
     {
       const AnimationPropertySegment& segment = property.mSegments[segmentIdx];
 
       binding_detail::FastAnimationPropertyValueDetails fromValue;
       CreatePropertyValue(property.mProperty, segment.mFromKey,
                           segment.mTimingFunction, segment.mFromValue,
-                          fromValue);
+                          segment.mFromComposite, fromValue);
       // We don't apply timing functions for zero-length segments, so
       // don't return one here.
       if (segment.mFromKey == segment.mToKey) {
         fromValue.mEasing.Reset();
       }
       // The following won't fail since we have already allocated the capacity
       // above.
       propertyDetails.mValues.AppendElement(fromValue, mozilla::fallible);
@@ -864,17 +938,18 @@ KeyframeEffectReadOnly::GetProperties(
       // identical to the from-value from the next segment. However, we need
       // to add it if either:
       // a) this is the last segment, or
       // b) the next segment's from-value differs.
       if (segmentIdx == segmentLen - 1 ||
           property.mSegments[segmentIdx + 1].mFromValue != segment.mToValue) {
         binding_detail::FastAnimationPropertyValueDetails toValue;
         CreatePropertyValue(property.mProperty, segment.mToKey,
-                            Nothing(), segment.mToValue, toValue);
+                            Nothing(), segment.mToValue,
+                            segment.mToComposite, toValue);
         // It doesn't really make sense to have a timing function on the
         // last property value or before a sudden jump so we just drop the
         // easing property altogether.
         toValue.mEasing.Reset();
         propertyDetails.mValues.AppendElement(toValue, mozilla::fallible);
       }
     }
 
@@ -1300,16 +1375,25 @@ CreateStyleContextForAnimationValue(nsCS
 void
 KeyframeEffectReadOnly::CalculateCumulativeChangeHint(
   nsStyleContext *aStyleContext)
 {
   mCumulativeChangeHint = nsChangeHint(0);
 
   for (const AnimationProperty& property : mProperties) {
     for (const AnimationPropertySegment& segment : property.mSegments) {
+      // In case composite operation is not 'replace', we can't throttle
+      // animations which will not cause any layout changes on invisible
+      // elements because we can't calculate the change hint for such properties
+      // until we compose it.
+      if (segment.mFromComposite != CompositeOperation::Replace ||
+          segment.mToComposite != CompositeOperation::Replace) {
+        mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
+        return;
+      }
       RefPtr<nsStyleContext> fromContext =
         CreateStyleContextForAnimationValue(property.mProperty,
                                             segment.mFromValue, aStyleContext);
 
       RefPtr<nsStyleContext> toContext =
         CreateStyleContextForAnimationValue(property.mProperty,
                                             segment.mToValue, aStyleContext);
 
--- a/dom/animation/KeyframeEffectReadOnly.h
+++ b/dom/animation/KeyframeEffectReadOnly.h
@@ -98,41 +98,49 @@ struct Keyframe
   }
 
   Keyframe& operator=(const Keyframe& aOther) = default;
   Keyframe& operator=(Keyframe&& aOther)
   {
     mOffset         = aOther.mOffset;
     mComputedOffset = aOther.mComputedOffset;
     mTimingFunction = Move(aOther.mTimingFunction);
+    mComposite      = Move(aOther.mComposite);
     mPropertyValues = Move(aOther.mPropertyValues);
     return *this;
   }
 
   Maybe<double>                 mOffset;
   static constexpr double kComputedOffsetNotSet = -1.0;
   double                        mComputedOffset = kComputedOffsetNotSet;
   Maybe<ComputedTimingFunction> mTimingFunction; // Nothing() here means
                                                  // "linear"
+  Maybe<dom::CompositeOperation> mComposite;
   nsTArray<PropertyValuePair>   mPropertyValues;
 };
 
 struct AnimationPropertySegment
 {
   float mFromKey, mToKey;
+  // NOTE: In the case that no keyframe for 0 or 1 offset is specified
+  // the unit of mFromValue or mToValue is eUnit_Null.
   StyleAnimationValue mFromValue, mToValue;
   Maybe<ComputedTimingFunction> mTimingFunction;
+  dom::CompositeOperation mFromComposite = dom::CompositeOperation::Replace;
+  dom::CompositeOperation mToComposite = dom::CompositeOperation::Replace;
 
   bool operator==(const AnimationPropertySegment& aOther) const
   {
     return mFromKey == aOther.mFromKey &&
            mToKey == aOther.mToKey &&
            mFromValue == aOther.mFromValue &&
            mToValue == aOther.mToValue &&
-           mTimingFunction == aOther.mTimingFunction;
+           mTimingFunction == aOther.mTimingFunction &&
+           mFromComposite == aOther.mFromComposite &&
+           mToComposite == aOther.mToComposite;
   }
   bool operator!=(const AnimationPropertySegment& aOther) const
   {
     return !(*this == aOther);
   }
 };
 
 struct AnimationProperty
@@ -381,16 +389,26 @@ protected:
   // infinite recursion.
   already_AddRefed<nsStyleContext>
   GetTargetStyleContext();
 
   // A wrapper for marking cascade update according to the current
   // target and its effectSet.
   void MarkCascadeNeedsUpdate();
 
+  // Composites |aValueToComposite| using |aCompositeOperation| onto the value
+  // for |aProperty| in |aAnimationRule|, or, if there is no suitable value in
+  // |aAnimationRule|, uses the base value for the property recorded on the
+  // target element's EffectSet.
+  StyleAnimationValue CompositeValue(
+    nsCSSPropertyID aProperty,
+    const RefPtr<AnimValuesStyleRule>& aAnimationRule,
+    const StyleAnimationValue& aValueToComposite,
+    CompositeOperation aCompositeOperation);
+
   Maybe<OwningAnimationTarget> mTarget;
 
   KeyframeEffectParams mEffectOptions;
 
   // The specified keyframes.
   nsTArray<Keyframe>          mKeyframes;
 
   // A set of per-property value arrays, derived from |mKeyframes|.
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -267,16 +267,17 @@ struct AdditionalProperty
  * to gather data for each individual segment.
  */
 struct KeyframeValueEntry
 {
   nsCSSPropertyID mProperty;
   StyleAnimationValue mValue;
   float mOffset;
   Maybe<ComputedTimingFunction> mTimingFunction;
+  dom::CompositeOperation mComposite;
 
   struct PropertyOffsetComparator
   {
     static bool Equals(const KeyframeValueEntry& aLhs,
                        const KeyframeValueEntry& aRhs)
     {
       return aLhs.mProperty == aRhs.mProperty &&
              aLhs.mOffset == aRhs.mOffset;
@@ -459,23 +460,20 @@ KeyframeUtils::GetKeyframesFromObject(JS
   }
 
   if (aRv.Failed()) {
     MOZ_ASSERT(keyframes.IsEmpty(),
                "Should not set any keyframes when there is an error");
     return keyframes;
   }
 
-  // We currently don't support additive animation. However, Web Animations
-  // says that if you don't have a keyframe at offset 0 or 1, then you should
-  // synthesize one using an additive zero value when you go to compose style.
-  // Until we implement additive animations we just throw if we encounter any
-  // set of keyframes that would put us in that situation.
-
-  if (RequiresAdditiveAnimation(keyframes, aDocument)) {
+  // FIXME: Bug 1311257: Support missing keyframes for Servo backend.
+  if ((!AnimationUtils::IsCoreAPIEnabled() ||
+       aDocument->IsStyledByServo()) &&
+      RequiresAdditiveAnimation(keyframes, aDocument)) {
     aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
     keyframes.Clear();
   }
 
   return keyframes;
 }
 
 /* static */ void
@@ -664,16 +662,17 @@ KeyframeUtils::GetComputedKeyframeValues
   MOZ_ASSERT(result.Length() == aKeyframes.Length(), "Array length mismatch");
   return result;
 }
 
 /* static */ nsTArray<AnimationProperty>
 KeyframeUtils::GetAnimationPropertiesFromKeyframes(
   const nsTArray<Keyframe>& aKeyframes,
   const nsTArray<ComputedKeyframeValues>& aComputedValues,
+  dom::CompositeOperation aEffectComposite,
   nsStyleContext* aStyleContext)
 {
   MOZ_ASSERT(aKeyframes.Length() == aComputedValues.Length(),
              "Array length mismatch");
 
   nsTArray<KeyframeValueEntry> entries(aKeyframes.Length());
 
   const size_t len = aKeyframes.Length();
@@ -682,16 +681,18 @@ KeyframeUtils::GetAnimationPropertiesFro
     for (auto& value : aComputedValues[i]) {
       MOZ_ASSERT(frame.mComputedOffset != Keyframe::kComputedOffsetNotSet,
                  "Invalid computed offset");
       KeyframeValueEntry* entry = entries.AppendElement();
       entry->mOffset = frame.mComputedOffset;
       entry->mProperty = value.mProperty;
       entry->mValue = value.mValue;
       entry->mTimingFunction = frame.mTimingFunction;
+      entry->mComposite =
+        frame.mComposite ? frame.mComposite.value() : aEffectComposite;
     }
   }
 
   nsTArray<AnimationProperty> result;
   BuildSegmentsFromValueEntries(entries, result);
   return result;
 }
 
@@ -1129,16 +1130,104 @@ MarkAsComputeValuesFailureKey(PropertyVa
 static bool
 IsComputeValuesFailureKey(const PropertyValuePair& aPair)
 {
   return nsCSSProps::IsShorthand(aPair.mProperty) &&
          aPair.mValue.GetTokenStreamValue()->mPropertyID ==
            eCSSPropertyExtra_no_properties;
 }
 
+static void
+AppendInitialSegment(AnimationProperty* aAnimationProperty,
+                     const KeyframeValueEntry& aFirstEntry)
+{
+  AnimationPropertySegment* segment =
+    aAnimationProperty->mSegments.AppendElement();
+  segment->mFromKey        = 0.0f;
+  segment->mFromComposite  = dom::CompositeOperation::Add;
+  segment->mToKey          = aFirstEntry.mOffset;
+  segment->mToValue        = aFirstEntry.mValue;
+  segment->mToComposite    = aFirstEntry.mComposite;
+}
+
+static void
+AppendFinalSegment(AnimationProperty* aAnimationProperty,
+                   const KeyframeValueEntry& aLastEntry)
+{
+  AnimationPropertySegment* segment =
+    aAnimationProperty->mSegments.AppendElement();
+  segment->mFromKey        = aLastEntry.mOffset;
+  segment->mFromValue      = aLastEntry.mValue;
+  segment->mFromComposite  = aLastEntry.mComposite;
+  segment->mToKey          = 1.0f;
+  segment->mToComposite    = dom::CompositeOperation::Add;
+  segment->mTimingFunction = aLastEntry.mTimingFunction;
+}
+
+// Returns a newly created AnimationProperty if one was created to fill-in the
+// missing keyframe, nullptr otherwise (if we decided not to fill the keyframe
+// becase we don't support additive animation).
+static AnimationProperty*
+HandleMissingInitialKeyframe(nsTArray<AnimationProperty>& aResult,
+                             const KeyframeValueEntry& aEntry)
+{
+  MOZ_ASSERT(aEntry.mOffset != 0.0f,
+             "The offset of the entry should not be 0.0");
+
+  // If the preference of the core Web Animations API is not enabled, don't fill
+  // in the missing keyframe since the missing keyframe requires support for
+  // additive animation which is guarded by this pref.
+  if (!AnimationUtils::IsCoreAPIEnabled()){
+    return nullptr;
+  }
+
+  AnimationProperty* result = aResult.AppendElement();
+  result->mProperty = aEntry.mProperty;
+
+  AppendInitialSegment(result, aEntry);
+
+  return result;
+}
+
+static void
+HandleMissingFinalKeyframe(nsTArray<AnimationProperty>& aResult,
+                           const KeyframeValueEntry& aEntry,
+                           AnimationProperty* aCurrentAnimationProperty)
+{
+  MOZ_ASSERT(aEntry.mOffset != 1.0f,
+             "The offset of the entry should not be 1.0");
+
+  // If the preference of the core Web Animations API is not enabled, don't fill
+  // in the missing keyframe since the missing keyframe requires support for
+  // additive animation which is guarded by this pref.
+  if (!AnimationUtils::IsCoreAPIEnabled()){
+    // If we have already appended a new entry for the property so we have to
+    // remove it.
+    if (aCurrentAnimationProperty) {
+      aResult.RemoveElementAt(aResult.Length() - 1);
+    }
+    return;
+  }
+
+  // If |aCurrentAnimationProperty| is nullptr, that means this is the first
+  // entry for the property, we have to append a new AnimationProperty for this
+  // property.
+  if (!aCurrentAnimationProperty) {
+    aCurrentAnimationProperty = aResult.AppendElement();
+    aCurrentAnimationProperty->mProperty = aEntry.mProperty;
+
+    // If we have only one entry whose offset is neither 1 nor 0 for this
+    // property, we need to append the initial segment as well.
+    if (aEntry.mOffset != 0.0f) {
+      AppendInitialSegment(aCurrentAnimationProperty, aEntry);
+    }
+  }
+  AppendFinalSegment(aCurrentAnimationProperty, aEntry);
+}
+
 /**
  * Builds an array of AnimationProperty objects to represent the keyframe
  * animation segments in aEntries.
  */
 static void
 BuildSegmentsFromValueEntries(nsTArray<KeyframeValueEntry>& aEntries,
                               nsTArray<AnimationProperty>& aResult)
 {
@@ -1165,57 +1254,69 @@ BuildSegmentsFromValueEntries(nsTArray<K
   // offset 1, if we have multiple values for a given property at that offset,
   // since we need to retain the very first and very last value so they can
   // be used for reverse and forward filling.
   //
   // Typically, for each property in |aEntries|, we expect there to be at least
   // one KeyframeValueEntry with offset 0.0, and at least one with offset 1.0.
   // However, since it is possible that when building |aEntries|, the call to
   // StyleAnimationValue::ComputeValues might fail, this can't be guaranteed.
-  // Furthermore, since we don't yet implement additive animation and hence
-  // don't have sensible fallback behavior when these values are missing, the
-  // following loop takes care to identify properties that lack a value at
-  // offset 0.0/1.0 and drops those properties from |aResult|.
+  // Furthermore, if additive animation is disabled, the following loop takes
+  // care to identify properties that lack a value at offset 0.0/1.0 and drops
+  // those properties from |aResult|.
 
   nsCSSPropertyID lastProperty = eCSSProperty_UNKNOWN;
   AnimationProperty* animationProperty = nullptr;
 
   size_t i = 0, n = aEntries.Length();
 
   while (i < n) {
-    // Check that the last property ends with an entry at offset 1.
+    // If we've reached the end of the array of entries, synthesize a final (and
+    // initial) segment if necessary.
     if (i + 1 == n) {
-      if (aEntries[i].mOffset != 1.0f && animationProperty) {
-        aResult.RemoveElementAt(aResult.Length() - 1);
-        animationProperty = nullptr;
+      if (aEntries[i].mOffset != 1.0f) {
+        HandleMissingFinalKeyframe(aResult, aEntries[i], animationProperty);
+      } else if (aEntries[i].mOffset == 1.0f && !animationProperty) {
+        // If the last entry with offset 1 and no animation property, that means
+        // it is the only entry for this property so append a single segment
+        // from 0 offset to |aEntry[i].offset|.
+        Unused << HandleMissingInitialKeyframe(aResult, aEntries[i]);
       }
+      animationProperty = nullptr;
       break;
     }
 
     MOZ_ASSERT(aEntries[i].mProperty != eCSSProperty_UNKNOWN &&
                aEntries[i + 1].mProperty != eCSSProperty_UNKNOWN,
                "Each entry should specify a valid property");
 
-    // Skip properties that don't have an entry with offset 0.
+    // No keyframe for this property at offset 0.
     if (aEntries[i].mProperty != lastProperty &&
         aEntries[i].mOffset != 0.0f) {
-      // Since the entries are sorted by offset for a given property, and
-      // since we don't update |lastProperty|, we will keep hitting this
-      // condition until we change property.
-      ++i;
-      continue;
+      // If we don't support additive animation we can't fill in the missing
+      // keyframes and we should just skip this property altogether. Since the
+      // entries are sorted by offset for a given property, and since we don't
+      // update |lastProperty|, we will keep hitting this condition until we
+      // change property.
+      animationProperty = HandleMissingInitialKeyframe(aResult, aEntries[i]);
+      if (animationProperty) {
+        lastProperty = aEntries[i].mProperty;
+      } else {
+        // Skip this entry if we did not handle the missing entry.
+        ++i;
+        continue;
+      }
     }
 
-    // Drop properties that don't end with an entry with offset 1.
+    // No keyframe for this property at offset 1.
     if (aEntries[i].mProperty != aEntries[i + 1].mProperty &&
         aEntries[i].mOffset != 1.0f) {
-      if (animationProperty) {
-        aResult.RemoveElementAt(aResult.Length() - 1);
-        animationProperty = nullptr;
-      }
+      HandleMissingFinalKeyframe(aResult, aEntries[i], animationProperty);
+      // Move on to new property.
+      animationProperty = nullptr;
       ++i;
       continue;
     }
 
     // Starting from i, determine the next [i, j] interval from which to
     // generate a segment.
     size_t j;
     if (aEntries[i].mOffset == 0.0f && aEntries[i + 1].mOffset == 0.0f) {
@@ -1317,29 +1418,34 @@ GetKeyframeListFromPropertyIndexedKeyfra
   JS::Rooted<JSObject*> object(aCx, &aValue.toObject());
   nsTArray<PropertyValuesPair> propertyValuesPairs;
   if (!GetPropertyValuesPairs(aCx, object, ListAllowance::eAllow,
                               propertyValuesPairs)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
+  bool isServoBackend = aDocument->IsStyledByServo();
+
   // Create a set of keyframes for each property.
   nsCSSParser parser(aDocument->CSSLoader());
   nsClassHashtable<nsFloatHashKey, Keyframe> processedKeyframes;
   for (const PropertyValuesPair& pair : propertyValuesPairs) {
     size_t count = pair.mValues.Length();
     if (count == 0) {
       // No animation values for this property.
       continue;
     }
-    if (count == 1) {
-      // We don't support additive values and so can't support an
-      // animation that goes from the underlying value to this
-      // specified value.  Throw an exception until we do support this.
+
+    // If we only have one value, we should animate from the underlying value
+    // using additive animation--however, we don't support additive animation
+    // for Servo backend (bug 1311257) or when the core animation API pref is
+    // switched off.
+    if ((!AnimationUtils::IsCoreAPIEnabled() || isServoBackend) &&
+        count == 1) {
       aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
       return;
     }
 
     size_t n = pair.mValues.Length() - 1;
     size_t i = 0;
 
     for (const nsString& stringValue : pair.mValues) {
--- a/dom/animation/KeyframeUtils.h
+++ b/dom/animation/KeyframeUtils.h
@@ -122,23 +122,27 @@ public:
    *
    * @param aKeyframes The input keyframes.
    * @param aComputedValues The computed keyframe values (as returned by
    *   GetComputedKeyframeValues) used to fill in the individual
    *   AnimationPropertySegment objects. Although these values could be
    *   calculated from |aKeyframes|, passing them in as a separate parameter
    *   allows the result of GetComputedKeyframeValues to be re-used both
    *   here and in ApplySpacing.
+   * @param aEffectComposite The composite operation specified on the effect.
+   *   For any keyframes in |aKeyframes| that do not specify a composite
+   *   operation, this value will be used.
    * @param aStyleContext The style context to calculate the style difference.
    * @return The set of animation properties. If an error occurs, the returned
    *   array will be empty.
    */
   static nsTArray<AnimationProperty> GetAnimationPropertiesFromKeyframes(
     const nsTArray<Keyframe>& aKeyframes,
     const nsTArray<ComputedKeyframeValues>& aComputedValues,
+    dom::CompositeOperation aEffectComposite,
     nsStyleContext* aStyleContext);
 
   /**
    * Check if the property or, for shorthands, one or more of
    * its subproperties, is animatable.
    *
    * @param aProperty The property to check.
    * @return true if |aProperty| is animatable.
--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -10,8 +10,9 @@ support-files =
 # over HTTP
 [chrome/test_animation_observers.html]
 [chrome/test_animation_performance_warning.html]
 [chrome/test_animation_properties.html]
 [chrome/test_generated_content_getAnimations.html]
 [chrome/test_observers_for_sync_api.html]
 [chrome/test_restyles.html]
 [chrome/test_running_on_compositor.html]
+[chrome/test_simulate_compute_values_failure.html]
--- a/dom/animation/test/chrome/test_animation_properties.html
+++ b/dom/animation/test/chrome/test_animation_properties.html
@@ -28,30 +28,21 @@ function assert_properties_equal(actual,
   assert_equals(actual.length, expected.length);
 
   var compareProperties = (a, b) =>
     a.property == b.property ? 0 : (a.property < b.property ? -1 : 1);
 
   var sortedActual   = actual.sort(compareProperties);
   var sortedExpected = expected.sort(compareProperties);
 
-  // We want to serialize the values in the following form:
-  //
-  //  { offset: 0, easing: linear, composite: replace, value: 5px }, ...
-  //
-  // So that we can just compare strings and, in the failure case,
-  // easily see where the differences lie.
-  var serializeMember = value => {
-    return typeof value === 'undefined' ? '<not set>' : value;
-  }
   var serializeValues = values =>
     values.map(value =>
       '{ ' +
         [ 'offset', 'value', 'easing', 'composite' ].map(
-          member => `${member}: ${serializeMember(value[member])}`
+          member => `${member}: ${value[member]}`
         ).join(', ') +
       ' }')
     .join(', ');
 
   for (var i = 0; i < sortedActual.length; i++) {
     assert_equals(sortedActual[i].property,
                   sortedExpected[i].property,
                   'CSS property name should match');
@@ -697,291 +688,124 @@ var gTests = [
 
   // ---------------------------------------------------------------------
   //
   // Tests for properties that parse correctly but which we fail to
   // convert to computed values.
   //
   // ---------------------------------------------------------------------
 
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' initial keyframe',
-    frames:   [ { margin: '5px', simulateComputeValuesFailure: true },
-                { margin: '5px' } ],
-    expected: [  ]
-  },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' initial keyframe where we have enough values to create'
-              + ' a final segment',
-    frames:   [ { margin: '5px', simulateComputeValuesFailure: true },
-                { margin: '5px' },
-                { margin: '5px' } ],
-    expected: [  ]
-  },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' initial overlapping keyframes (first in series of two)',
-    frames:   [ { margin: '5px', offset: 0,
-                  simulateComputeValuesFailure: true },
-                { margin: '5px', offset: 0 },
+  { desc:     'a missing property in initial keyframe',
+    frames:   [ { },
                 { margin: '5px' } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace', 'linear'),
+                  values: [ value(0, undefined, 'add', 'linear'),
                             value(1, '5px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] } ]
-  },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' initial overlapping keyframes (second in series of two)',
-    frames:   [ { margin: '5px', offset: 0 },
-                { margin: '5px', offset: 0,
-                  simulateComputeValuesFailure: true },
-                { margin: '5px' } ],
-    expected: [ { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
+                  values: [ value(0, undefined, 'add', 'linear'),
                             value(1, '5px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
+                  values: [ value(0, undefined, 'add', 'linear'),
                             value(1, '5px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
+                  values: [ value(0, undefined, 'add', 'linear'),
                             value(1, '5px', 'replace') ] } ]
   },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' initial overlapping keyframes (second in series of three)',
-    frames:   [ { margin: '5px', offset: 0 },
-                { margin: '5px', offset: 0,
-                  simulateComputeValuesFailure: true },
-                { margin: '5px', offset: 0 },
-                { margin: '5px' } ],
+  { desc:     'a missing property in final keyframe',
+    frames:   [ { margin: '5px' },
+                { } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace'),
-                            value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, undefined, 'add') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace'),
-                            value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, undefined, 'add') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace'),
-                            value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, undefined, 'add') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace'),
-                            value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] } ]
-  },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' final keyframe',
-    frames:   [ { margin: '5px' },
-                { margin: '5px', simulateComputeValuesFailure: true } ],
-    expected: [  ]
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, undefined, 'add') ] } ]
   },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' final keyframe where it forms the last segment in the series',
+  { desc:     'a missing property in final keyframe where it forms the last'
+              + ' segment in the series',
     frames:   [ { margin: '5px' },
-                { margin: '5px',
-                  marginLeft: '5px',
+                { marginLeft: '5px',
                   marginRight: '5px',
-                  marginBottom: '5px',
-                  // margin-top sorts last and only it will be missing since
-                  // the other longhand components are specified
-                  simulateComputeValuesFailure: true } ],
+                  marginBottom: '5px' } ],
     expected: [ { property: 'margin-bottom',
                   values: [ value(0, '5px', 'replace', 'linear'),
                             value(1, '5px', 'replace') ] },
                 { property: 'margin-left',
                   values: [ value(0, '5px', 'replace', 'linear'),
                             value(1, '5px', 'replace') ] },
                 { property: 'margin-right',
                   values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] } ]
-  },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' final keyframe where we have enough values to create'
-              + ' an initial segment',
-    frames:   [ { margin: '5px' },
-                { margin: '5px' },
-                { margin: '5px', simulateComputeValuesFailure: true } ],
-    expected: [  ]
-  },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' final overlapping keyframes (first in series of two)',
-    frames:   [ { margin: '5px' },
-                { margin: '5px', offset: 1,
-                  simulateComputeValuesFailure: true },
-                { margin: '5px', offset: 1 } ],
-    expected: [ { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace', 'linear'),
                             value(1, '5px', 'replace') ] },
-                { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-bottom',
+                { property: 'margin-top',
                   values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] } ]
-  },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' final overlapping keyframes (second in series of two)',
-    frames:   [ { margin: '5px' },
-                { margin: '5px', offset: 1 },
-                { margin: '5px', offset: 1,
-                  simulateComputeValuesFailure: true } ],
-    expected: [ { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] } ]
+                            value(1, undefined, 'add') ] } ]
   },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' final overlapping keyframes (second in series of three)',
-    frames:   [ { margin: '5px' },
-                { margin: '5px', offset: 1 },
-                { margin: '5px', offset: 1,
-                  simulateComputeValuesFailure: true },
-                { margin: '5px', offset: 1 } ],
-    expected: [ { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace'),
-                            value(1, '5px', 'replace') ] } ]
-  },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' intermediate keyframe',
-    frames:   [ { margin: '5px' },
-                { margin: '5px', simulateComputeValuesFailure: true },
-                { margin: '5px' } ],
-    expected: [ { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] } ]
-  },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' initial keyframe along with other values',
-    // simulateComputeValuesFailure only applies to shorthands so we can set
-    // it on the same keyframe and it will only apply to |margin| and not
-    // |left|.
-    frames:   [ { margin: '77%', left: '10px',
-                  simulateComputeValuesFailure: true },
+  { desc:     'a missing property in initial keyframe along with other values',
+    frames:   [ {                left: '10px' },
                 { margin: '5px', left: '20px' } ],
     expected: [ { property: 'left',
                   values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] } ],
+                            value(1, '20px', 'replace') ] },
+                { property: 'margin-top',
+                  values: [ value(0, undefined, 'add', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, undefined, 'add', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, undefined, 'add', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, undefined, 'add', 'linear'),
+                            value(1, '5px', 'replace') ] } ]
   },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' initial keyframe along with other values where those'
-              + ' values sort after the property with missing values',
-    frames:   [ { margin: '77%', right: '10px',
-                  simulateComputeValuesFailure: true },
-                { margin: '5px', right: '20px' } ],
-    expected: [ { property: 'right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] } ],
-  },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' final keyframe along with other values',
+  { desc:     'a missing property in final keyframe along with other values',
     frames:   [ { margin: '5px', left: '10px' },
-                { margin: '5px', left: '20px',
-                  simulateComputeValuesFailure: true } ],
+                {                left: '20px' } ],
     expected: [ { property: 'left',
                   values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] } ],
-  },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' final keyframe along with other values where those'
-              + ' values sort after the property with missing values',
-    frames:   [ { margin: '5px', right: '10px' },
-                { margin: '5px', right: '20px',
-                  simulateComputeValuesFailure: true } ],
-    expected: [ { property: 'right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] } ],
-  },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' an intermediate keyframe along with other values',
-    frames:   [ { margin: '5px', left: '10px' },
-                { margin: '5px', left: '20px',
-                  simulateComputeValuesFailure: true },
-                { margin: '5px', left: '30px' } ],
-    expected: [ { property: 'margin-top',
+                            value(1, '20px', 'replace') ] },
+                { property: 'margin-top',
                   values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                            value(1, undefined, 'add') ] },
                 { property: 'margin-right',
                   values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                            value(1, undefined, 'add') ] },
                 { property: 'margin-bottom',
                   values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                            value(1, undefined, 'add') ] },
                 { property: 'margin-left',
                   values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'left',
-                  values: [ value(0,   '10px', 'replace', 'linear'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1,   '30px', 'replace') ] } ]
+                            value(1, undefined, 'add') ] } ]
+  },
+  { desc:     'missing properties in both of initial and final keyframe',
+    frames:   [ { left: '5px', offset: 0.5 } ],
+    expected: [ { property: 'left',
+                  values: [ value(0,   undefined, 'add',     'linear'),
+                            value(0.5, '5px',       'replace', 'linear'),
+                            value(1,   undefined, 'add') ] } ]
   },
-  { desc:     'a property that can\'t be resolved to computed values in'
-              + ' an intermediate keyframe by itself',
-    frames:   [ { margin: '5px', left: '10px' },
-                { margin: '5px',
-                  simulateComputeValuesFailure: true },
-                { margin: '5px', left: '30px' } ],
-    expected: [ { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
-                { property: 'left',
-                  values: [ value(0,   '10px', 'replace', 'linear'),
-                            value(1,   '30px', 'replace') ] } ]
+  { desc:     'missing propertes in both of initial and final keyframe along'
+              + 'with other values',
+    frames:   [ { left:  '5px',  offset: 0 },
+                { right: '5px',  offset: 0.5 },
+                { left:  '10px', offset: 1 } ],
+    expected: [ { property: 'left',
+                  values: [ value(0, '5px',  'replace', 'linear'),
+                            value(1, '10px', 'replace') ] },
+                { property: 'right',
+                  values: [ value(0,   undefined, 'add',     'linear'),
+                            value(0.5, '5px',     'replace', 'linear'),
+                            value(1,   undefined, 'add') ] } ]
   },
 ];
 
 gTests.forEach(function(subtest) {
   test(function(t) {
     var div = addDiv(t);
     var animation = div.animate(subtest.frames, 100 * MS_PER_SEC);
     assert_properties_equal(animation.effect.getProperties(),
--- a/dom/animation/test/chrome/test_restyles.html
+++ b/dom/animation/test/chrome/test_restyles.html
@@ -804,12 +804,52 @@ waitForAllPaints(function() {
          '!important rule begins running on the compositor even if the ' +
          '!important rule had been dropped before the target element was ' +
          'removed');
 
       yield ensureElementRemoval(div);
     }
   );
 
+  // Tests that additive animations don't throttle at all.
+  add_task(function* no_throttling_animations_out_of_view_element() {
+    if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
+      return;
+    }
+
+    var div = addDiv(null, { style: 'transform: translateY(-400px);' });
+    var animation =
+      div.animate([{ visibility: 'visible' }], 100 * MS_PER_SEC);
+
+    yield animation.ready;
+
+    var markers = yield observeStyling(5);
+
+    is(markers.length, 5,
+       'Discrete animation has has no keyframe whose offset is 0 or 1 in an ' +
+       'out-of-view element should not be throttled');
+    yield ensureElementRemoval(div);
+  });
+
+  // Counter part of the above test.
+  add_task(function* no_restyling_discrete_animations_out_of_view_element() {
+    if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
+      return;
+    }
+
+    var div = addDiv(null, { style: 'transform: translateY(-400px);' });
+    var animation =
+      div.animate({ visibility: ['visible', 'hidden'] }, 100 * MS_PER_SEC);
+
+    yield animation.ready;
+
+    var markers = yield observeStyling(5);
+
+    is(markers.length, 0,
+       'Discrete animation running on the main-thread in an out-of-view ' +
+       'element should never cause restyles');
+    yield ensureElementRemoval(div);
+  });
+
 });
 
 </script>
 </body>
copy from dom/animation/test/chrome/test_animation_properties.html
copy to dom/animation/test/chrome/test_simulate_compute_values_failure.html
--- a/dom/animation/test/chrome/test_animation_properties.html
+++ b/dom/animation/test/chrome/test_simulate_compute_values_failure.html
@@ -1,31 +1,21 @@
 <!doctype html>
 <head>
 <meta charset=utf-8>
-<title>Bug 1254419 - Test the values returned by
-       KeyframeEffectReadOnly.getProperties()</title>
+<title>Bug 1276688 - Test for properties that parse correctly but which we fail
+       to convert to computed values</title>
 <script type="application/javascript" src="../testharness.js"></script>
 <script type="application/javascript" src="../testharnessreport.js"></script>
 <script type="application/javascript" src="../testcommon.js"></script>
 </head>
 <body>
-<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1254419"
-  target="_blank">Mozilla Bug 1254419</a>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1276688"
+  target="_blank">Mozilla Bug 1276688</a>
 <div id="log"></div>
-<style>
-
-:root {
-  --var-100px: 100px;
-  --var-100px-200px: 100px 200px;
-}
-div {
-  font-size: 10px; /* For calculating em-based units */
-}
-</style>
 <script>
 'use strict';
 
 function assert_properties_equal(actual, expected) {
   assert_equals(actual.length, expected.length);
 
   var compareProperties = (a, b) =>
     a.property == b.property ? 0 : (a.property < b.property ? -1 : 1);
@@ -63,643 +53,16 @@ function assert_properties_equal(actual,
 }
 
 // Shorthand for constructing a value object
 function value(offset, value, composite, easing) {
   return { offset: offset, value: value, easing: easing, composite: composite };
 }
 
 var gTests = [
-
-  // ---------------------------------------------------------------------
-  //
-  // Tests for property-indexed specifications
-  //
-  // ---------------------------------------------------------------------
-
-  { desc:     'a one-property two-value property-indexed specification',
-    frames:   { left: ['10px', '20px'] },
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] } ]
-  },
-  { desc:     'a one-shorthand-property two-value property-indexed'
-              + ' specification',
-    frames:   { margin: ['10px', '10px 20px 30px 40px'] },
-    expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '10px', 'replace') ] },
-                { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
-                { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
-                { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] } ]
-  },
-  { desc:     'a two-property (one shorthand and one of its longhand'
-              + ' components) two-value property-indexed specification',
-    frames:   { marginTop: ['50px', '60px'],
-                margin: ['10px', '10px 20px 30px 40px'] },
-    expected: [ { property: 'margin-top',
-                  values: [ value(0, '50px', 'replace', 'linear'),
-                            value(1, '60px', 'replace') ] },
-                { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
-                { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
-                { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] } ]
-  },
-  { desc:     'a two-property property-indexed specification with different'
-              + ' numbers of values',
-    frames:   { left: ['10px', '20px', '30px'],
-                top: ['40px', '50px'] },
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
-                { property: 'top',
-                  values: [ value(0, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
-  },
-  { desc:     'a property-indexed specification with an invalid value',
-    frames:   { left: ['10px', '20px', '30px', '40px', '50px'],
-                top:  ['15px', '25px', 'invalid', '45px', '55px'] },
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.25, '20px', 'replace', 'linear'),
-                            value(0.5, '30px', 'replace', 'linear'),
-                            value(0.75, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] },
-                { property: 'top',
-                  values: [ value(0, '15px', 'replace', 'linear'),
-                            value(0.25, '25px', 'replace', 'linear'),
-                            value(0.75, '45px', 'replace', 'linear'),
-                            value(1, '55px', 'replace') ] } ]
-  },
-  { desc:     'a one-property two-value property-indexed specification that'
-              + ' needs to stringify its values',
-    frames:   { opacity: [0, 1] },
-    expected: [ { property: 'opacity',
-                  values: [ value(0, '0', 'replace', 'linear'),
-                            value(1, '1', 'replace') ] } ]
-  },
-  { desc:     'a property-indexed keyframe where a lesser shorthand precedes'
-              + ' a greater shorthand',
-    frames:   { borderLeft: [ '1px solid rgb(1, 2, 3)',
-                              '2px solid rgb(4, 5, 6)' ],
-                border:     [ '3px dotted rgb(7, 8, 9)',
-                              '4px dashed rgb(10, 11, 12)' ] },
-    expected: [ { property: 'border-bottom-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
-                { property: 'border-left-color',
-                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
-                            value(1, 'rgb(4, 5, 6)', 'replace') ] },
-                { property: 'border-right-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
-                { property: 'border-top-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
-                { property: 'border-bottom-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
-                { property: 'border-left-width',
-                  values: [ value(0, '1px', 'replace', 'linear'),
-                            value(1, '2px', 'replace') ] },
-                { property: 'border-right-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
-                { property: 'border-top-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
-                { property: 'border-bottom-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-left-style',
-                  values: [ value(0, 'solid', 'replace', 'linear'),
-                            value(1, 'solid', 'replace') ] },
-                { property: 'border-right-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-top-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-image-outset',
-                  values: [ value(0, '0 0 0 0', 'replace', 'linear'),
-                            value(1, '0 0 0 0', 'replace') ] },
-                { property: 'border-image-repeat',
-                  values: [ value(0, 'stretch stretch', 'replace', 'linear'),
-                            value(1, 'stretch stretch', 'replace') ] },
-                { property: 'border-image-slice',
-                  values: [ value(0, '100% 100% 100% 100%',
-                                  'replace', 'linear'),
-                            value(1, '100% 100% 100% 100%', 'replace') ] },
-                { property: 'border-image-source',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: 'border-image-width',
-                  values: [ value(0, '1 1 1 1', 'replace', 'linear'),
-                            value(1, '1 1 1 1', 'replace') ] },
-                { property: '-moz-border-bottom-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: '-moz-border-left-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: '-moz-border-right-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: '-moz-border-top-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] } ]
-  },
-  { desc:     'a property-indexed keyframe where a greater shorthand precedes'
-              + ' a lesser shorthand',
-    frames:   { border:     [ '3px dotted rgb(7, 8, 9)',
-                              '4px dashed rgb(10, 11, 12)' ],
-                borderLeft: [ '1px solid rgb(1, 2, 3)',
-                              '2px solid rgb(4, 5, 6)' ] },
-    expected: [ { property: 'border-bottom-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
-                { property: 'border-left-color',
-                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
-                            value(1, 'rgb(4, 5, 6)', 'replace') ] },
-                { property: 'border-right-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
-                { property: 'border-top-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
-                { property: 'border-bottom-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
-                { property: 'border-left-width',
-                  values: [ value(0, '1px', 'replace', 'linear'),
-                            value(1, '2px', 'replace') ] },
-                { property: 'border-right-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
-                { property: 'border-top-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
-                { property: 'border-bottom-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-left-style',
-                  values: [ value(0, 'solid', 'replace', 'linear'),
-                            value(1, 'solid', 'replace') ] },
-                { property: 'border-right-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-top-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-image-outset',
-                  values: [ value(0, '0 0 0 0', 'replace', 'linear'),
-                            value(1, '0 0 0 0', 'replace') ] },
-                { property: 'border-image-repeat',
-                  values: [ value(0, 'stretch stretch', 'replace', 'linear'),
-                            value(1, 'stretch stretch', 'replace') ] },
-                { property: 'border-image-slice',
-                  values: [ value(0, '100% 100% 100% 100%',
-                                  'replace', 'linear'),
-                            value(1, '100% 100% 100% 100%', 'replace') ] },
-                { property: 'border-image-source',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: 'border-image-width',
-                  values: [ value(0, '1 1 1 1', 'replace', 'linear'),
-                            value(1, '1 1 1 1', 'replace') ] },
-                { property: '-moz-border-bottom-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: '-moz-border-left-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: '-moz-border-right-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: '-moz-border-top-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] } ]
-  },
-
-  // ---------------------------------------------------------------------
-  //
-  // Tests for keyframe sequences
-  //
-  // ---------------------------------------------------------------------
-
-  { desc:     'a keyframe sequence specification with repeated values at'
-              + ' offset 0/1 with different easings',
-    frames:   [ { 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' } ],
-    expected: [ { property: 'left',
-                  values: [ value(0, '100px', 'replace'),
-                            value(0, '200px', 'replace', 'ease'),
-                            value(0.5, '300px', 'replace', 'linear'),
-                            value(1, '400px', 'replace'),
-                            value(1, '500px', 'replace') ] } ]
-  },
-  { desc:     'a one-property two-keyframe sequence',
-    frames:   [ { offset: 0, left: '10px' },
-                { offset: 1, left: '20px' } ],
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] } ]
-  },
-  { desc:     'a two-property two-keyframe sequence',
-    frames:   [ { offset: 0, left: '10px', top: '30px' },
-                { offset: 1, left: '20px', top: '40px' } ],
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
-                { property: 'top',
-                  values: [ value(0, '30px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] } ]
-  },
-  { desc:     'a one shorthand property two-keyframe sequence',
-    frames:   [ { offset: 0, margin: '10px' },
-                { offset: 1, margin: '20px 30px 40px 50px' } ],
-    expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
-                { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
-                { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] },
-                { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
-  },
-  { desc:     'a two-property (a shorthand and one of its component longhands)'
-              + ' two-keyframe sequence',
-    frames:   [ { offset: 0, margin: '10px', marginTop: '20px' },
-                { offset: 1, marginTop: '70px',
-                             margin: '30px 40px 50px 60px' } ],
-    expected: [ { property: 'margin-top',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(1, '70px', 'replace') ] },
-                { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] },
-                { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] },
-                { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '60px', 'replace') ] } ]
-  },
-  { desc:     'a keyframe sequence with duplicate values for a given interior'
-              + ' offset',
-    frames:   [ { 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' } ],
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.5, '20px', 'replace'),
-                            value(0.5, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
-  },
-  { desc:     'a keyframe sequence with duplicate values for offsets 0 and 1',
-    frames:   [ { offset: 0, left: '10px' },
-                { offset: 0, left: '20px' },
-                { offset: 0, left: '30px' },
-                { offset: 1, left: '40px' },
-                { offset: 1, left: '50px' },
-                { offset: 1, left: '60px' } ],
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace'),
-                            value(0, '30px', 'replace', 'linear'),
-                            value(1, '40px', 'replace'),
-                            value(1, '60px', 'replace') ] } ]
-  },
-  { desc:     'a two-property four-keyframe sequence',
-    frames:   [ { offset: 0, left: '10px' },
-                { offset: 0, top: '20px' },
-                { offset: 1, top: '30px' },
-                { offset: 1, left: '40px' } ],
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] },
-                { property: 'top',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] } ]
-  },
-  { desc:     'a one-property keyframe sequence with some omitted offsets',
-    frames:   [ { offset: 0.00, left: '10px' },
-                { offset: 0.25, left: '20px' },
-                { left: '30px' },
-                { left: '40px' },
-                { offset: 1.00, left: '50px' } ],
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.25, '20px', 'replace', 'linear'),
-                            value(0.5, '30px', 'replace', 'linear'),
-                            value(0.75, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
-  },
-  { desc:     'a two-property keyframe sequence with some omitted offsets',
-    frames:   [ { 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' } ],
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.25, '30px', 'replace', 'linear'),
-                            value(0.5, '40px', 'replace', 'linear'),
-                            value(0.75, '50px', 'replace', 'linear'),
-                            value(1, '70px', 'replace') ] },
-                { property: 'top',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(0.75, '60px', 'replace', 'linear'),
-                            value(1, '80px', 'replace') ] } ]
-  },
-  { desc:     'a one-property keyframe sequence with all omitted offsets',
-    frames:   [ { left: '10px' },
-                { left: '20px' },
-                { left: '30px' },
-                { left: '40px' },
-                { left: '50px' } ],
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.25, '20px', 'replace', 'linear'),
-                            value(0.5, '30px', 'replace', 'linear'),
-                            value(0.75, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
-  },
-  { desc:     'a keyframe sequence with different easing values, but the'
-              + ' same easing value for a given offset',
-    frames:   [ { 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' } ],
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'ease'),
-                            value(0.5, '30px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] },
-                { property: 'top',
-                  values: [ value(0, '20px', 'replace', 'ease'),
-                            value(0.5, '40px', 'replace', 'linear'),
-                            value(1, '60px', 'replace') ] } ]
-  },
-  { desc:     'a one-property two-keyframe sequence that needs to'
-              + ' stringify its values',
-    frames:   [ { offset: 0, opacity: 0 },
-                { offset: 1, opacity: 1 } ],
-    expected: [ { property: 'opacity',
-                  values: [ value(0, '0', 'replace', 'linear'),
-                            value(1, '1', 'replace') ] } ]
-  },
-  { desc:     'a keyframe sequence where shorthand precedes longhand',
-    frames:   [ { offset: 0, margin: '10px', marginRight: '20px' },
-                { offset: 1, margin: '30px' } ],
-    expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
-                { property: 'margin-right',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
-                { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
-                { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] } ]
-  },
-  { desc:     'a keyframe sequence where longhand precedes shorthand',
-    frames:   [ { offset: 0, marginRight: '20px', margin: '10px' },
-                { offset: 1, margin: '30px' } ],
-    expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
-                { property: 'margin-right',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
-                { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
-                { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] } ]
-  },
-  { desc:     'a keyframe sequence where lesser shorthand precedes greater'
-              + ' shorthand',
-    frames:   [ { 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)' } ],
-    expected: [ { property: 'border-bottom-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
-                { property: 'border-left-color',
-                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
-                { property: 'border-right-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
-                { property: 'border-top-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
-                { property: 'border-bottom-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
-                { property: 'border-left-width',
-                  values: [ value(0, '1px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
-                { property: 'border-right-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
-                { property: 'border-top-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
-                { property: 'border-bottom-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-left-style',
-                  values: [ value(0, 'solid', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-right-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-top-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-image-outset',
-                  values: [ value(0, '0 0 0 0', 'replace', 'linear'),
-                            value(1, '0 0 0 0', 'replace') ] },
-                { property: 'border-image-repeat',
-                  values: [ value(0, 'stretch stretch', 'replace', 'linear'),
-                            value(1, 'stretch stretch', 'replace') ] },
-                { property: 'border-image-slice',
-                  values: [ value(0, '100% 100% 100% 100%',
-                                  'replace', 'linear'),
-                            value(1, '100% 100% 100% 100%', 'replace') ] },
-                { property: 'border-image-source',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: 'border-image-width',
-                  values: [ value(0, '1 1 1 1', 'replace', 'linear'),
-                            value(1, '1 1 1 1', 'replace') ] },
-                { property: '-moz-border-bottom-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: '-moz-border-left-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: '-moz-border-right-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: '-moz-border-top-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] } ]
-  },
-  { desc:     'a keyframe sequence where greater shorthand precedes'
-              + ' lesser shorthand',
-    frames:   [ { 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)' } ],
-    expected: [ { property: 'border-bottom-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
-                { property: 'border-left-color',
-                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
-                { property: 'border-right-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
-                { property: 'border-top-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
-                { property: 'border-bottom-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
-                { property: 'border-left-width',
-                  values: [ value(0, '1px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
-                { property: 'border-right-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
-                { property: 'border-top-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
-                { property: 'border-bottom-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-left-style',
-                  values: [ value(0, 'solid', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-right-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-top-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
-                { property: 'border-image-outset',
-                  values: [ value(0, '0 0 0 0', 'replace', 'linear'),
-                            value(1, '0 0 0 0', 'replace') ] },
-                { property: 'border-image-repeat',
-                  values: [ value(0, 'stretch stretch', 'replace', 'linear'),
-                            value(1, 'stretch stretch', 'replace') ] },
-                { property: 'border-image-slice',
-                  values: [ value(0, '100% 100% 100% 100%',
-                                  'replace', 'linear'),
-                            value(1, '100% 100% 100% 100%', 'replace') ] },
-                { property: 'border-image-source',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: 'border-image-width',
-                  values: [ value(0, '1 1 1 1', 'replace', 'linear'),
-                            value(1, '1 1 1 1', 'replace') ] },
-                { property: '-moz-border-bottom-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: '-moz-border-left-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: '-moz-border-right-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
-                { property: '-moz-border-top-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] } ]
-  },
-
-  // ---------------------------------------------------------------------
-  //
-  // Tests for unit conversion
-  //
-  // ---------------------------------------------------------------------
-
-  { desc:     'em units are resolved to px values',
-    frames:   { left: ['10em', '20em'] },
-    expected: [ { property: 'left',
-                  values: [ value(0, '100px', 'replace', 'linear'),
-                            value(1, '200px', 'replace') ] } ]
-  },
-  { desc:     'calc() expressions are resolved to the equivalent units',
-    frames:   { left: ['calc(10em + 10px)', 'calc(10em + 10%)'] },
-    expected: [ { property: 'left',
-                  values: [ value(0, 'calc(110px)', 'replace', 'linear'),
-                            value(1, 'calc(100px + 10%)', 'replace') ] } ]
-  },
-
-  // ---------------------------------------------------------------------
-  //
-  // Tests for CSS variable handling conversion
-  //
-  // ---------------------------------------------------------------------
-
-  { desc:     'CSS variables are resolved to their corresponding values',
-    frames:   { left: ['10px', 'var(--var-100px)'] },
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '100px', 'replace') ] } ]
-  },
-  { desc:     'CSS variables in calc() expressions are resolved',
-    frames:   { left: ['10px', 'calc(var(--var-100px) / 2 - 10%)'] },
-    expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, 'calc(50px + -10%)', 'replace') ] } ]
-  },
-  { desc:     'CSS variables in shorthands are resolved to their corresponding'
-              + ' values',
-    frames:   { margin: ['10px', 'var(--var-100px-200px)'] },
-    expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '100px', 'replace') ] },
-                { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '200px', 'replace') ] },
-                { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '100px', 'replace') ] },
-                { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '200px', 'replace') ] } ]
-  },
-
   // ---------------------------------------------------------------------
   //
   // Tests for properties that parse correctly but which we fail to
   // convert to computed values.
   //
   // ---------------------------------------------------------------------
 
   { desc:     'a property that can\'t be resolved to computed values in'
@@ -975,19 +338,27 @@ var gTests = [
                   values: [ value(0, '5px', 'replace', 'linear'),
                             value(1, '5px', 'replace') ] },
                 { property: 'left',
                   values: [ value(0,   '10px', 'replace', 'linear'),
                             value(1,   '30px', 'replace') ] } ]
   },
 ];
 
-gTests.forEach(function(subtest) {
-  test(function(t) {
-    var div = addDiv(t);
-    var animation = div.animate(subtest.frames, 100 * MS_PER_SEC);
-    assert_properties_equal(animation.effect.getProperties(),
-                            subtest.expected);
-  }, subtest.desc);
-});
+setup({explicit_done: true});
 
+SpecialPowers.pushPrefEnv(
+  { set: [["dom.animations-api.core.enabled", false]] },
+  function() {
+    gTests.forEach(function(subtest) {
+      test(function(t) {
+        var div = addDiv(t);
+        var animation = div.animate(subtest.frames, 100 * MS_PER_SEC);
+        assert_properties_equal(animation.effect.getProperties(),
+                                subtest.expected);
+      }, subtest.desc);
+    });
+
+    done();
+  }
+);
 </script>
 </body>
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -529,24 +529,30 @@ GetMinAndMaxScaleForAnimationProperty(co
     // not yet finished or which are filling forwards).
     MOZ_ASSERT(anim->IsRelevant());
 
     dom::KeyframeEffectReadOnly* effect =
       anim->GetEffect() ? anim->GetEffect()->AsKeyframeEffect() : nullptr;
     MOZ_ASSERT(effect, "A playing animation should have a keyframe effect");
     for (size_t propIdx = effect->Properties().Length(); propIdx-- != 0; ) {
       const AnimationProperty& prop = effect->Properties()[propIdx];
-      if (prop.mProperty == eCSSProperty_transform) {
-        for (uint32_t segIdx = prop.mSegments.Length(); segIdx-- != 0; ) {
-          const AnimationPropertySegment& segment = prop.mSegments[segIdx];
+      if (prop.mProperty != eCSSProperty_transform) {
+        continue;
+      }
+      for (const AnimationPropertySegment& segment : prop.mSegments) {
+        // In case of add or accumulate composite, StyleAnimationValue does
+        // not have a valid value.
+        if (segment.mFromComposite == dom::CompositeOperation::Replace) {
           gfxSize from = segment.mFromValue.GetScaleValue(aFrame);
           aMaxScale.width = std::max<float>(aMaxScale.width, from.width);
           aMaxScale.height = std::max<float>(aMaxScale.height, from.height);
           aMinScale.width = std::min<float>(aMinScale.width, from.width);
           aMinScale.height = std::min<float>(aMinScale.height, from.height);
+        }
+        if (segment.mToComposite == dom::CompositeOperation::Replace) {
           gfxSize to = segment.mToValue.GetScaleValue(aFrame);
           aMaxScale.width = std::max<float>(aMaxScale.width, to.width);
           aMaxScale.height = std::max<float>(aMaxScale.height, to.height);
           aMinScale.width = std::min<float>(aMinScale.width, to.width);
           aMinScale.height = std::min<float>(aMinScale.height, to.height);
         }
       }
     }
--- a/testing/web-platform/meta/web-animations/interfaces/Animatable/animate.html.ini
+++ b/testing/web-platform/meta/web-animations/interfaces/Animatable/animate.html.ini
@@ -1,41 +1,13 @@
 [animate.html]
   type: testharness
-  [Element.animate() accepts a one property one value property-indexed keyframes specification]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [Element.animate() accepts a one property one non-array value property-indexed keyframes specification]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [Element.animate() accepts a one property two value property-indexed keyframes specification where the first value is invalid]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [Element.animate() accepts a one property two value property-indexed keyframes specification where the second value is invalid]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
   [Element.animate() accepts a one property one keyframe sequence]
     expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [Element.animate() accepts a single keyframe sequence with omitted offsets]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844 # This will be passed in a subsequent patch.
 
   [Element.animate() accepts a keyframe sequence with different composite values, but the same composite value for a given offset]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1291468
 
-  [Element.animate() accepts a two property keyframe sequence where one property is missing from the first keyframe]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [Element.animate() accepts a two property keyframe sequence where one property is missing from the last keyframe]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
   [Element.animate() accepts a single keyframe sequence with string offset]
     expected: FAIL
 
--- a/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/constructor.html.ini
+++ b/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/constructor.html.ini
@@ -5,84 +5,21 @@
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1291468
 
   [composite values are parsed correctly when passed to the KeyframeEffectReadOnly constructor in regular keyframes]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1291468
 
   [composite values are parsed correctly when passed to the KeyframeEffectReadOnly constructor in KeyframeTimingOptions]
     expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly can be constructed with a one property one value property-indexed keyframes specification]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly constructed with a one property one value property-indexed keyframes specification roundtrips]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly can be constructed with a one property one non-array value property-indexed keyframes specification]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly constructed with a one property one non-array value property-indexed keyframes specification roundtrips]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly can be constructed with a one property two value property-indexed keyframes specification where the first value is invalid]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly constructed with a one property two value property-indexed keyframes specification where the first value is invalid roundtrips]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly can be constructed with a one property two value property-indexed keyframes specification where the second value is invalid]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly constructed with a one property two value property-indexed keyframes specification where the second value is invalid roundtrips]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1311620 # This needs additive animation
 
   [a KeyframeEffectReadOnly can be constructed with a one property one keyframe sequence]
     expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly constructed with a one property one keyframe sequence roundtrips]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly can be constructed with a single keyframe sequence with omitted offsets]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly constructed with a single keyframe sequence with omitted offsets roundtrips]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844 # This will be passed in a subsequent patch
 
   [a KeyframeEffectReadOnly can be constructed with a keyframe sequence with different composite values, but the same composite value for a given offset]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1291468
 
-  [a KeyframeEffectReadOnly can be constructed with a two property keyframe sequence where one property is missing from the first keyframe]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly constructed with a two property keyframe sequence where one property is missing from the first keyframe roundtrips]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly can be constructed with a two property keyframe sequence where one property is missing from the last keyframe]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [a KeyframeEffectReadOnly constructed with a two property keyframe sequence where one property is missing from the last keyframe roundtrips]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
   [a KeyframeEffectReadOnly can be constructed with a single keyframe sequence with string offset]
     expected: FAIL
 
-  [a KeyframeEffectReadOnly constructed with a single keyframe sequence with string offset roundtrips]
-    expected: FAIL
-
--- a/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/setKeyframes.html.ini
+++ b/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/setKeyframes.html.ini
@@ -1,39 +1,12 @@
 [setKeyframes.html]
   type: testharness
-  [Keyframes can be replaced with a one property one value property-indexed keyframes specification]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [Keyframes can be replaced with a one property one non-array value property-indexed keyframes specification]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [Keyframes can be replaced with a one property two value property-indexed keyframes specification where the first value is invalid]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [Keyframes can be replaced with a one property two value property-indexed keyframes specification where the second value is invalid]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
   [Keyframes can be replaced with a one property one keyframe sequence]
-    expected: FAIL
-
-  [Keyframes can be replaced with a single keyframe sequence with omitted offsets]
-    expected: FAIL
+    expected: FAIL # This will be passed in a subsequent patch.
 
   [Keyframes can be replaced with a keyframe sequence with different composite values, but the same composite value for a given offset]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1291468
 
-  [Keyframes can be replaced with a two property keyframe sequence where one property is missing from the first keyframe]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
-  [Keyframes can be replaced with a two property keyframe sequence where one property is missing from the last keyframe]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1216844
-
   [Keyframes can be replaced with a single keyframe sequence with string offset]
     expected: FAIL