Bug 1456394 - Merge KeyframeEffectReadOnly and KeyframeEffect; r=bz,hiro
authorBrian Birtles <birtles@gmail.com>
Mon, 07 May 2018 11:08:59 +0900
changeset 417869 238d0908de38dbeed24a2e288b8b25e64087ec7c
parent 417868 ede7f6c30b62fa74d2f49ad841e08b1d65124700
child 417870 7eae2c106d30b402e5c070bc3ae8b05cc59e78aa
push id103165
push userebalazs@mozilla.com
push dateFri, 11 May 2018 09:45:25 +0000
treeherdermozilla-inbound@59a49b12b268 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, hiro
bugs1456394
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1456394 - Merge KeyframeEffectReadOnly and KeyframeEffect; r=bz,hiro MozReview-Commit-ID: FvTMGjxfRXk
dom/animation/Animation.cpp
dom/animation/Animation.h
dom/animation/AnimationEffectReadOnly.cpp
dom/animation/AnimationEffectReadOnly.h
dom/animation/AnimationEffectTiming.cpp
dom/animation/AnimationEffectTiming.h
dom/animation/AnimationUtils.cpp
dom/animation/EffectCompositor.cpp
dom/animation/EffectSet.cpp
dom/animation/EffectSet.h
dom/animation/KeyframeEffect.cpp
dom/animation/KeyframeEffect.h
dom/animation/test/chrome/file_animate_xrays.html
dom/animation/test/chrome/test_animation_observers_sync.html
dom/animation/test/chrome/test_animation_properties.html
dom/animation/test/crashtests/1216842-1.html
dom/animation/test/crashtests/1216842-2.html
dom/animation/test/crashtests/1216842-3.html
dom/animation/test/crashtests/1216842-4.html
dom/animation/test/crashtests/1216842-5.html
dom/animation/test/crashtests/1216842-6.html
dom/animation/test/crashtests/1239889-1.html
dom/animation/test/css-animations/test_animation-computed-timing.html
dom/animation/test/css-animations/test_effect-target.html
dom/animation/test/css-animations/test_keyframeeffect-getkeyframes.html
dom/animation/test/css-animations/test_pseudoElement-get-animations.html
dom/animation/test/css-transitions/test_effect-target.html
dom/animation/test/css-transitions/test_keyframeeffect-getkeyframes.html
dom/animation/test/style/test_animation-setting-effect.html
dom/base/Element.cpp
dom/base/nsDOMMutationObserver.cpp
dom/tests/mochitest/general/test_interfaces.js
dom/webidl/BaseKeyframeTypes.webidl
dom/webidl/KeyframeEffect.webidl
gfx/layers/AnimationHelper.cpp
layout/base/RestyleManager.cpp
layout/base/nsLayoutUtils.cpp
layout/painting/nsDisplayList.cpp
layout/style/nsAnimationManager.cpp
layout/style/nsAnimationManager.h
layout/style/nsTransitionManager.cpp
layout/style/nsTransitionManager.h
testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/idlharness.html.ini
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -698,17 +698,17 @@ Animation::Tick()
 
   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
 
   if (!mEffect) {
     return;
   }
 
   // Update layers if we are newly finished.
-  KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
+  KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
   if (keyframeEffect &&
       !keyframeEffect->Properties().IsEmpty() &&
       !mFinishedAtLastComposeStyle &&
       PlayState() == AnimationPlayState::Finished) {
     PostUpdate();
   }
 }
 
@@ -903,19 +903,19 @@ Animation::ShouldBeSynchronizedWithMainT
     return false;
   }
 
   // Currently only transform animations need to be synchronized
   if (aProperty != eCSSProperty_transform) {
     return false;
   }
 
-  KeyframeEffectReadOnly* keyframeEffect = mEffect
-                                           ? mEffect->AsKeyframeEffect()
-                                           : nullptr;
+  KeyframeEffect* keyframeEffect = mEffect
+                                   ? mEffect->AsKeyframeEffect()
+                                   : nullptr;
   if (!keyframeEffect) {
     return false;
   }
 
   // Are we starting at the same time as other geometric animations?
   // We check this before calling ShouldBlockAsyncTransformAnimations, partly
   // because it's cheaper, but also because it's often the most useful thing
   // to know when you're debugging performance.
@@ -1015,17 +1015,17 @@ Animation::HasLowerCompositeOrderThan(co
 
 void
 Animation::WillComposeStyle()
 {
   mFinishedAtLastComposeStyle = (PlayState() == AnimationPlayState::Finished);
 
   MOZ_ASSERT(mEffect);
 
-  KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
+  KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
   if (keyframeEffect) {
     keyframeEffect->WillComposeStyle();
   }
 }
 
 void
 Animation::ComposeStyle(RawServoAnimationValueMap& aComposeResult,
                         const nsCSSPropertyIDSet& aPropertiesToSkip)
@@ -1080,17 +1080,17 @@ Animation::ComposeStyle(RawServoAnimatio
         timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now());
       }
       if (!timeToUse.IsNull()) {
         mHoldTime = CurrentTimeFromTimelineTime(
           timeToUse.Value(), mStartTime.Value(), mPlaybackRate);
       }
     }
 
-    KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
+    KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
     if (keyframeEffect) {
       keyframeEffect->ComposeStyle(aComposeResult, aPropertiesToSkip);
     }
   }
 
   MOZ_ASSERT(pending == Pending(),
              "Pending state should not change during the course of compositing");
 }
@@ -1405,17 +1405,17 @@ Animation::UpdateFinishedState(SeekFlag 
 }
 
 void
 Animation::UpdateEffect()
 {
   if (mEffect) {
     UpdateRelevance();
 
-    KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
+    KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
     if (keyframeEffect) {
       keyframeEffect->NotifyAnimationTimingUpdated();
     }
   }
 }
 
 void
 Animation::FlushUnanimatedStyle() const
@@ -1429,17 +1429,17 @@ Animation::FlushUnanimatedStyle() const
 
 void
 Animation::PostUpdate()
 {
   if (!mEffect) {
     return;
   }
 
-  KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
+  KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
   if (!keyframeEffect) {
     return;
   }
   keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Layer);
 }
 
 void
 Animation::CancelPendingTasks()
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -358,17 +358,17 @@ public:
    * style on the main thread (e.g. because it is empty, or is
    * running on the compositor).
    */
   bool CanThrottle() const;
 
   /**
    * Updates various bits of state that we need to update as the result of
    * running ComposeStyle().
-   * See the comment of KeyframeEffectReadOnly::WillComposeStyle for more detail.
+   * See the comment of KeyframeEffect::WillComposeStyle for more detail.
    */
   void WillComposeStyle();
 
   /**
    * Updates |aComposeResult| with the animation values of this animation's
    * effect, if any.
    * Any properties contained in |aPropertiesToSkip| will not be added or
    * updated in |aComposeResult|.
--- a/dom/animation/AnimationEffectReadOnly.cpp
+++ b/dom/animation/AnimationEffectReadOnly.cpp
@@ -83,17 +83,17 @@ AnimationEffectReadOnly::SetSpecifiedTim
   mTiming->SetTimingParams(aTiming);
   if (mAnimation) {
     mAnimation->NotifyEffectTimingUpdated();
     if (AsKeyframeEffect()) {
       AsKeyframeEffect()->RequestRestyle(EffectCompositor::RestyleType::Layer);
     }
   }
   // For keyframe effects, NotifyEffectTimingUpdated above will eventually cause
-  // KeyframeEffectReadOnly::NotifyAnimationTimingUpdated to be called so it can
+  // KeyframeEffect::NotifyAnimationTimingUpdated to be called so it can
   // update its registration with the target element as necessary.
 }
 
 ComputedTiming
 AnimationEffectReadOnly::GetComputedTimingAt(
     const Nullable<TimeDuration>& aLocalTime,
     const TimingParams& aTiming,
     double aPlaybackRate)
--- a/dom/animation/AnimationEffectReadOnly.h
+++ b/dom/animation/AnimationEffectReadOnly.h
@@ -21,30 +21,30 @@
 namespace mozilla {
 
 struct ElementPropertyTransition;
 
 namespace dom {
 
 class Animation;
 class AnimationEffectTimingReadOnly;
-class KeyframeEffectReadOnly;
+class KeyframeEffect;
 struct ComputedTimingProperties;
 
 class AnimationEffectReadOnly : public nsISupports,
                                 public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AnimationEffectReadOnly)
 
   AnimationEffectReadOnly(nsIDocument* aDocument,
                           AnimationEffectTimingReadOnly* aTiming);
 
-  virtual KeyframeEffectReadOnly* AsKeyframeEffect() { return nullptr; }
+  virtual KeyframeEffect* AsKeyframeEffect() { return nullptr; }
 
   virtual ElementPropertyTransition* AsTransition() { return nullptr; }
   virtual const ElementPropertyTransition* AsTransition() const
   {
     return nullptr;
   }
 
   nsISupports* GetParentObject() const { return mDocument; }
--- a/dom/animation/AnimationEffectTiming.cpp
+++ b/dom/animation/AnimationEffectTiming.cpp
@@ -17,17 +17,17 @@ namespace dom {
 
 JSObject*
 AnimationEffectTiming::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return AnimationEffectTimingBinding::Wrap(aCx, this, aGivenProto);
 }
 
 static inline void
-PostSpecifiedTimingUpdated(KeyframeEffectReadOnly* aEffect)
+PostSpecifiedTimingUpdated(KeyframeEffect* aEffect)
 {
   if (aEffect) {
     aEffect->NotifySpecifiedTimingUpdated();
   }
 }
 
 void
 AnimationEffectTiming::SetDelay(double aDelay)
--- a/dom/animation/AnimationEffectTiming.h
+++ b/dom/animation/AnimationEffectTiming.h
@@ -9,24 +9,24 @@
 
 #include "mozilla/dom/AnimationEffectTimingReadOnly.h"
 #include "mozilla/Attributes.h" // For MOZ_NON_OWNING_REF
 #include "nsStringFwd.h"
 
 namespace mozilla {
 namespace dom {
 
-class KeyframeEffectReadOnly;
+class KeyframeEffect;
 
 class AnimationEffectTiming : public AnimationEffectTimingReadOnly
 {
 public:
   AnimationEffectTiming(nsIDocument* aDocument,
                         const TimingParams& aTiming,
-                        KeyframeEffectReadOnly* aEffect)
+                        KeyframeEffect* aEffect)
     : AnimationEffectTimingReadOnly(aDocument, aTiming)
     , mEffect(aEffect) { }
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void Unlink() override { mEffect = nullptr; }
 
   void SetDelay(double aDelay);
@@ -35,15 +35,15 @@ public:
   void SetIterationStart(double aIterationStart, ErrorResult& aRv);
   void SetIterations(double aIterations, ErrorResult& aRv);
   void SetDuration(const UnrestrictedDoubleOrString& aDuration,
                    ErrorResult& aRv);
   void SetDirection(const PlaybackDirection& aDirection);
   void SetEasing(const nsAString& aEasing, ErrorResult& aRv);
 
 private:
-  KeyframeEffectReadOnly* MOZ_NON_OWNING_REF mEffect;
+  KeyframeEffect* MOZ_NON_OWNING_REF mEffect;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_AnimationEffectTiming_h
--- a/dom/animation/AnimationUtils.cpp
+++ b/dom/animation/AnimationUtils.cpp
@@ -73,17 +73,17 @@ AnimationUtils::IsOffscreenThrottlingEna
 
   return sOffscreenThrottlingEnabled;
 }
 
 /* static */ bool
 AnimationUtils::EffectSetContainsAnimatedScale(EffectSet& aEffects,
                                                const nsIFrame* aFrame)
 {
-  for (const dom::KeyframeEffectReadOnly* effect : aEffects) {
+  for (const dom::KeyframeEffect* effect : aEffects) {
     if (effect->ContainsAnimatedScale(aFrame)) {
       return true;
     }
   }
 
   return false;
 }
 
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -33,17 +33,17 @@
 #include "nsIPresShell.h"
 #include "nsIPresShellInlines.h"
 #include "nsLayoutUtils.h"
 #include "nsTArray.h"
 #include "PendingAnimationTracker.h"
 
 using mozilla::dom::Animation;
 using mozilla::dom::Element;
-using mozilla::dom::KeyframeEffectReadOnly;
+using mozilla::dom::KeyframeEffect;
 
 namespace mozilla {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(EffectCompositor)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EffectCompositor)
   for (auto& elementSet : tmp->mElementsToRestyle) {
     elementSet.Clear();
@@ -76,17 +76,17 @@ enum class MatchForCompositor {
   // This animation does not match or can't be run on the compositor and,
   // furthermore, its presence means we should not run any animations for this
   // property on the compositor.
   NoAndBlockThisProperty
 };
 }
 
 static MatchForCompositor
-IsMatchForCompositor(const KeyframeEffectReadOnly& aEffect,
+IsMatchForCompositor(const KeyframeEffect& aEffect,
                      nsCSSPropertyID aProperty,
                      const nsIFrame* aFrame)
 {
   const Animation* animation = aEffect.GetAnimation();
   MOZ_ASSERT(animation);
 
   if (!animation->IsRelevant()) {
     return MatchForCompositor::No;
@@ -192,17 +192,17 @@ FindAnimationsForCompositor(const nsIFra
         AnimationPerformanceWarning(
           AnimationPerformanceWarning::Type::HasRenderingObserver));
       return false;
     }
     content = content->GetParent();
   }
 
   bool foundRunningAnimations = false;
-  for (KeyframeEffectReadOnly* effect : *effects) {
+  for (KeyframeEffect* effect : *effects) {
     MatchForCompositor matchResult =
       IsMatchForCompositor(*effect, aProperty, aFrame);
 
     if (matchResult == MatchForCompositor::NoAndBlockThisProperty) {
       // For a given |aFrame|, we don't want some animations of |aProperty| to
       // run on the compositor and others to run on the main thread, so if any
       // need to be synchronized with the main thread, run them all there.
       if (aMatches) {
@@ -390,33 +390,31 @@ EffectCompositor::UpdateEffectProperties
     return;
   }
 
   // Style context (Gecko) or computed values (Stylo) change might cause CSS
   // cascade level, e.g removing !important, so we should update the cascading
   // result.
   effectSet->MarkCascadeNeedsUpdate();
 
-  for (KeyframeEffectReadOnly* effect : *effectSet) {
+  for (KeyframeEffect* effect : *effectSet) {
     effect->UpdateProperties(aStyle);
   }
 }
 
 
 namespace {
   class EffectCompositeOrderComparator {
   public:
-    bool Equals(const KeyframeEffectReadOnly* a,
-                const KeyframeEffectReadOnly* b) const
+    bool Equals(const KeyframeEffect* a, const KeyframeEffect* b) const
     {
       return a == b;
     }
 
-    bool LessThan(const KeyframeEffectReadOnly* a,
-                  const KeyframeEffectReadOnly* b) const
+    bool LessThan(const KeyframeEffect* a, const KeyframeEffect* b) const
     {
       MOZ_ASSERT(a->GetAnimation() && b->GetAnimation());
       MOZ_ASSERT(
         Equals(a, b) ||
         a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation()) !=
           b->GetAnimation()->HasLowerCompositeOrderThan(*a->GetAnimation()));
       return a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation());
     }
@@ -439,30 +437,30 @@ EffectCompositor::GetServoAnimationRule(
              " without a pres shell (e.g. XMLHttpRequest documents)");
 
   EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
   if (!effectSet) {
     return false;
   }
 
   // Get a list of effects sorted by composite order.
-  nsTArray<KeyframeEffectReadOnly*> sortedEffectList(effectSet->Count());
-  for (KeyframeEffectReadOnly* effect : *effectSet) {
+  nsTArray<KeyframeEffect*> sortedEffectList(effectSet->Count());
+  for (KeyframeEffect* effect : *effectSet) {
     sortedEffectList.AppendElement(effect);
   }
   sortedEffectList.Sort(EffectCompositeOrderComparator());
 
   // If multiple animations affect the same property, animations with higher
   // composite order (priority) override or add or animations with lower
   // priority.
   const nsCSSPropertyIDSet propertiesToSkip =
     aCascadeLevel == CascadeLevel::Animations
       ? effectSet->PropertiesForAnimationsLevel().Inverse()
       : effectSet->PropertiesForAnimationsLevel();
-  for (KeyframeEffectReadOnly* effect : sortedEffectList) {
+  for (KeyframeEffect* effect : sortedEffectList) {
     effect->GetAnimation()->ComposeStyle(*aAnimationValues, propertiesToSkip);
   }
 
   MOZ_ASSERT(effectSet == EffectSet::GetEffectSet(aElement, aPseudoType),
              "EffectSet should not change while composing style");
 
   return true;
 }
@@ -528,17 +526,17 @@ EffectCompositor::GetAnimationsForCompos
 EffectCompositor::ClearIsRunningOnCompositor(const nsIFrame *aFrame,
                                              nsCSSPropertyID aProperty)
 {
   EffectSet* effects = EffectSet::GetEffectSet(aFrame);
   if (!effects) {
     return;
   }
 
-  for (KeyframeEffectReadOnly* effect : *effects) {
+  for (KeyframeEffect* effect : *effects) {
     effect->SetIsRunningOnCompositor(aProperty, false);
   }
 }
 
 /* static */ void
 EffectCompositor::MaybeUpdateCascadeResults(Element* aElement,
                                             CSSPseudoElementType aPseudoType)
 {
@@ -602,17 +600,17 @@ EffectCompositor::GetOverriddenPropertie
   Element* elementToRestyle = GetElementToRestyle(aElement, aPseudoType);
   if (!elementToRestyle) {
     return result;
   }
 
   AutoTArray<nsCSSPropertyID, LayerAnimationInfo::kRecords> propertiesToTrack;
   {
     nsCSSPropertyIDSet propertiesToTrackAsSet;
-    for (KeyframeEffectReadOnly* effect : aEffectSet) {
+    for (KeyframeEffect* effect : aEffectSet) {
       for (const AnimationProperty& property : effect->Properties()) {
         if (nsCSSProps::PropHasFlags(property.mProperty,
                                      CSSPropFlags::CanAnimateOnCompositor) &&
             !propertiesToTrackAsSet.HasProperty(property.mProperty)) {
           propertiesToTrackAsSet.AddProperty(property.mProperty);
           propertiesToTrack.AppendElement(property.mProperty);
         }
       }
@@ -642,18 +640,18 @@ EffectCompositor::UpdateCascadeResults(E
   MOZ_ASSERT(EffectSet::GetEffectSet(aElement, aPseudoType) == &aEffectSet,
              "Effect set should correspond to the specified (pseudo-)element");
   if (aEffectSet.IsEmpty()) {
     aEffectSet.MarkCascadeUpdated();
     return;
   }
 
   // Get a list of effects sorted by composite order.
-  nsTArray<KeyframeEffectReadOnly*> sortedEffectList(aEffectSet.Count());
-  for (KeyframeEffectReadOnly* effect : aEffectSet) {
+  nsTArray<KeyframeEffect*> sortedEffectList(aEffectSet.Count());
+  for (KeyframeEffect* effect : aEffectSet) {
     sortedEffectList.AppendElement(effect);
   }
   sortedEffectList.Sort(EffectCompositeOrderComparator());
 
   // Get properties that override the *animations* level of the cascade.
   //
   // We only do this for properties that we can animate on the compositor
   // since we will apply other properties on the main thread where the usual
@@ -690,17 +688,17 @@ EffectCompositor::UpdateCascadeResults(E
   nsCSSPropertyIDSet prevPropertiesForAnimationsLevel =
     propertiesForAnimationsLevel;
 
   propertiesWithImportantRules.Empty();
   propertiesForAnimationsLevel.Empty();
 
   nsCSSPropertyIDSet propertiesForTransitionsLevel;
 
-  for (const KeyframeEffectReadOnly* effect : sortedEffectList) {
+  for (const KeyframeEffect* effect : sortedEffectList) {
     MOZ_ASSERT(effect->GetAnimation(),
                "Effects on a target element should have an Animation");
     CascadeLevel cascadeLevel = effect->GetAnimation()->CascadeLevel();
 
     for (const AnimationProperty& prop : effect->Properties()) {
       if (overriddenProperties.HasProperty(prop.mProperty)) {
         propertiesWithImportantRules.AddProperty(prop.mProperty);
       }
@@ -761,17 +759,17 @@ EffectCompositor::SetPerformanceWarning(
   nsCSSPropertyID aProperty,
   const AnimationPerformanceWarning& aWarning)
 {
   EffectSet* effects = EffectSet::GetEffectSet(aFrame);
   if (!effects) {
     return;
   }
 
-  for (KeyframeEffectReadOnly* effect : *effects) {
+  for (KeyframeEffect* effect : *effects) {
     effect->SetPerformanceWarning(aProperty, aWarning);
   }
 }
 
 bool
 EffectCompositor::PreTraverse(ServoTraversalFlags aFlags)
 {
   return PreTraverseInSubtree(aFlags, nullptr);
@@ -896,17 +894,17 @@ EffectCompositor::PreTraverseInSubtree(S
       EffectSet* effects = EffectSet::GetEffectSet(target.mElement,
                                                    target.mPseudoType);
       if (!effects) {
         // Drop EffectSets that have been destroyed.
         iter.Remove();
         continue;
       }
 
-      for (KeyframeEffectReadOnly* effect : *effects) {
+      for (KeyframeEffect* effect : *effects) {
         effect->GetAnimation()->WillComposeStyle();
       }
 
       // Remove the element from the list of elements to restyle since we are
       // about to restyle it.
       iter.Remove();
     }
 
@@ -972,17 +970,17 @@ EffectCompositor::PreTraverse(dom::Eleme
                                     cascadeLevel == CascadeLevel::Transitions
                                       ? eRestyle_CSSTransitions
                                       : eRestyle_CSSAnimations);
 
     EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType);
     if (effects) {
       MaybeUpdateCascadeResults(aElement, aPseudoType);
 
-      for (KeyframeEffectReadOnly* effect : *effects) {
+      for (KeyframeEffect* effect : *effects) {
         effect->GetAnimation()->WillComposeStyle();
       }
     }
 
     elementSet.Remove(key);
     found = true;
   }
   return found;
--- a/dom/animation/EffectSet.cpp
+++ b/dom/animation/EffectSet.cpp
@@ -144,28 +144,28 @@ EffectSet::GetEffectSetPropertyAtom(CSSP
     default:
       NS_NOTREACHED("Should not try to get animation effects for a pseudo "
                     "other that :before or :after");
       return nullptr;
   }
 }
 
 void
-EffectSet::AddEffect(dom::KeyframeEffectReadOnly& aEffect)
+EffectSet::AddEffect(dom::KeyframeEffect& aEffect)
 {
   if (mEffects.Contains(&aEffect)) {
     return;
   }
 
   mEffects.PutEntry(&aEffect);
   MarkCascadeNeedsUpdate();
 }
 
 void
-EffectSet::RemoveEffect(dom::KeyframeEffectReadOnly& aEffect)
+EffectSet::RemoveEffect(dom::KeyframeEffect& aEffect)
 {
   if (!mEffects.Contains(&aEffect)) {
     return;
   }
 
   mEffects.RemoveEntry(&aEffect);
   MarkCascadeNeedsUpdate();
 }
--- a/dom/animation/EffectSet.h
+++ b/dom/animation/EffectSet.h
@@ -61,26 +61,26 @@ public:
   static EffectSet* GetEffectSet(const dom::Element* aElement,
                                  CSSPseudoElementType aPseudoType);
   static EffectSet* GetEffectSet(const nsIFrame* aFrame);
   static EffectSet* GetOrCreateEffectSet(dom::Element* aElement,
                                          CSSPseudoElementType aPseudoType);
   static void DestroyEffectSet(dom::Element* aElement,
                                CSSPseudoElementType aPseudoType);
 
-  void AddEffect(dom::KeyframeEffectReadOnly& aEffect);
-  void RemoveEffect(dom::KeyframeEffectReadOnly& aEffect);
+  void AddEffect(dom::KeyframeEffect& aEffect);
+  void RemoveEffect(dom::KeyframeEffect& aEffect);
 
   void SetMayHaveOpacityAnimation() { mMayHaveOpacityAnim = true; }
   bool MayHaveOpacityAnimation() const { return mMayHaveOpacityAnim; }
   void SetMayHaveTransformAnimation() { mMayHaveTransformAnim = true; }
   bool MayHaveTransformAnimation() const { return mMayHaveTransformAnim; }
 
 private:
-  typedef nsTHashtable<nsRefPtrHashKey<dom::KeyframeEffectReadOnly>>
+  typedef nsTHashtable<nsRefPtrHashKey<dom::KeyframeEffect>>
     OwningEffectSet;
 
 public:
   // A simple iterator to support iterating over the effects in this object in
   // range-based for loops.
   //
   // This allows us to avoid exposing mEffects directly and saves the
   // caller from having to dereference hashtable iterators using
@@ -131,17 +131,17 @@ public:
     }
 
     Iterator& operator++() {
       MOZ_ASSERT(!Done());
       mHashIterator.Next();
       return *this;
     }
 
-    dom::KeyframeEffectReadOnly* operator* ()
+    dom::KeyframeEffect* operator*()
     {
       MOZ_ASSERT(!Done());
       return mHashIterator.Get()->GetKey();
     }
 
   private:
     Iterator() = delete;
     Iterator(const Iterator&) = delete;
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -49,70 +49,69 @@ PropertyValuePair::operator==(const Prop
     return false;
   }
   return Servo_DeclarationBlock_Equals(mServoDeclarationBlock,
                                        aOther.mServoDeclarationBlock);
 }
 
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadOnly,
+NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffect,
                                    AnimationEffectReadOnly,
                                    mTarget)
 
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffectReadOnly,
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffect,
                                                AnimationEffectReadOnly)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(KeyframeEffectReadOnly)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(KeyframeEffect)
 NS_INTERFACE_MAP_END_INHERITING(AnimationEffectReadOnly)
 
-NS_IMPL_ADDREF_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
-NS_IMPL_RELEASE_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
+NS_IMPL_ADDREF_INHERITED(KeyframeEffect, AnimationEffectReadOnly)
+NS_IMPL_RELEASE_INHERITED(KeyframeEffect, AnimationEffectReadOnly)
 
-KeyframeEffectReadOnly::KeyframeEffectReadOnly(
+KeyframeEffect::KeyframeEffect(
   nsIDocument* aDocument,
   const Maybe<OwningAnimationTarget>& aTarget,
   const TimingParams& aTiming,
   const KeyframeEffectParams& aOptions)
-  : KeyframeEffectReadOnly(aDocument, aTarget,
-                           new AnimationEffectTimingReadOnly(aDocument,
-                                                             aTiming),
-                           aOptions)
+  : KeyframeEffect(aDocument, aTarget,
+                   new AnimationEffectTiming(aDocument, aTiming, this),
+                   aOptions)
 {
 }
 
-KeyframeEffectReadOnly::KeyframeEffectReadOnly(
+KeyframeEffect::KeyframeEffect(
   nsIDocument* aDocument,
   const Maybe<OwningAnimationTarget>& aTarget,
   AnimationEffectTimingReadOnly* aTiming,
   const KeyframeEffectParams& aOptions)
   : AnimationEffectReadOnly(aDocument, aTiming)
   , mTarget(aTarget)
   , mEffectOptions(aOptions)
   , mInEffectOnLastAnimationTimingUpdate(false)
   , mCumulativeChangeHint(nsChangeHint(0))
 {
 }
 
 JSObject*
-KeyframeEffectReadOnly::WrapObject(JSContext* aCx,
+KeyframeEffect::WrapObject(JSContext* aCx,
                                    JS::Handle<JSObject*> aGivenProto)
 {
-  return KeyframeEffectReadOnlyBinding::Wrap(aCx, this, aGivenProto);
+  return KeyframeEffectBinding::Wrap(aCx, this, aGivenProto);
 }
 
-IterationCompositeOperation KeyframeEffectReadOnly::IterationComposite(
+IterationCompositeOperation KeyframeEffect::IterationComposite(
   CallerType /*aCallerType*/) const
 {
   return mEffectOptions.mIterationComposite;
 }
 
 void
-KeyframeEffectReadOnly::SetIterationComposite(
+KeyframeEffect::SetIterationComposite(
   const IterationCompositeOperation& aIterationComposite,
   CallerType aCallerType)
 {
   // Ignore iterationComposite if the Web Animations API is not enabled,
   // then the default value 'Replace' will be used.
   if (!nsDocument::IsWebAnimationsEnabled(aCallerType)) {
     return;
   }
@@ -125,23 +124,23 @@ KeyframeEffectReadOnly::SetIterationComp
     nsNodeUtils::AnimationChanged(mAnimation);
   }
 
   mEffectOptions.mIterationComposite = aIterationComposite;
   RequestRestyle(EffectCompositor::RestyleType::Layer);
 }
 
 CompositeOperation
-KeyframeEffectReadOnly::Composite() const
+KeyframeEffect::Composite() const
 {
   return mEffectOptions.mComposite;
 }
 
 void
-KeyframeEffectReadOnly::SetComposite(const CompositeOperation& aComposite)
+KeyframeEffect::SetComposite(const CompositeOperation& aComposite)
 {
   if (mEffectOptions.mComposite == aComposite) {
     return;
   }
 
   mEffectOptions.mComposite = aComposite;
 
   if (mAnimation && mAnimation->IsRelevant()) {
@@ -152,17 +151,17 @@ KeyframeEffectReadOnly::SetComposite(con
     RefPtr<ComputedStyle> computedStyle = GetTargetComputedStyle();
     if (computedStyle) {
       UpdateProperties(computedStyle);
     }
   }
 }
 
 void
-KeyframeEffectReadOnly::NotifySpecifiedTimingUpdated()
+KeyframeEffect::NotifySpecifiedTimingUpdated()
 {
   // Use the same document for a pseudo element and its parent element.
   // Use nullptr if we don't have mTarget, so disable the mutation batch.
   nsAutoAnimationMutationBatch mb(mTarget ? mTarget->mElement->OwnerDoc()
                                           : nullptr);
 
   if (mAnimation) {
     mAnimation->NotifyEffectTimingUpdated();
@@ -171,17 +170,17 @@ KeyframeEffectReadOnly::NotifySpecifiedT
       nsNodeUtils::AnimationChanged(mAnimation);
     }
 
     RequestRestyle(EffectCompositor::RestyleType::Layer);
   }
 }
 
 void
-KeyframeEffectReadOnly::NotifyAnimationTimingUpdated()
+KeyframeEffect::NotifyAnimationTimingUpdated()
 {
   UpdateTargetRegistration();
 
   // If the effect is not relevant it will be removed from the target
   // element's effect set. However, effects not in the effect set
   // will not be included in the set of candidate effects for running on
   // the compositor and hence they won't have their compositor status
   // updated. As a result, we need to make sure we clear their compositor
@@ -238,33 +237,33 @@ KeyframesEqualIgnoringComputedOffsets(co
       return false;
     }
   }
   return true;
 }
 
 // https://drafts.csswg.org/web-animations/#dom-keyframeeffect-setkeyframes
 void
-KeyframeEffectReadOnly::SetKeyframes(JSContext* aContext,
-                                     JS::Handle<JSObject*> aKeyframes,
-                                     ErrorResult& aRv)
+KeyframeEffect::SetKeyframes(JSContext* aContext,
+                             JS::Handle<JSObject*> aKeyframes,
+                             ErrorResult& aRv)
 {
   nsTArray<Keyframe> keyframes =
     KeyframeUtils::GetKeyframesFromObject(aContext, mDocument, aKeyframes, aRv);
   if (aRv.Failed()) {
     return;
   }
 
   RefPtr<ComputedStyle> style = GetTargetComputedStyle();
   SetKeyframes(Move(keyframes), style);
 }
 
 
 void
-KeyframeEffectReadOnly::SetKeyframes(
+KeyframeEffect::SetKeyframes(
   nsTArray<Keyframe>&& aKeyframes,
   const ComputedStyle* aStyle)
 {
   if (KeyframesEqualIgnoringComputedOffsets(aKeyframes, mKeyframes)) {
     return;
   }
 
   mKeyframes = Move(aKeyframes);
@@ -278,18 +277,17 @@ KeyframeEffectReadOnly::SetKeyframes(
   // style (e.g. the target element is not associated with any document).
   if (aStyle) {
     UpdateProperties(aStyle);
     MaybeUpdateFrameForCompositor();
   }
 }
 
 const AnimationProperty*
-KeyframeEffectReadOnly::GetEffectiveAnimationOfProperty(
-  nsCSSPropertyID aProperty) const
+KeyframeEffect::GetEffectiveAnimationOfProperty(nsCSSPropertyID aProperty) const
 {
   EffectSet* effectSet =
     EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
   for (size_t propIdx = 0, propEnd = mProperties.Length();
        propIdx != propEnd; ++propIdx) {
     if (aProperty == mProperties[propIdx].mProperty) {
       const AnimationProperty* result = &mProperties[propIdx];
       // Skip if there is a property of animation level that is overridden
@@ -303,17 +301,17 @@ KeyframeEffectReadOnly::GetEffectiveAnim
       }
       return result;
     }
   }
   return nullptr;
 }
 
 bool
-KeyframeEffectReadOnly::HasAnimationOfProperty(nsCSSPropertyID aProperty) const
+KeyframeEffect::HasAnimationOfProperty(nsCSSPropertyID aProperty) const
 {
   for (const AnimationProperty& property : mProperties) {
     if (property.mProperty == aProperty) {
       return true;
     }
   }
   return false;
 }
@@ -337,17 +335,17 @@ SpecifiedKeyframeArraysAreEqual(const ns
     }
   }
 
   return true;
 }
 #endif
 
 void
-KeyframeEffectReadOnly::UpdateProperties(const ComputedStyle* aStyle)
+KeyframeEffect::UpdateProperties(const ComputedStyle* aStyle)
 {
   MOZ_ASSERT(aStyle);
 
   nsTArray<AnimationProperty> properties = BuildProperties(aStyle);
 
   // We need to update base styles even if any properties are not changed at all
   // since base styles might have been changed due to parent style changes, etc.
   EnsureBaseStyles(aStyle, properties);
@@ -377,17 +375,17 @@ KeyframeEffectReadOnly::UpdateProperties
 
   MarkCascadeNeedsUpdate();
 
   RequestRestyle(EffectCompositor::RestyleType::Layer);
 }
 
 
 void
-KeyframeEffectReadOnly::EnsureBaseStyles(
+KeyframeEffect::EnsureBaseStyles(
   const ComputedStyle* aComputedValues,
   const nsTArray<AnimationProperty>& aProperties)
 {
   if (!mTarget) {
     return;
   }
 
   mBaseStyleValuesForServo.Clear();
@@ -413,17 +411,17 @@ KeyframeEffectReadOnly::EnsureBaseStyles
     EnsureBaseStyle(property,
                     presContext,
                     aComputedValues,
                     baseComputedStyle);
   }
 }
 
 void
-KeyframeEffectReadOnly::EnsureBaseStyle(
+KeyframeEffect::EnsureBaseStyle(
   const AnimationProperty& aProperty,
   nsPresContext* aPresContext,
   const ComputedStyle* aComputedStyle,
  RefPtr<ComputedStyle>& aBaseComputedStyle)
 {
   bool hasAdditiveValues = false;
 
   for (const AnimationPropertySegment& segment : aProperty.mSegments) {
@@ -446,42 +444,42 @@ KeyframeEffectReadOnly::EnsureBaseStyle(
   }
   RefPtr<RawServoAnimationValue> baseValue =
     Servo_ComputedValues_ExtractAnimationValue(aBaseComputedStyle,
                                                aProperty.mProperty).Consume();
   mBaseStyleValuesForServo.Put(aProperty.mProperty, baseValue);
 }
 
 void
-KeyframeEffectReadOnly::WillComposeStyle()
+KeyframeEffect::WillComposeStyle()
 {
   ComputedTiming computedTiming = GetComputedTiming();
   mProgressOnLastCompose = computedTiming.mProgress;
   mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration;
 }
 
 
 void
-KeyframeEffectReadOnly::ComposeStyleRule(
+KeyframeEffect::ComposeStyleRule(
   RawServoAnimationValueMap& aAnimationValues,
   const AnimationProperty& aProperty,
   const AnimationPropertySegment& aSegment,
   const ComputedTiming& aComputedTiming)
 {
   Servo_AnimationCompose(&aAnimationValues,
                          &mBaseStyleValuesForServo,
                          aProperty.mProperty,
                          &aSegment,
                          &aProperty.mSegments.LastElement(),
                          &aComputedTiming,
                          mEffectOptions.mIterationComposite);
 }
 
 void
-KeyframeEffectReadOnly::ComposeStyle(
+KeyframeEffect::ComposeStyle(
   RawServoAnimationValueMap& aComposeResult,
   const nsCSSPropertyIDSet& aPropertiesToSkip)
 {
   ComputedTiming computedTiming = GetComputedTiming();
 
   // If the progress is null, we don't have fill data for the current
   // time so we shouldn't animate.
   if (computedTiming.mProgress.IsNull()) {
@@ -538,33 +536,33 @@ KeyframeEffectReadOnly::ComposeStyle(
       MOZ_ASSERT(effectSet, "ComposeStyle should only be called on an effect "
                             "that is part of an effect set");
       effectSet->UpdateLastTransformSyncTime(now);
     }
   }
 }
 
 bool
-KeyframeEffectReadOnly::IsRunningOnCompositor() const
+KeyframeEffect::IsRunningOnCompositor() const
 {
   // We consider animation is running on compositor if there is at least
   // one property running on compositor.
   // Animation.IsRunningOnCompotitor will return more fine grained
   // information in bug 1196114.
   for (const AnimationProperty& property : mProperties) {
     if (property.mIsRunningOnCompositor) {
       return true;
     }
   }
   return false;
 }
 
 void
-KeyframeEffectReadOnly::SetIsRunningOnCompositor(nsCSSPropertyID aProperty,
-                                                 bool aIsRunning)
+KeyframeEffect::SetIsRunningOnCompositor(nsCSSPropertyID aProperty,
+                                         bool aIsRunning)
 {
   MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
                                       CSSPropFlags::CanAnimateOnCompositor),
              "Property being animated on compositor is a recognized "
              "compositor-animatable property");
   for (AnimationProperty& property : mProperties) {
     if (property.mProperty == aProperty) {
       property.mIsRunningOnCompositor = aIsRunning;
@@ -575,17 +573,17 @@ KeyframeEffectReadOnly::SetIsRunningOnCo
         property.mPerformanceWarning.reset();
       }
       return;
     }
   }
 }
 
 void
-KeyframeEffectReadOnly::ResetIsRunningOnCompositor()
+KeyframeEffect::ResetIsRunningOnCompositor()
 {
   for (AnimationProperty& property : mProperties) {
     property.mIsRunningOnCompositor = false;
   }
 }
 
 static const KeyframeEffectOptions&
 KeyframeEffectOptionsFromUnion(
@@ -619,17 +617,17 @@ KeyframeEffectParamsFromUnion(const Opti
   const KeyframeEffectOptions& options =
     KeyframeEffectOptionsFromUnion(aOptions);
   result.mIterationComposite = options.mIterationComposite;
   result.mComposite = options.mComposite;
   return result;
 }
 
 /* static */ Maybe<OwningAnimationTarget>
-KeyframeEffectReadOnly::ConvertTarget(
+KeyframeEffect::ConvertTarget(
   const Nullable<ElementOrCSSPseudoElement>& aTarget)
 {
   // Return value optimization.
   Maybe<OwningAnimationTarget> result;
 
   if (aTarget.IsNull()) {
     return result;
   }
@@ -644,33 +642,32 @@ KeyframeEffectReadOnly::ConvertTarget(
     RefPtr<Element> elem = target.GetAsCSSPseudoElement().ParentElement();
     result.emplace(elem, target.GetAsCSSPseudoElement().GetType());
   }
   return result;
 }
 
 template <class KeyframeEffectType, class OptionsType>
 /* static */ already_AddRefed<KeyframeEffectType>
-KeyframeEffectReadOnly::ConstructKeyframeEffect(
+KeyframeEffect::ConstructKeyframeEffect(
     const GlobalObject& aGlobal,
     const Nullable<ElementOrCSSPseudoElement>& aTarget,
     JS::Handle<JSObject*> aKeyframes,
     const OptionsType& aOptions,
     ErrorResult& aRv)
 {
   // We should get the document from `aGlobal` instead of the current Realm
   // to make this works in Xray case.
   //
   // In all non-Xray cases, `aGlobal` matches the current Realm, so this
   // matches the spec behavior.
   //
   // In Xray case, the new objects should be created using the document of
-  // the target global, but KeyframeEffect and KeyframeEffectReadOnly
-  // constructors are called in the caller's compartment to access
-  // `aKeyframes` object.
+  // the target global, but the KeyframeEffect constructors are called in the
+  // caller's compartment to access `aKeyframes` object.
   nsIDocument* doc = AnimationUtils::GetDocumentFromGlobal(aGlobal.Get());
   if (!doc) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   TimingParams timingParams =
     TimingParams::FromOptionsUnion(aOptions, doc, aRv);
@@ -690,29 +687,29 @@ KeyframeEffectReadOnly::ConstructKeyfram
     return nullptr;
   }
 
   return effect.forget();
 }
 
 template<class KeyframeEffectType>
 /* static */ already_AddRefed<KeyframeEffectType>
-KeyframeEffectReadOnly::ConstructKeyframeEffect(const GlobalObject& aGlobal,
-                                                KeyframeEffectReadOnly& aSource,
-                                                ErrorResult& aRv)
+KeyframeEffect::ConstructKeyframeEffect(const GlobalObject& aGlobal,
+                                        KeyframeEffect& aSource,
+                                        ErrorResult& aRv)
 {
   nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
   if (!doc) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  // Create a new KeyframeEffectReadOnly object with aSource's target,
+  // Create a new KeyframeEffect object with aSource's target,
   // iteration composite operation, composite operation, and spacing mode.
-  // The constructor creates a new AnimationEffect(ReadOnly) object by
+  // The constructor creates a new AnimationEffectTiming object by
   // aSource's TimingParams.
   // Note: we don't need to re-throw exceptions since the value specified on
   //       aSource's timing object can be assumed valid.
   RefPtr<KeyframeEffectType> effect =
     new KeyframeEffectType(doc,
                            aSource.mTarget,
                            aSource.SpecifiedTiming(),
                            aSource.mEffectOptions);
@@ -724,17 +721,17 @@ KeyframeEffectReadOnly::ConstructKeyfram
   // Note: We don't call SetKeyframes directly, which might revise the
   //       computed offsets and rebuild the animation properties.
   effect->mKeyframes = aSource.mKeyframes;
   effect->mProperties = aSource.mProperties;
   return effect.forget();
 }
 
 nsTArray<AnimationProperty>
-KeyframeEffectReadOnly::BuildProperties(const ComputedStyle* aStyle)
+KeyframeEffect::BuildProperties(const ComputedStyle* aStyle)
 {
 
   MOZ_ASSERT(aStyle);
 
   nsTArray<AnimationProperty> result;
   // If mTarget is null, return an empty property array.
   if (!mTarget) {
     return result;
@@ -761,17 +758,17 @@ KeyframeEffectReadOnly::BuildProperties(
              " should not be modified");
 #endif
 
   mKeyframes.SwapElements(keyframesCopy);
   return result;
 }
 
 void
-KeyframeEffectReadOnly::UpdateTargetRegistration()
+KeyframeEffect::UpdateTargetRegistration()
 {
   if (!mTarget) {
     return;
   }
 
   bool isRelevant = mAnimation && mAnimation->IsRelevant();
 
   // Animation::IsRelevant() returns a cached value. It only updates when
@@ -793,17 +790,17 @@ KeyframeEffectReadOnly::UpdateTargetRegi
       f = f->GetNextContinuation();
     }
   } else if (!isRelevant && mInEffectSet) {
     UnregisterTarget();
   }
 }
 
 void
-KeyframeEffectReadOnly::UnregisterTarget()
+KeyframeEffect::UnregisterTarget()
 {
   if (!mInEffectSet) {
     return;
   }
 
   EffectSet* effectSet =
     EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
   MOZ_ASSERT(effectSet, "If mInEffectSet is true, there must be an EffectSet"
@@ -819,32 +816,31 @@ KeyframeEffectReadOnly::UnregisterTarget
   nsIFrame* f = GetPrimaryFrame();
   while (f) {
     f->MarkNeedsDisplayItemRebuild();
     f = f->GetNextContinuation();
   }
 }
 
 void
-KeyframeEffectReadOnly::RequestRestyle(
-  EffectCompositor::RestyleType aRestyleType)
+KeyframeEffect::RequestRestyle(EffectCompositor::RestyleType aRestyleType)
 {
    if (!mTarget) {
     return;
   }
   nsPresContext* presContext = nsContentUtils::GetContextForContent(mTarget->mElement);
   if (presContext && mAnimation) {
     presContext->EffectCompositor()->
       RequestRestyle(mTarget->mElement, mTarget->mPseudoType,
                      aRestyleType, mAnimation->CascadeLevel());
   }
 }
 
 already_AddRefed<ComputedStyle>
-KeyframeEffectReadOnly::GetTargetComputedStyle()
+KeyframeEffect::GetTargetComputedStyle()
 {
   if (!GetRenderedDocument()) {
     return nullptr;
   }
 
   MOZ_ASSERT(mTarget,
              "Should only have a document when we have a target element");
 
@@ -871,40 +867,50 @@ DumpAnimationProperties(nsTArray<Animati
       printf("  %f..%f: %s..%s\n", s.mFromKey, s.mToKey,
              NS_ConvertUTF16toUTF8(fromValue).get(),
              NS_ConvertUTF16toUTF8(toValue).get());
     }
   }
 }
 #endif
 
-/* static */ already_AddRefed<KeyframeEffectReadOnly>
-KeyframeEffectReadOnly::Constructor(
+/* static */ already_AddRefed<KeyframeEffect>
+KeyframeEffect::Constructor(
     const GlobalObject& aGlobal,
     const Nullable<ElementOrCSSPseudoElement>& aTarget,
     JS::Handle<JSObject*> aKeyframes,
     const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
     ErrorResult& aRv)
 {
-  return ConstructKeyframeEffect<KeyframeEffectReadOnly>(aGlobal, aTarget,
-                                                         aKeyframes, aOptions,
-                                                         aRv);
+  return ConstructKeyframeEffect<KeyframeEffect>(aGlobal, aTarget,
+                                                 aKeyframes, aOptions, aRv);
 }
 
-/* static */ already_AddRefed<KeyframeEffectReadOnly>
-KeyframeEffectReadOnly::Constructor(const GlobalObject& aGlobal,
-                                    KeyframeEffectReadOnly& aSource,
-                                    ErrorResult& aRv)
+/* static */ already_AddRefed<KeyframeEffect>
+KeyframeEffect::Constructor(const GlobalObject& aGlobal,
+                            KeyframeEffect& aSource,
+                            ErrorResult& aRv)
 {
-  return ConstructKeyframeEffect<KeyframeEffectReadOnly>(aGlobal, aSource, aRv);
+  return ConstructKeyframeEffect<KeyframeEffect>(aGlobal, aSource, aRv);
+}
+
+/* static */ already_AddRefed<KeyframeEffect>
+KeyframeEffect::Constructor(
+    const GlobalObject& aGlobal,
+    const Nullable<ElementOrCSSPseudoElement>& aTarget,
+    JS::Handle<JSObject*> aKeyframes,
+    const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
+    ErrorResult& aRv)
+{
+  return ConstructKeyframeEffect<KeyframeEffect>(aGlobal, aTarget, aKeyframes,
+                                                 aOptions, aRv);
 }
 
 void
-KeyframeEffectReadOnly::GetTarget(
-    Nullable<OwningElementOrCSSPseudoElement>& aRv) const
+KeyframeEffect::GetTarget(Nullable<OwningElementOrCSSPseudoElement>& aRv) const
 {
   if (!mTarget) {
     aRv.SetNull();
     return;
   }
 
   switch (mTarget->mPseudoType) {
     case CSSPseudoElementType::before:
@@ -920,18 +926,17 @@ KeyframeEffectReadOnly::GetTarget(
 
     default:
       NS_NOTREACHED("Animation of unsupported pseudo-type");
       aRv.SetNull();
   }
 }
 
 void
-KeyframeEffectReadOnly::SetTarget(
-  const Nullable<ElementOrCSSPseudoElement>& aTarget)
+KeyframeEffect::SetTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget)
 {
   Maybe<OwningAnimationTarget> newTarget = ConvertTarget(aTarget);
   if (mTarget == newTarget) {
     // Assign the same target, skip it.
     return;
   }
 
   if (mTarget) {
@@ -988,17 +993,17 @@ CreatePropertyValue(nsCSSPropertyID aPro
   } else {
     aResult.mEasing.Construct(NS_LITERAL_STRING("linear"));
   }
 
   aResult.mComposite = aComposite;
 }
 
 void
-KeyframeEffectReadOnly::GetProperties(
+KeyframeEffect::GetProperties(
     nsTArray<AnimationPropertyDetails>& aProperties,
     ErrorResult& aRv) const
 {
   for (const AnimationProperty& property : mProperties) {
     AnimationPropertyDetails propertyDetails;
     propertyDetails.mProperty =
       NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(property.mProperty));
     propertyDetails.mRunningOnCompositor = property.mIsRunningOnCompositor;
@@ -1053,19 +1058,19 @@ KeyframeEffectReadOnly::GetProperties(
       }
     }
 
     aProperties.AppendElement(propertyDetails);
   }
 }
 
 void
-KeyframeEffectReadOnly::GetKeyframes(JSContext*& aCx,
-                                     nsTArray<JSObject*>& aResult,
-                                     ErrorResult& aRv)
+KeyframeEffect::GetKeyframes(JSContext*& aCx,
+                             nsTArray<JSObject*>& aResult,
+                             ErrorResult& aRv)
 {
   MOZ_ASSERT(aResult.IsEmpty());
   MOZ_ASSERT(!aRv.Failed());
 
   if (!aResult.SetCapacity(mKeyframes.Length(), mozilla::fallible)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
@@ -1171,28 +1176,28 @@ KeyframeEffectReadOnly::GetKeyframes(JSC
       }
     }
 
     aResult.AppendElement(keyframeObject);
   }
 }
 
 /* static */ const TimeDuration
-KeyframeEffectReadOnly::OverflowRegionRefreshInterval()
+KeyframeEffect::OverflowRegionRefreshInterval()
 {
   // The amount of time we can wait between updating throttled animations
   // on the main thread that influence the overflow region.
   static const TimeDuration kOverflowRegionRefreshInterval =
     TimeDuration::FromMilliseconds(200);
 
   return kOverflowRegionRefreshInterval;
 }
 
 bool
-KeyframeEffectReadOnly::CanThrottle() const
+KeyframeEffect::CanThrottle() const
 {
   // Unthrottle if we are not in effect or current. This will be the case when
   // our owning animation has finished, is idle, or when we are in the delay
   // phase (but without a backwards fill). In each case the computed progress
   // value produced on each tick will be the same so we will skip requesting
   // unnecessary restyles in NotifyAnimationTimingUpdated. Any calls we *do* get
   // here will be because of a change in state (e.g. we are newly finished or
   // newly no longer in effect) in which case we shouldn't throttle the sample.
@@ -1283,34 +1288,34 @@ KeyframeEffectReadOnly::CanThrottle() co
       return false;
     }
   }
 
   return true;
 }
 
 bool
-KeyframeEffectReadOnly::CanThrottleTransformChanges(const nsIFrame& aFrame) const
+KeyframeEffect::CanThrottleTransformChanges(const nsIFrame& aFrame) const
 {
   TimeStamp now = aFrame.PresContext()->RefreshDriver()->MostRecentRefresh();
 
   EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement,
                                                  mTarget->mPseudoType);
   MOZ_ASSERT(effectSet, "CanThrottleTransformChanges is expected to be called"
                         " on an effect in an effect set");
   MOZ_ASSERT(mAnimation, "CanThrottleTransformChanges is expected to be called"
                          " on an effect with a parent animation");
   TimeStamp lastSyncTime = effectSet->LastTransformSyncTime();
   // If this animation can cause overflow, we can throttle some of the ticks.
   return (!lastSyncTime.IsNull() &&
     (now - lastSyncTime) < OverflowRegionRefreshInterval());
 }
 
 bool
-KeyframeEffectReadOnly::CanThrottleTransformChangesInScrollable(nsIFrame& aFrame) const
+KeyframeEffect::CanThrottleTransformChangesInScrollable(nsIFrame& aFrame) const
 {
   // If the target element is not associated with any documents, we don't care
   // it.
   nsIDocument* doc = GetRenderedDocument();
   if (!doc) {
     return true;
   }
 
@@ -1350,28 +1355,28 @@ KeyframeEffectReadOnly::CanThrottleTrans
       scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) {
     return true;
   }
 
   return false;
 }
 
 nsIFrame*
-KeyframeEffectReadOnly::GetStyleFrame() const
+KeyframeEffect::GetStyleFrame() const
 {
   nsIFrame* frame = GetPrimaryFrame();
   if (!frame) {
     return nullptr;
   }
 
   return nsLayoutUtils::GetStyleFrame(frame);
 }
 
 nsIFrame*
-KeyframeEffectReadOnly::GetPrimaryFrame() const
+KeyframeEffect::GetPrimaryFrame() const
 {
   nsIFrame* frame = nullptr;
   if (!mTarget) {
     return frame;
   }
 
   if (mTarget->mPseudoType == CSSPseudoElementType::before) {
     frame = nsLayoutUtils::GetBeforeFrame(mTarget->mElement);
@@ -1382,37 +1387,36 @@ KeyframeEffectReadOnly::GetPrimaryFrame(
     MOZ_ASSERT(mTarget->mPseudoType == CSSPseudoElementType::NotPseudo,
                "unknown mTarget->mPseudoType");
   }
 
   return frame;
 }
 
 nsIDocument*
-KeyframeEffectReadOnly::GetRenderedDocument() const
+KeyframeEffect::GetRenderedDocument() const
 {
   if (!mTarget) {
     return nullptr;
   }
   return mTarget->mElement->GetComposedDoc();
 }
 
 nsIPresShell*
-KeyframeEffectReadOnly::GetPresShell() const
+KeyframeEffect::GetPresShell() const
 {
   nsIDocument* doc = GetRenderedDocument();
   if (!doc) {
     return nullptr;
   }
   return doc->GetShell();
 }
 
 /* static */ bool
-KeyframeEffectReadOnly::IsGeometricProperty(
-  const nsCSSPropertyID aProperty)
+KeyframeEffect::IsGeometricProperty(const nsCSSPropertyID aProperty)
 {
   MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
              "Property should be a longhand property");
 
   switch (aProperty) {
     case eCSSProperty_bottom:
     case eCSSProperty_height:
     case eCSSProperty_left:
@@ -1429,17 +1433,17 @@ KeyframeEffectReadOnly::IsGeometricPrope
     case eCSSProperty_width:
       return true;
     default:
       return false;
   }
 }
 
 /* static */ bool
-KeyframeEffectReadOnly::CanAnimateTransformOnCompositor(
+KeyframeEffect::CanAnimateTransformOnCompositor(
   const nsIFrame* aFrame,
   AnimationPerformanceWarning::Type& aPerformanceWarning)
 {
   // Disallow OMTA for preserve-3d transform. Note that we check the style property
   // rather than Extend3DContext() since that can recurse back into this function
   // via HasOpacity(). See bug 779598.
   if (aFrame->Combines3DTransformWithAncestors() ||
       aFrame->StyleDisplay()->mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D) {
@@ -1461,17 +1465,17 @@ KeyframeEffectReadOnly::CanAnimateTransf
     aPerformanceWarning = AnimationPerformanceWarning::Type::TransformSVG;
     return false;
   }
 
   return true;
 }
 
 bool
-KeyframeEffectReadOnly::ShouldBlockAsyncTransformAnimations(
+KeyframeEffect::ShouldBlockAsyncTransformAnimations(
   const nsIFrame* aFrame,
   AnimationPerformanceWarning::Type& aPerformanceWarning) const
 {
   EffectSet* effectSet =
     EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
   for (const AnimationProperty& property : mProperties) {
     // If there is a property for animations level that is overridden by
     // !important rules, it should not block other animations from running
@@ -1508,29 +1512,29 @@ KeyframeEffectReadOnly::ShouldBlockAsync
   if (aFrame->StyleDisplay()->HasIndividualTransform()) {
     return true;
   }
 
   return false;
 }
 
 bool
-KeyframeEffectReadOnly::HasGeometricProperties() const
+KeyframeEffect::HasGeometricProperties() const
 {
   for (const AnimationProperty& property : mProperties) {
     if (IsGeometricProperty(property.mProperty)) {
       return true;
     }
   }
 
   return false;
 }
 
 void
-KeyframeEffectReadOnly::SetPerformanceWarning(
+KeyframeEffect::SetPerformanceWarning(
   nsCSSPropertyID aProperty,
   const AnimationPerformanceWarning& aWarning)
 {
   for (AnimationProperty& property : mProperties) {
     if (property.mProperty == aProperty &&
         (!property.mPerformanceWarning ||
          *property.mPerformanceWarning != aWarning)) {
       property.mPerformanceWarning = Some(aWarning);
@@ -1543,17 +1547,17 @@ KeyframeEffectReadOnly::SetPerformanceWa
       }
       return;
     }
   }
 }
 
 
 already_AddRefed<ComputedStyle>
-KeyframeEffectReadOnly::CreateComputedStyleForAnimationValue(
+KeyframeEffect::CreateComputedStyleForAnimationValue(
   nsCSSPropertyID aProperty,
   const AnimationValue& aValue,
   nsPresContext* aPresContext,
   const ComputedStyle* aBaseComputedStyle)
 {
   MOZ_ASSERT(aBaseComputedStyle,
              "CreateComputedStyleForAnimationValue needs to be called "
              "with a valid ComputedStyle");
@@ -1564,17 +1568,17 @@ KeyframeEffectReadOnly::CreateComputedSt
                                           mTarget->mPseudoType);
   MOZ_ASSERT(elementForResolve, "The target element shouldn't be null");
   return styleSet->ResolveServoStyleByAddingAnimation(elementForResolve,
                                                       aBaseComputedStyle,
                                                       aValue.mServo);
 }
 
 void
-KeyframeEffectReadOnly::CalculateCumulativeChangeHint(const ComputedStyle* aComputedStyle)
+KeyframeEffect::CalculateCumulativeChangeHint(const ComputedStyle* aComputedStyle)
 {
   mCumulativeChangeHint = nsChangeHint(0);
 
   nsPresContext* presContext =
     nsContentUtils::GetContextForContent(mTarget->mElement);
   if (!presContext) {
     // Change hints make no sense if we're not rendered.
     //
@@ -1626,17 +1630,17 @@ KeyframeEffectReadOnly::CalculateCumulat
         fromContext->CalcStyleDifference(toContext, &equalStructs);
 
       mCumulativeChangeHint |= changeHint;
     }
   }
 }
 
 void
-KeyframeEffectReadOnly::SetAnimation(Animation* aAnimation)
+KeyframeEffect::SetAnimation(Animation* aAnimation)
 {
   if (mAnimation == aAnimation) {
     return;
   }
 
   // Restyle for the old animation.
   RequestRestyle(EffectCompositor::RestyleType::Layer);
 
@@ -1651,30 +1655,30 @@ KeyframeEffectReadOnly::SetAnimation(Ani
   }
   NotifyAnimationTimingUpdated();
   if (mAnimation) {
     MarkCascadeNeedsUpdate();
   }
 }
 
 bool
-KeyframeEffectReadOnly::CanIgnoreIfNotVisible() const
+KeyframeEffect::CanIgnoreIfNotVisible() const
 {
   if (!AnimationUtils::IsOffscreenThrottlingEnabled()) {
     return false;
   }
 
   // FIXME: For further sophisticated optimization we need to check
   // change hint on the segment corresponding to computedTiming.progress.
   return NS_IsHintSubset(
     mCumulativeChangeHint, nsChangeHint_Hints_CanIgnoreIfNotVisible);
 }
 
 void
-KeyframeEffectReadOnly::MaybeUpdateFrameForCompositor()
+KeyframeEffect::MaybeUpdateFrameForCompositor()
 {
   nsIFrame* frame = GetStyleFrame();
   if (!frame) {
     return;
   }
 
   // FIXME: Bug 1272495: If this effect does not win in the cascade, the
   // NS_FRAME_MAY_BE_TRANSFORMED flag should be removed when the animation
@@ -1684,58 +1688,58 @@ KeyframeEffectReadOnly::MaybeUpdateFrame
     if (property.mProperty == eCSSProperty_transform) {
       frame->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
       return;
     }
   }
 }
 
 void
-KeyframeEffectReadOnly::MarkCascadeNeedsUpdate()
+KeyframeEffect::MarkCascadeNeedsUpdate()
 {
   if (!mTarget) {
     return;
   }
 
   EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement,
                                                  mTarget->mPseudoType);
   if (!effectSet) {
     return;
   }
   effectSet->MarkCascadeNeedsUpdate();
 }
 
 /* static */ bool
-KeyframeEffectReadOnly::HasComputedTimingChanged(
+KeyframeEffect::HasComputedTimingChanged(
   const ComputedTiming& aComputedTiming,
   IterationCompositeOperation aIterationComposite,
   const Nullable<double>& aProgressOnLastCompose,
   uint64_t aCurrentIterationOnLastCompose)
 {
   // Typically we don't need to request a restyle if the progress hasn't
   // changed since the last call to ComposeStyle. The one exception is if the
   // iteration composite mode is 'accumulate' and the current iteration has
   // changed, since that will often produce a different result.
   return aComputedTiming.mProgress != aProgressOnLastCompose ||
          (aIterationComposite == IterationCompositeOperation::Accumulate &&
           aComputedTiming.mCurrentIteration != aCurrentIterationOnLastCompose);
 }
 
 bool
-KeyframeEffectReadOnly::HasComputedTimingChanged() const
+KeyframeEffect::HasComputedTimingChanged() const
 {
   ComputedTiming computedTiming = GetComputedTiming();
   return HasComputedTimingChanged(computedTiming,
                                   mEffectOptions.mIterationComposite,
                                   mProgressOnLastCompose,
                                   mCurrentIterationOnLastCompose);
 }
 
 bool
-KeyframeEffectReadOnly::ContainsAnimatedScale(const nsIFrame* aFrame) const
+KeyframeEffect::ContainsAnimatedScale(const nsIFrame* aFrame) const
 {
   if (!IsCurrent()) {
     return false;
   }
 
   for (const AnimationProperty& prop : mProperties) {
     if (prop.mProperty != eCSSProperty_transform) {
       continue;
@@ -1770,17 +1774,17 @@ KeyframeEffectReadOnly::ContainsAnimated
       }
     }
   }
 
   return false;
 }
 
 void
-KeyframeEffectReadOnly::UpdateEffectSet(EffectSet* aEffectSet) const
+KeyframeEffect::UpdateEffectSet(EffectSet* aEffectSet) const
 {
   if (!mInEffectSet) {
     return;
   }
 
   EffectSet* effectSet =
     aEffectSet ? aEffectSet
                : EffectSet::GetEffectSet(mTarget->mElement,
@@ -1799,59 +1803,10 @@ KeyframeEffectReadOnly::UpdateEffectSet(
   if (HasAnimationOfProperty(eCSSProperty_transform)) {
     effectSet->SetMayHaveTransformAnimation();
     if (frame) {
       frame->SetMayHaveTransformAnimation();
     }
   }
 }
 
-KeyframeEffect::KeyframeEffect(nsIDocument* aDocument,
-                               const Maybe<OwningAnimationTarget>& aTarget,
-                               const TimingParams& aTiming,
-                               const KeyframeEffectParams& aOptions)
-  : KeyframeEffectReadOnly(aDocument, aTarget,
-                           new AnimationEffectTiming(aDocument, aTiming, this),
-                           aOptions)
-{
-}
-
-JSObject*
-KeyframeEffect::WrapObject(JSContext* aCx,
-                           JS::Handle<JSObject*> aGivenProto)
-{
-  return KeyframeEffectBinding::Wrap(aCx, this, aGivenProto);
-}
-
-/* static */ already_AddRefed<KeyframeEffect>
-KeyframeEffect::Constructor(
-    const GlobalObject& aGlobal,
-    const Nullable<ElementOrCSSPseudoElement>& aTarget,
-    JS::Handle<JSObject*> aKeyframes,
-    const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
-    ErrorResult& aRv)
-{
-  return ConstructKeyframeEffect<KeyframeEffect>(aGlobal, aTarget, aKeyframes,
-                                                 aOptions, aRv);
-}
-
-/* static */ already_AddRefed<KeyframeEffect>
-KeyframeEffect::Constructor(const GlobalObject& aGlobal,
-                            KeyframeEffectReadOnly& aSource,
-                            ErrorResult& aRv)
-{
-  return ConstructKeyframeEffect<KeyframeEffect>(aGlobal, aSource, aRv);
-}
-
-/* static */ already_AddRefed<KeyframeEffect>
-KeyframeEffect::Constructor(
-    const GlobalObject& aGlobal,
-    const Nullable<ElementOrCSSPseudoElement>& aTarget,
-    JS::Handle<JSObject*> aKeyframes,
-    const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
-    ErrorResult& aRv)
-{
-  return ConstructKeyframeEffect<KeyframeEffect>(aGlobal, aTarget, aKeyframes,
-                                                 aOptions, aRv);
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -107,44 +107,54 @@ struct AnimationProperty
 };
 
 struct ElementPropertyTransition;
 
 namespace dom {
 
 class Animation;
 
-class KeyframeEffectReadOnly : public AnimationEffectReadOnly
+class KeyframeEffect : public AnimationEffectReadOnly
 {
 public:
-  KeyframeEffectReadOnly(nsIDocument* aDocument,
-                         const Maybe<OwningAnimationTarget>& aTarget,
-                         const TimingParams& aTiming,
-                         const KeyframeEffectParams& aOptions);
+  KeyframeEffect(nsIDocument* aDocument,
+                 const Maybe<OwningAnimationTarget>& aTarget,
+                 const TimingParams& aTiming,
+                 const KeyframeEffectParams& aOptions);
 
   NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(KeyframeEffectReadOnly,
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(KeyframeEffect,
                                                         AnimationEffectReadOnly)
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
-  KeyframeEffectReadOnly* AsKeyframeEffect() override { return this; }
+  KeyframeEffect* AsKeyframeEffect() override { return this; }
 
-  // KeyframeEffectReadOnly interface
-  static already_AddRefed<KeyframeEffectReadOnly>
+  // KeyframeEffect interface
+  static already_AddRefed<KeyframeEffect>
   Constructor(const GlobalObject& aGlobal,
               const Nullable<ElementOrCSSPseudoElement>& aTarget,
               JS::Handle<JSObject*> aKeyframes,
               const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
               ErrorResult& aRv);
 
-  static already_AddRefed<KeyframeEffectReadOnly>
+  static already_AddRefed<KeyframeEffect>
   Constructor(const GlobalObject& aGlobal,
-              KeyframeEffectReadOnly& aSource,
+              KeyframeEffect& aSource,
+              ErrorResult& aRv);
+
+  // Variant of Constructor that accepts a KeyframeAnimationOptions object
+  // for use with for Animatable.animate.
+  // Not exposed to content.
+  static already_AddRefed<KeyframeEffect>
+  Constructor(const GlobalObject& aGlobal,
+              const Nullable<ElementOrCSSPseudoElement>& aTarget,
+              JS::Handle<JSObject*> aKeyframes,
+              const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
               ErrorResult& aRv);
 
   void GetTarget(Nullable<OwningElementOrCSSPseudoElement>& aRv) const;
   Maybe<NonOwningAnimationTarget> GetTarget() const
   {
     Maybe<NonOwningAnimationTarget> result;
     if (mTarget) {
       result.emplace(*mTarget);
@@ -285,38 +295,38 @@ public:
 
   static bool HasComputedTimingChanged(
     const ComputedTiming& aComputedTiming,
     IterationCompositeOperation aIterationComposite,
     const Nullable<double>& aProgressOnLastCompose,
     uint64_t aCurrentIterationOnLastCompose);
 
 protected:
-  KeyframeEffectReadOnly(nsIDocument* aDocument,
-                         const Maybe<OwningAnimationTarget>& aTarget,
-                         AnimationEffectTimingReadOnly* aTiming,
-                         const KeyframeEffectParams& aOptions);
+  KeyframeEffect(nsIDocument* aDocument,
+                 const Maybe<OwningAnimationTarget>& aTarget,
+                 AnimationEffectTimingReadOnly* aTiming,
+                 const KeyframeEffectParams& aOptions);
 
-  ~KeyframeEffectReadOnly() override = default;
+  ~KeyframeEffect() override = default;
 
   static Maybe<OwningAnimationTarget>
   ConvertTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget);
 
   template<class KeyframeEffectType, class OptionsType>
   static already_AddRefed<KeyframeEffectType>
   ConstructKeyframeEffect(const GlobalObject& aGlobal,
                           const Nullable<ElementOrCSSPseudoElement>& aTarget,
                           JS::Handle<JSObject*> aKeyframes,
                           const OptionsType& aOptions,
                           ErrorResult& aRv);
 
   template<class KeyframeEffectType>
   static already_AddRefed<KeyframeEffectType>
   ConstructKeyframeEffect(const GlobalObject& aGlobal,
-                          KeyframeEffectReadOnly& aSource,
+                          KeyframeEffect& aSource,
                           ErrorResult& aRv);
 
   // Build properties by recalculating from |mKeyframes| using |aComputedStyle|
   // to resolve specified values. This function also applies paced spacing if
   // needed.
   nsTArray<AnimationProperty> BuildProperties(const ComputedStyle* aStyle);
 
   // This effect is registered with its target element so long as:
@@ -449,46 +459,12 @@ private:
   // Returns true if this effect causes visibility change.
   // (i.e. 'visibility: hidden' -> 'visibility: visible' and vice versa.)
   bool HasVisibilityChange() const
   {
     return mCumulativeChangeHint & nsChangeHint_VisibilityChange;
   }
 };
 
-class KeyframeEffect : public KeyframeEffectReadOnly
-{
-public:
-  KeyframeEffect(nsIDocument* aDocument,
-                 const Maybe<OwningAnimationTarget>& aTarget,
-                 const TimingParams& aTiming,
-                 const KeyframeEffectParams& aOptions);
-
-  JSObject* WrapObject(JSContext* aCx,
-                       JS::Handle<JSObject*> aGivenProto) override;
-
-  static already_AddRefed<KeyframeEffect>
-  Constructor(const GlobalObject& aGlobal,
-              const Nullable<ElementOrCSSPseudoElement>& aTarget,
-              JS::Handle<JSObject*> aKeyframes,
-              const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
-              ErrorResult& aRv);
-
-  static already_AddRefed<KeyframeEffect>
-  Constructor(const GlobalObject& aGlobal,
-              KeyframeEffectReadOnly& aSource,
-              ErrorResult& aRv);
-
-  // Variant of Constructor that accepts a KeyframeAnimationOptions object
-  // for use with for Animatable.animate.
-  // Not exposed to content.
-  static already_AddRefed<KeyframeEffect>
-  Constructor(const GlobalObject& aGlobal,
-              const Nullable<ElementOrCSSPseudoElement>& aTarget,
-              JS::Handle<JSObject*> aKeyframes,
-              const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
-              ErrorResult& aRv);
-};
-
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_KeyframeEffect_h
--- a/dom/animation/test/chrome/file_animate_xrays.html
+++ b/dom/animation/test/chrome/file_animate_xrays.html
@@ -1,17 +1,17 @@
 <!doctype html>
 <html>
 <head>
 <meta charset=utf-8>
 <script>
 Element.prototype.animate = function() {
   throw 'Called animate() as defined in content document';
 }
-for (let name of ["KeyframeEffect", "KeyframeEffectReadOnly", "Animation"]) {
+for (let name of ["KeyframeEffect", "Animation"]) {
   this[name] = function() {
     throw `Called overridden ${name} constructor`;
   };
 }
 </script>
 <body>
 <div id="target"></div>
 </body>
--- a/dom/animation/test/chrome/test_animation_observers_sync.html
+++ b/dom/animation/test/chrome/test_animation_observers_sync.html
@@ -288,19 +288,19 @@ function runTest() {
 
     test(t => {
       var div = addDiv(t);
       var observer =
         setupSynchronousObserver(t,
                                  aOptions.subtree ? div.parentNode : div,
                                  aOptions.subtree);
 
-      var effect = new KeyframeEffectReadOnly(null,
-                                              { opacity: [ 0, 1 ] },
-                                              { duration: 100 * MS_PER_SEC });
+      var effect = new KeyframeEffect(null,
+                                      { opacity: [ 0, 1 ] },
+                                      { duration: 100 * MS_PER_SEC });
       var anim = new Animation(effect, document.timeline);
       anim.play();
       assert_equals_records(observer.takeRecords(),
         [], "no records after animation is added");
     }, "create_animation_without_target");
 
     test(t => {
       var div = addDiv(t);
--- a/dom/animation/test/chrome/test_animation_properties.html
+++ b/dom/animation/test/chrome/test_animation_properties.html
@@ -1,13 +1,13 @@
 <!doctype html>
 <head>
 <meta charset=utf-8>
 <title>Bug 1254419 - Test the values returned by
-       KeyframeEffectReadOnly.getProperties()</title>
+       KeyframeEffect.getProperties()</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>
 <div id="log"></div>
--- a/dom/animation/test/crashtests/1216842-1.html
+++ b/dom/animation/test/crashtests/1216842-1.html
@@ -10,17 +10,17 @@
     </style>
   </head>
   <body>
   <div id="target"></div>
   </body>
   <script>
     var target = document.getElementById("target");
     var effect =
-      new KeyframeEffectReadOnly(
+      new KeyframeEffect(
         target,
         { opacity: [0, 1] },
         {
           fill: "forwards",
           /* The function produces negative values in (0, 0.766312060) */
           easing: "cubic-bezier(0,-0.5,1,-0.5)",
           duration: 100,
           iterations: 0.75 /* To finish in the extraporation range */
--- a/dom/animation/test/crashtests/1216842-2.html
+++ b/dom/animation/test/crashtests/1216842-2.html
@@ -10,17 +10,17 @@
     </style>
   </head>
   <body>
   <div id="target"></div>
   </body>
   <script>
     var target = document.getElementById("target");
     var effect =
-      new KeyframeEffectReadOnly(
+      new KeyframeEffect(
         target,
         { opacity: [0, 1] },
         {
           fill: "forwards",
           /* The function produces values greater than 1 in (0.23368794, 1) */
           easing: "cubic-bezier(0,1.5,1,1.5)",
           duration: 100,
           iterations: 0.25 /* To finish in the extraporation range */
--- a/dom/animation/test/crashtests/1216842-3.html
+++ b/dom/animation/test/crashtests/1216842-3.html
@@ -4,17 +4,17 @@
     <title>Bug 1216842: effect-level easing function produces values greater than 1 (main-thread)</title>
   </head>
   <body>
   <div id="target"></div>
   </body>
   <script>
     var target = document.getElementById("target");
     var effect =
-      new KeyframeEffectReadOnly(
+      new KeyframeEffect(
         target,
         { color: ["red", "blue"] },
         {
           fill: "forwards",
           /* The function produces values greater than 1 in (0.23368794, 1) */
           easing: "cubic-bezier(0,1.5,1,1.5)",
           duration: 100
         }
--- a/dom/animation/test/crashtests/1216842-4.html
+++ b/dom/animation/test/crashtests/1216842-4.html
@@ -4,17 +4,17 @@
     <title>Bug 1216842: effect-level easing function produces negative values (main-thread)</title>
   </head>
   <body>
   <div id="target"></div>
   </body>
   <script>
     var target = document.getElementById("target");
     var effect =
-      new KeyframeEffectReadOnly(
+      new KeyframeEffect(
         target,
         { color: ["red", "blue"] },
         {
           fill: "forwards",
           /* The function produces negative values in (0, 0.766312060) */
           easing: "cubic-bezier(0,-0.5,1,-0.5)",
           duration: 100
         }
--- a/dom/animation/test/crashtests/1216842-5.html
+++ b/dom/animation/test/crashtests/1216842-5.html
@@ -13,17 +13,17 @@
     </style>
   </head>
   <body>
   <div id="target"></div>
   </body>
   <script>
     var target = document.getElementById("target");
     var effect =
-      new KeyframeEffectReadOnly(
+      new KeyframeEffect(
         target,
         { opacity: [0, 1], easing: "step-end" },
         {
           fill: "forwards",
           /* The function produces negative values in (0, 0.766312060) */
           easing: "cubic-bezier(0,-0.5,1,-0.5)",
           duration: 100,
           iterations: 0.75 /* To finish in the extraporation range */
--- a/dom/animation/test/crashtests/1216842-6.html
+++ b/dom/animation/test/crashtests/1216842-6.html
@@ -13,17 +13,17 @@
     </style>
   </head>
   <body>
   <div id="target"></div>
   </body>
   <script>
     var target = document.getElementById("target");
     var effect =
-      new KeyframeEffectReadOnly(
+      new KeyframeEffect(
         target,
         { opacity: [0, 1], easing: "step-end" },
         {
           fill: "forwards",
           /* The function produces values greater than 1 in (0.23368794, 1) */
           easing: "cubic-bezier(0,1.5,1,1.5)",
           duration: 100,
           iterations: 0.25 /* To finish in the extraporation range */
--- a/dom/animation/test/crashtests/1239889-1.html
+++ b/dom/animation/test/crashtests/1239889-1.html
@@ -2,15 +2,15 @@
 <html class="reftest-wait">
   <head>
     <title>Bug 1239889</title>
   </head>
   <body>
   </body>
   <script>
     var div = document.createElement('div');
-    var effect = new KeyframeEffectReadOnly(div, { opacity: [0, 1] });
+    var effect = new KeyframeEffect(div, { opacity: [0, 1] });
     requestAnimationFrame(() => {
       document.body.appendChild(div);
       document.documentElement.classList.remove("reftest-wait");
     });
   </script>
 </html>
--- a/dom/animation/test/css-animations/test_animation-computed-timing.html
+++ b/dom/animation/test/css-animations/test_animation-computed-timing.html
@@ -269,17 +269,17 @@ promise_test(function(t) {
   }).then(function() {
     assert_equals(anim.effect.getComputedTiming().localTime, anim.currentTime,
                   'localTime is equal to currentTime');
   });
 }, 'localTime reflects playbackRate immediately');
 
 test(function(t) {
   var div = addDiv(t);
-  var effect = new KeyframeEffectReadOnly(div, {left: ["0px", "100px"]});
+  var effect = new KeyframeEffect(div, {left: ["0px", "100px"]});
 
   assert_equals(effect.getComputedTiming().localTime, null,
                 'localTime for orphaned effect');
 }, 'localTime of an AnimationEffect without an Animation');
 
 
 // --------------------
 // progress
@@ -550,17 +550,17 @@ test(function(t) {
   // Finish
   anim.finish();
   assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
                 'Value of currentIteration in after phase');
 }, 'currentIteration of an animation with a default iteration count');
 
 test(function(t) {
   var div = addDiv(t);
-  var effect = new KeyframeEffectReadOnly(div, {left: ["0px", "100px"]});
+  var effect = new KeyframeEffect(div, {left: ["0px", "100px"]});
 
   assert_equals(effect.getComputedTiming().currentIteration, null,
                 'currentIteration for orphaned effect');
 }, 'currentIteration of an AnimationEffect without an Animation');
 
 // TODO: If iteration duration is Infinity, currentIteration is 0.
 // However, we cannot set iteration duration to Infinity in CSS Animation now.
 
--- a/dom/animation/test/css-animations/test_effect-target.html
+++ b/dom/animation/test/css-animations/test_effect-target.html
@@ -34,29 +34,29 @@ test(function(t) {
   assert_equals(anims[0].effect.target, anims[1].effect.target,
                 'Both animations return the same target object');
 }, 'effect.target should return the same CSSPseudoElement object each time');
 
 test(function(t) {
   addStyle(t, { '.after::after': 'animation: anim 10s;' });
   var div = addDiv(t, { class: 'after' });
   var pseudoTarget = document.getAnimations()[0].effect.target;
-  var effect = new KeyframeEffectReadOnly(pseudoTarget,
-                                          { background: ["blue", "red"] },
-                                          3 * MS_PER_SEC);
+  var effect = new KeyframeEffect(pseudoTarget,
+                                  { background: ["blue", "red"] },
+                                  3 * MS_PER_SEC);
   var newAnim = new Animation(effect, document.timeline);
   newAnim.play();
   var anims = document.getAnimations();
   assert_equals(anims.length, 2,
                 'Got animations running on ::after pseudo element');
   assert_not_equals(anims[0], newAnim,
                     'The scriped-generated animation appears last');
   assert_equals(newAnim.effect.target, pseudoTarget,
                 'The effect.target of the scripted-generated animation is ' +
                 'the same as the one from the argument of ' +
-                'KeyframeEffectReadOnly constructor');
+                'KeyframeEffect constructor');
   assert_equals(anims[0].effect.target, newAnim.effect.target,
                 'Both animations return the same target object');
 }, 'effect.target from the script-generated animation should return the same ' +
    'CSSPseudoElement object as that from the CSS generated animation');
 
 </script>
 </body>
--- a/dom/animation/test/css-animations/test_keyframeeffect-getkeyframes.html
+++ b/dom/animation/test/css-animations/test_keyframeeffect-getkeyframes.html
@@ -210,17 +210,17 @@ test(function(t) {
   assert_equals(getKeyframes(div).length, 0,
                 "number of frames when @keyframes only has keyframes with " +
                 "animation-timing-function");
 
   div.style.animation = 'anim-only-non-animatable 100s';
   assert_equals(getKeyframes(div).length, 0,
                 "number of frames when @keyframes only has frames with " +
                 "non-animatable properties");
-}, 'KeyframeEffectReadOnly.getKeyframes() returns no frames for various kinds'
+}, 'KeyframeEffect.getKeyframes() returns no frames for various kinds'
    + ' of empty enimations');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-simple 100s';
   var frames = getKeyframes(div);
 
@@ -231,67 +231,67 @@ test(function(t) {
       color: "rgb(0, 0, 0)", composite: null },
     { offset: 1, computedOffset: 1, easing: "ease",
       color: "rgb(255, 255, 255)", composite: null },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for a simple'
+}, 'KeyframeEffect.getKeyframes() returns expected frames for a simple'
    + ' animation');
 
 test(function(t) {
   kTimingFunctionValues.forEach(function(easing) {
     var div = addDiv(t);
 
     div.style.animation = 'anim-simple-three 100s ' + easing;
     var frames = getKeyframes(div);
 
     assert_equals(frames.length, 3, "number of frames");
 
     for (var i = 0; i < frames.length; i++) {
       assert_equals(frames[i].easing, easing,
                     "value for 'easing' on ComputedKeyframe #" + i);
     }
   });
-}, 'KeyframeEffectReadOnly.getKeyframes() returns frames with expected easing'
+}, 'KeyframeEffect.getKeyframes() returns frames with expected easing'
    + ' values, when the easing comes from animation-timing-function on the'
    + ' element');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-simple-timing 100s';
   var frames = getKeyframes(div);
 
   assert_equals(frames.length, 3, "number of frames");
   assert_equals(frames[0].easing, "linear",
                 "value of 'easing' on ComputedKeyframe #0");
   assert_equals(frames[1].easing, "ease-in-out",
                 "value of 'easing' on ComputedKeyframe #1");
   assert_equals(frames[2].easing, "steps(1)",
                 "value of 'easing' on ComputedKeyframe #2");
-}, 'KeyframeEffectReadOnly.getKeyframes() returns frames with expected easing'
+}, 'KeyframeEffect.getKeyframes() returns frames with expected easing'
    + ' values, when the easing is specified on each keyframe');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-simple-timing-some 100s step-start';
   var frames = getKeyframes(div);
 
   assert_equals(frames.length, 3, "number of frames");
   assert_equals(frames[0].easing, "linear",
                 "value of 'easing' on ComputedKeyframe #0");
   assert_equals(frames[1].easing, "steps(1, start)",
                 "value of 'easing' on ComputedKeyframe #1");
   assert_equals(frames[2].easing, "steps(1, start)",
                 "value of 'easing' on ComputedKeyframe #2");
-}, 'KeyframeEffectReadOnly.getKeyframes() returns frames with expected easing'
+}, 'KeyframeEffect.getKeyframes() returns frames with expected easing'
    + ' values, when the easing is specified on some keyframes');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-simple-shorthand 100s';
   var frames = getKeyframes(div);
 
@@ -304,17 +304,17 @@ test(function(t) {
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       marginBottom: "16px", marginLeft: "16px",
       marginRight: "16px", marginTop: "16px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for a simple'
+}, 'KeyframeEffect.getKeyframes() returns expected frames for a simple'
    + ' animation that specifies a single shorthand property');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-omit-to 100s';
   div.style.color = 'rgb(255, 255, 255)';
   var frames = getKeyframes(div);
@@ -326,17 +326,17 @@ test(function(t) {
       color: "rgb(0, 0, 255)" },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       color: "rgb(255, 255, 255)" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' +
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
    'animation with a 0% keyframe and no 100% keyframe');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-omit-from 100s';
   div.style.color = 'rgb(255, 255, 255)';
   var frames = getKeyframes(div);
@@ -348,17 +348,17 @@ test(function(t) {
       color: "rgb(255, 255, 255)" },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       color: "rgb(0, 0, 255)" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' +
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
    'animation with a 100% keyframe and no 0% keyframe');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-omit-from-to 100s';
   div.style.color = 'rgb(255, 255, 255)';
   var frames = getKeyframes(div);
@@ -372,17 +372,17 @@ test(function(t) {
       color: "rgb(0, 0, 255)" },
     { offset: 1,   computedOffset: 1,   easing: "ease", composite: null,
       color: "rgb(255, 255, 255)" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' +
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
    'animation with no 0% or 100% keyframe but with a 50% keyframe');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-partially-omit-to 100s';
   div.style.marginTop = '250px';
   var frames = getKeyframes(div);
@@ -394,17 +394,17 @@ test(function(t) {
       marginTop: '50px', marginBottom: '100px' },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       marginTop: '250px', marginBottom: '200px' },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' +
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
    'animation with a partially complete 100% keyframe (because the ' +
    '!important rule is ignored)');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-different-props 100s';
   var frames = getKeyframes(div);
@@ -420,17 +420,17 @@ test(function(t) {
       marginTop: "12px" },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       color: "rgb(255, 255, 255)", marginTop: "16px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' +
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
    'animation with different properties on different keyframes, all ' +
    'with the same easing function');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-different-props-and-easing 100s';
   var frames = getKeyframes(div);
@@ -446,17 +446,17 @@ test(function(t) {
       marginTop: "12px" },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       color: "rgb(255, 255, 255)", marginTop: "16px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' +
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
    'animation with different properties on different keyframes, with ' +
    'a different easing function on each');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-merge-offset 100s';
   var frames = getKeyframes(div);
@@ -468,17 +468,17 @@ test(function(t) {
       color: "rgb(0, 0, 0)", marginTop: "8px" },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       color: "rgb(255, 255, 255)", marginTop: "16px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' +
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
    'animation with multiple keyframes for the same time, and all with ' +
    'the same easing function');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-merge-offset-and-easing 100s';
   var frames = getKeyframes(div);
@@ -493,17 +493,17 @@ test(function(t) {
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       color: "rgb(255, 255, 255)", fontSize: "32px", marginTop: "16px",
       paddingLeft: "4px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' +
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
    'animation with multiple keyframes for the same time and with ' +
    'different easing functions');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-no-merge-equiv-easing 100s';
   var frames = getKeyframes(div);
@@ -517,17 +517,17 @@ test(function(t) {
       marginTop: "10px", marginRight: "10px", marginBottom: "10px" },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       marginTop: "20px", marginRight: "20px", marginBottom: "20px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' +
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
    'animation with multiple keyframes for the same time and with ' +
    'different but equivalent easing functions');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-overriding 100s';
   var frames = getKeyframes(div);
@@ -547,17 +547,17 @@ test(function(t) {
       paddingTop: "60px" },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       paddingTop: "70px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for ' +
+}, 'KeyframeEffect.getKeyframes() returns expected frames for ' +
    'overlapping keyframes');
 
 // Gecko-specific test case: We are specifically concerned here that the
 // computed value for filter, "none", is correctly represented.
 
 test(function(t) {
   var div = addDiv(t);
 
@@ -571,17 +571,17 @@ test(function(t) {
       filter: "none" },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       filter: "blur(5px) sepia(60%) saturate(30%)" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' +
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
    'animations with filter properties and missing keyframes');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-filter-drop-shadow 100s';
   var frames = getKeyframes(div);
 
@@ -592,17 +592,17 @@ test(function(t) {
       filter: "drop-shadow(rgb(0, 255, 0) 10px 10px 10px)" },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       filter: "drop-shadow(rgb(255, 0, 0) 50px 30px 10px)" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' +
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
    'animation with drop-shadow of filter property');
 
 // Gecko-specific test case: We are specifically concerned here that the
 // computed value for text-shadow and a "none" specified on a keyframe
 // are correctly represented.
 
 test(function(t) {
   var div = addDiv(t);
@@ -622,17 +622,17 @@ test(function(t) {
                   + " rgb(0, 0, 255) 0px 0px 3.2px" },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       textShadow: "none" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' +
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
    'animations with text-shadow properties and missing keyframes');
 
 // Gecko-specific test case: We are specifically concerned here that the
 // initial value for background-size and the specified list are correctly
 // represented.
 
 test(function(t) {
   var div = addDiv(t);
@@ -658,17 +658,17 @@ test(function(t) {
   expected[0].backgroundSize = div.style.backgroundSize =
     "30px auto, 40% auto, auto auto";
   frames = getKeyframes(div);
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i
                         + " after updating current style");
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' +
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
    'animations with background-size properties and missing keyframes');
 
 test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'anim-variables 100s';
 
   var frames = getKeyframes(div);
 
@@ -678,17 +678,17 @@ test(function(t) {
     { offset: 0, computedOffset: 0, easing: "ease", composite: null,
       transform: "none" },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       transform: "translate(100px, 0px)" },
   ];
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' +
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
    'animations with CSS variables as keyframe values');
 
 test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'anim-variables-shorthand 100s';
 
   var frames = getKeyframes(div);
 
@@ -704,17 +704,17 @@ test(function(t) {
       marginBottom: "100px",
       marginLeft: "100px",
       marginRight: "100px",
       marginTop: "100px" },
   ];
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' +
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
    'animations with CSS variables as keyframe values in a shorthand property');
 
 test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'anim-custom-property-in-keyframe 100s';
 
   var frames = getKeyframes(div);
 
@@ -724,17 +724,17 @@ test(function(t) {
     { offset: 0, computedOffset: 0, easing: "ease", composite: null,
       color: "rgb(0, 0, 0)" },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       color: "rgb(0, 255, 0)" },
   ];
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' +
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
    'animations with a CSS variable which is overriden by the value in keyframe');
 
 test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'anim-only-custom-property-in-keyframe 100s';
 
   var frames = getKeyframes(div);
 
@@ -744,13 +744,13 @@ test(function(t) {
     { offset: 0, computedOffset: 0, easing: "ease", composite: null,
       transform: "translate(100px, 0px)" },
     { offset: 1, computedOffset: 1, easing: "ease", composite: null,
       transform: "none" },
   ];
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' +
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
    'animations with only custom property in a keyframe');
 
 </script>
 </body>
--- a/dom/animation/test/css-animations/test_pseudoElement-get-animations.html
+++ b/dom/animation/test/css-animations/test_pseudoElement-get-animations.html
@@ -40,19 +40,19 @@ test(function(t) {
 test(function(t) {
   var div = addDiv(t, { class: 'after-with-mix-anims-trans' });
   // Trigger transitions
   flushComputedStyle(div);
   div.classList.add('after-change');
 
   // Create additional animation on the pseudo-element from script
   var pseudoTarget = document.getAnimations()[0].effect.target;
-  var effect = new KeyframeEffectReadOnly(pseudoTarget,
-                                          { background: ["blue", "red"] },
-                                          3 * MS_PER_SEC);
+  var effect = new KeyframeEffect(pseudoTarget,
+                                  { background: ["blue", "red"] },
+                                  3 * MS_PER_SEC);
   var newAnimation = new Animation(effect, document.timeline);
   newAnimation.id = 'scripted-anim';
   newAnimation.play();
 
   // Check order - the script-generated animation should appear later
   var anims = pseudoTarget.getAnimations();
   assert_equals(anims.length, 5,
                 'Got expected number of animations/trnasitions running on ' +
--- a/dom/animation/test/css-transitions/test_effect-target.html
+++ b/dom/animation/test/css-transitions/test_effect-target.html
@@ -38,31 +38,31 @@ test(function(t) {
 
 test(function(t) {
   addStyle(t, { '.init::after': 'content: ""; width: 0px; transition: all 10s;',
                 '.change::after': 'width: 100px;' });
   var div = addDiv(t, { class: 'init' });
   flushComputedStyle(div);
   div.classList.add('change');
   var pseudoTarget = document.getAnimations()[0].effect.target;
-  var effect = new KeyframeEffectReadOnly(pseudoTarget,
-                                          { background: ["blue", "red"] },
-                                          3000);
+  var effect = new KeyframeEffect(pseudoTarget,
+                                  { background: ["blue", "red"] },
+                                  3000);
   var newAnim = new Animation(effect, document.timeline);
   newAnim.play();
 
   var anims = document.getAnimations();
   assert_equals(anims.length, 2,
                 'Got animations running on ::after pseudo element');
   assert_not_equals(anims[0], newAnim,
                     'The scriped-generated animation appears last');
   assert_equals(newAnim.effect.target, pseudoTarget,
                 'The effect.target of the scripted-generated animation is ' +
                 'the same as the one from the argument of ' +
-                'KeyframeEffectReadOnly constructor');
+                'KeyframeEffect constructor');
   assert_equals(anims[0].effect.target, newAnim.effect.target,
                 'Both the transition and the scripted-generated animation ' +
                 'return the same target object');
 }, 'effect.target from the script-generated animation should return the same ' +
    'CSSPseudoElement object as that from the CSS generated transition');
 
 </script>
 </body>
--- a/dom/animation/test/css-transitions/test_keyframeeffect-getkeyframes.html
+++ b/dom/animation/test/css-transitions/test_keyframeeffect-getkeyframes.html
@@ -43,17 +43,17 @@ test(function(t) {
       left: "0px" },
     { offset: 1, computedOffset: 1, easing: "linear", composite: null,
       left: "100px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for a simple'
+}, 'KeyframeEffect.getKeyframes() returns expected frames for a simple'
    + ' transition');
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.left = '0px';
   getComputedStyle(div).transitionProperty;
   div.style.transition = 'left 100s steps(2,end)';
@@ -68,17 +68,17 @@ test(function(t) {
       left: "0px" },
     { offset: 1, computedOffset: 1, easing: "linear", composite: null,
       left: "100px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for a simple'
+}, 'KeyframeEffect.getKeyframes() returns expected frames for a simple'
    + ' transition with a non-default easing function');
 
 test(function(t) {
   var div = addDiv(t);
   div.style.left = '0px';
   getComputedStyle(div).transitionProperty;
   div.style.transition = 'left 100s';
   div.style.left = 'var(--var-100px)';
@@ -91,13 +91,13 @@ test(function(t) {
     { offset: 0, computedOffset: 0, easing: 'ease', composite: null,
       left: '0px' },
     { offset: 1, computedOffset: 1, easing: 'linear', composite: null,
       left: '100px' },
   ];
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
-}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for a'
+}, 'KeyframeEffect.getKeyframes() returns expected frames for a'
    + ' transition with a CSS variable endpoint');
 
 </script>
 </body>
--- a/dom/animation/test/style/test_animation-setting-effect.html
+++ b/dom/animation/test/style/test_animation-setting-effect.html
@@ -11,19 +11,19 @@
     <div id="log"></div>
     <script type='text/javascript'>
 
 'use strict';
 
 test(function(t) {
   var target = addDiv(t);
   var anim = new Animation();
-  anim.effect = new KeyframeEffectReadOnly(target,
-                                           { marginLeft: [ '0px', '100px' ] },
-                                           100 * MS_PER_SEC);
+  anim.effect = new KeyframeEffect(target,
+                                   { marginLeft: [ '0px', '100px' ] },
+                                   100 * MS_PER_SEC);
   anim.currentTime = 50 * MS_PER_SEC;
   assert_equals(getComputedStyle(target).marginLeft, '50px');
 }, 'After setting target effect on an animation with null effect, the ' +
    'animation still works');
 
 test(function(t) {
   var target = addDiv(t);
   target.style.marginLeft = '10px';
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3771,17 +3771,17 @@ Element::GetAnimationsUnsorted(Element* 
              "Unsupported pseudo type");
   MOZ_ASSERT(aElement, "Null element");
 
   EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType);
   if (!effects) {
     return;
   }
 
-  for (KeyframeEffectReadOnly* effect : *effects) {
+  for (KeyframeEffect* effect : *effects) {
     MOZ_ASSERT(effect && effect->GetAnimation(),
                "Only effects associated with an animation should be "
                "added to an element's effect set");
     Animation* animation = effect->GetAnimation();
 
     MOZ_ASSERT(animation->IsRelevant(),
                "Only relevant animations should be added to an element's "
                "effect set");
--- a/dom/base/nsDOMMutationObserver.cpp
+++ b/dom/base/nsDOMMutationObserver.cpp
@@ -380,18 +380,17 @@ void
 nsAnimationReceiver::RecordAnimationMutation(Animation* aAnimation,
                                              AnimationMutation aMutationType)
 {
   mozilla::dom::AnimationEffectReadOnly* effect = aAnimation->GetEffect();
   if (!effect) {
     return;
   }
 
-  mozilla::dom::KeyframeEffectReadOnly* keyframeEffect =
-    effect->AsKeyframeEffect();
+  mozilla::dom::KeyframeEffect* keyframeEffect = effect->AsKeyframeEffect();
   if (!keyframeEffect) {
     return;
   }
 
   Maybe<NonOwningAnimationTarget> animationTarget = keyframeEffect->GetTarget();
   if (!animationTarget) {
     return;
   }
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -610,18 +610,16 @@ var interfaceNamesInGlobalScope =
     {name: "IntersectionObserver", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "IntersectionObserverEntry", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "KeyEvent", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "KeyboardEvent", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "KeyframeEffectReadOnly", insecureContext: true, release: false},
-// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "KeyframeEffect", insecureContext: true, release: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "LocalMediaStream", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Location", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MediaDeviceInfo", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/webidl/BaseKeyframeTypes.webidl
+++ b/dom/webidl/BaseKeyframeTypes.webidl
@@ -11,18 +11,18 @@
  *
  * Copyright © 2016 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 enum CompositeOperation { "replace", "add", "accumulate" };
 
 // The following dictionary types are not referred to by other .webidl files,
-// but we use it for manual JS->IDL and IDL->JS conversions in
-// KeyframeEffectReadOnly's implementation.
+// but we use it for manual JS->IDL and IDL->JS conversions in KeyframeEffect's
+// implementation.
 
 dictionary BasePropertyIndexedKeyframe {
   (double? or sequence<double?>) offset = [];
   (DOMString or sequence<DOMString>) easing = [];
   (CompositeOperation? or sequence<CompositeOperation?>) composite = [];
 };
 
 dictionary BaseKeyframe {
--- a/dom/webidl/KeyframeEffect.webidl
+++ b/dom/webidl/KeyframeEffect.webidl
@@ -15,34 +15,31 @@ enum IterationCompositeOperation {
   "accumulate"
 };
 
 dictionary KeyframeEffectOptions : AnimationEffectTimingProperties {
   IterationCompositeOperation iterationComposite = "replace";
   CompositeOperation          composite = "replace";
 };
 
-// KeyframeEffectReadOnly should run in the caller's compartment to do custom
+// KeyframeEffect should run in the caller's compartment to do custom
 // processing on the `keyframes` object.
 [Func="nsDocument::IsWebAnimationsEnabled",
  RunConstructorInCallerCompartment,
- HeaderFile="mozilla/dom/KeyframeEffect.h",
  Constructor ((Element or CSSPseudoElement)? target,
               object? keyframes,
               optional (unrestricted double or KeyframeEffectOptions) options),
- Constructor (KeyframeEffectReadOnly source)]
-interface KeyframeEffectReadOnly : AnimationEffectReadOnly {
+ Constructor (KeyframeEffect source)]
+interface KeyframeEffect : AnimationEffectReadOnly {
   attribute (Element or CSSPseudoElement)?  target;
   [NeedsCallerType]
-  attribute IterationCompositeOperation iterationComposite;
-  attribute CompositeOperation          composite;
-
-  // We use object instead of ComputedKeyframe so that we can put the
-  // property-value pairs on the object.
-  [Throws] sequence<object> getKeyframes();
+  attribute IterationCompositeOperation     iterationComposite;
+  attribute CompositeOperation              composite;
+  [Throws] sequence<object> getKeyframes ();
+  [Throws] void             setKeyframes (object? keyframes);
 };
 
 // Non-standard extensions
 dictionary AnimationPropertyValueDetails {
   required double             offset;
            DOMString          value;
            DOMString          easing;
   required CompositeOperation composite;
@@ -50,24 +47,11 @@ dictionary AnimationPropertyValueDetails
 
 dictionary AnimationPropertyDetails {
   required DOMString                               property;
   required boolean                                 runningOnCompositor;
            DOMString                               warning;
   required sequence<AnimationPropertyValueDetails> values;
 };
 
-partial interface KeyframeEffectReadOnly {
+partial interface KeyframeEffect {
   [ChromeOnly, Throws] sequence<AnimationPropertyDetails> getProperties();
 };
-
-// KeyframeEffect should run in the caller's compartment to do custom
-// processing on the `keyframes` object.
-[Func="nsDocument::IsWebAnimationsEnabled",
- RunConstructorInCallerCompartment,
- Constructor ((Element or CSSPseudoElement)? target,
-              object? keyframes,
-              optional (unrestricted double or KeyframeEffectOptions) options),
- Constructor (KeyframeEffectReadOnly source)]
-interface KeyframeEffect : KeyframeEffectReadOnly {
-  [Throws]
-  void setKeyframes (object? keyframes);
-};
--- a/gfx/layers/AnimationHelper.cpp
+++ b/gfx/layers/AnimationHelper.cpp
@@ -249,17 +249,17 @@ AnimationHelper::SampleAnimationForEachN
     // calculation.
     // Note that we don't skip calculate this animation if there is another
     // animation since the other animation might be 'accumulate' or 'add', or
     // might have a missing keyframe (i.e. this animation value will be used in
     // the missing keyframe).
     // FIXME Bug 1455476: We should do this optimizations for the case where
     // the layer has multiple animations.
     if (iEnd == 1 &&
-        !dom::KeyframeEffectReadOnly::HasComputedTimingChanged(
+        !dom::KeyframeEffect::HasComputedTimingChanged(
           computedTiming,
           iterCompositeOperation,
           animData.mProgressOnLastCompose,
           animData.mCurrentIterationOnLastCompose)) {
 #ifdef DEBUG
       shouldBeSkipped = true;
 #else
       return SampleResult::Skipped;
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -1845,17 +1845,17 @@ RestyleManager::AnimationsWithDestroyedF
 
     animationManager->StopAnimationsForElement(element, aPseudoType);
     transitionManager->StopAnimationsForElement(element, aPseudoType);
 
     // All other animations should keep running but not running on the
     // *compositor* at this point.
     EffectSet* effectSet = EffectSet::GetEffectSet(element, aPseudoType);
     if (effectSet) {
-      for (KeyframeEffectReadOnly* effect : *effectSet) {
+      for (KeyframeEffect* effect : *effectSet) {
         effect->ResetIsRunningOnCompositor();
       }
     }
   }
 }
 
 #ifdef DEBUG
 static bool
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -202,17 +202,17 @@ static ContentMap& GetContentMap() {
   }
   return *sContentMap;
 }
 
 template<typename TestType>
 static bool
 HasMatchingAnimations(EffectSet* aEffects, TestType&& aTest)
 {
-  for (KeyframeEffectReadOnly* effect : *aEffects) {
+  for (KeyframeEffect* effect : *aEffects) {
     if (aTest(*effect)) {
       return true;
     }
   }
 
   return false;
 }
 
@@ -227,17 +227,17 @@ HasMatchingAnimations(const nsIFrame* aF
 
   return HasMatchingAnimations(effects, aTest);
 }
 
 bool
 nsLayoutUtils::HasCurrentTransitions(const nsIFrame* aFrame)
 {
   return HasMatchingAnimations(aFrame,
-    [](KeyframeEffectReadOnly& aEffect)
+    [](KeyframeEffect& aEffect)
     {
       // Since |aEffect| is current, it must have an associated Animation
       // so we don't need to null-check the result of GetAnimation().
       return aEffect.IsCurrent() && aEffect.GetAnimation()->AsCSSTransition();
     }
   );
 }
 
@@ -276,34 +276,34 @@ bool
 nsLayoutUtils::HasAnimationOfProperty(EffectSet* aEffectSet,
                                       nsCSSPropertyID aProperty)
 {
   if (!aEffectSet || !MayHaveAnimationOfProperty(aEffectSet, aProperty)) {
     return false;
   }
 
   return HasMatchingAnimations(aEffectSet,
-    [&aProperty](KeyframeEffectReadOnly& aEffect)
+    [&aProperty](KeyframeEffect& aEffect)
     {
       return (aEffect.IsInEffect() || aEffect.IsCurrent()) &&
              aEffect.HasAnimationOfProperty(aProperty);
     }
   );
 }
 
 bool
 nsLayoutUtils::HasAnimationOfProperty(const nsIFrame* aFrame,
                                       nsCSSPropertyID aProperty)
 {
   if (!MayHaveAnimationOfProperty(aFrame, aProperty)) {
     return false;
   }
 
   return HasMatchingAnimations(aFrame,
-    [&aProperty](KeyframeEffectReadOnly& aEffect)
+    [&aProperty](KeyframeEffect& aEffect)
     {
       return (aEffect.IsInEffect() || aEffect.IsCurrent()) &&
              aEffect.HasAnimationOfProperty(aProperty);
     }
   );
 
 }
 
@@ -313,17 +313,17 @@ nsLayoutUtils::HasEffectiveAnimation(con
 {
   EffectSet* effects = EffectSet::GetEffectSet(aFrame);
   if (!effects || !MayHaveAnimationOfProperty(effects, aProperty)) {
     return false;
   }
 
 
   return HasMatchingAnimations(effects,
-    [&aProperty](KeyframeEffectReadOnly& aEffect)
+    [&aProperty](KeyframeEffect& aEffect)
     {
       return (aEffect.IsInEffect() || aEffect.IsCurrent()) &&
              aEffect.HasEffectiveAnimationOfProperty(aProperty);
     }
   );
 }
 
 static float
@@ -369,17 +369,17 @@ GetMinAndMaxScaleForAnimationProperty(co
 {
   for (dom::Animation* anim : aAnimations) {
     // This method is only expected to be passed animations that are running on
     // the compositor and we only pass playing animations to the compositor,
     // which are, by definition, "relevant" animations (animations that are
     // not yet finished or which are filling forwards).
     MOZ_ASSERT(anim->IsRelevant());
 
-    dom::KeyframeEffectReadOnly* effect =
+    dom::KeyframeEffect* 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) {
         continue;
       }
 
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -693,17 +693,17 @@ AddAnimationsForProperty(nsIFrame* aFram
 
   // Add from first to last (since last overrides)
   for (size_t animIdx = 0; animIdx < compositorAnimations.Length(); animIdx++) {
     dom::Animation* anim = compositorAnimations[animIdx];
     if (!anim->IsRelevant()) {
       continue;
     }
 
-    dom::KeyframeEffectReadOnly* keyframeEffect =
+    dom::KeyframeEffect* keyframeEffect =
       anim->GetEffect() ? anim->GetEffect()->AsKeyframeEffect() : nullptr;
     MOZ_ASSERT(keyframeEffect,
                "A playing animation should have a keyframe effect");
     const AnimationProperty* property =
       keyframeEffect->GetEffectiveAnimationOfProperty(aProperty);
     if (!property) {
       continue;
     }
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -32,17 +32,17 @@
 #include <algorithm> // std::stable_sort
 #include <math.h>
 
 using namespace mozilla;
 using namespace mozilla::css;
 using mozilla::dom::Animation;
 using mozilla::dom::AnimationEffectReadOnly;
 using mozilla::dom::AnimationPlayState;
-using mozilla::dom::KeyframeEffectReadOnly;
+using mozilla::dom::KeyframeEffect;
 using mozilla::dom::CSSAnimation;
 
 typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
 
 ////////////////////////// CSSAnimation ////////////////////////////
 
 JSObject*
 CSSAnimation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
@@ -365,18 +365,17 @@ public:
   {
     ServoStyleSet* styleSet = aPresContext->StyleSet();
     MOZ_ASSERT(styleSet);
     return styleSet->GetKeyframesForName(aElement,
                                          aName,
                                          aTimingFunction,
                                          aKeyframes);
   }
-  void SetKeyframes(KeyframeEffectReadOnly& aEffect,
-                    nsTArray<Keyframe>&& aKeyframes)
+  void SetKeyframes(KeyframeEffect& aEffect, nsTArray<Keyframe>&& aKeyframes)
   {
     aEffect.SetKeyframes(Move(aKeyframes), mComputedStyle);
   }
 
   // Currently all the animation building code in this file is based on
   // assumption that creating and removing animations should *not* trigger
   // additional restyles since those changes will be handled within the same
   // restyle.
@@ -402,17 +401,17 @@ public:
   // post the required restyles.
   void NotifyNewOrRemovedAnimation(const Animation& aAnimation)
   {
     dom::AnimationEffectReadOnly* effect = aAnimation.GetEffect();
     if (!effect) {
       return;
     }
 
-    KeyframeEffectReadOnly* keyframeEffect = effect->AsKeyframeEffect();
+    KeyframeEffect* keyframeEffect = effect->AsKeyframeEffect();
     if (!keyframeEffect) {
       return;
     }
 
     keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Standard);
   }
 
 private:
@@ -432,17 +431,17 @@ UpdateOldAnimationPropertiesWithNew(
 
   // Update the old from the new so we can keep the original object
   // identity (and any expando properties attached to it).
   if (aOld.GetEffect()) {
     dom::AnimationEffectReadOnly* oldEffect = aOld.GetEffect();
     animationChanged = oldEffect->SpecifiedTiming() != aNewTiming;
     oldEffect->SetSpecifiedTiming(aNewTiming);
 
-    KeyframeEffectReadOnly* oldKeyframeEffect = oldEffect->AsKeyframeEffect();
+    KeyframeEffect* oldKeyframeEffect = oldEffect->AsKeyframeEffect();
     if (oldKeyframeEffect) {
       aBuilder.SetKeyframes(*oldKeyframeEffect, Move(aNewKeyframes));
     }
   }
 
   // Handle changes in play state. If the animation is idle, however,
   // changes to animation-play-state should *not* restart it.
   if (aOld.PlayState() != AnimationPlayState::Idle) {
@@ -525,19 +524,18 @@ BuildAnimation(nsPresContext* aPresConte
                                         aBuilder);
     return oldAnim.forget();
   }
 
   // mTarget is non-null here, so we emplace it directly.
   Maybe<OwningAnimationTarget> target;
   target.emplace(aTarget.mElement, aTarget.mPseudoType);
   KeyframeEffectParams effectOptions;
-  RefPtr<KeyframeEffectReadOnly> effect =
-    new KeyframeEffectReadOnly(aPresContext->Document(), target, timing,
-                               effectOptions);
+  RefPtr<KeyframeEffect> effect =
+    new KeyframeEffect(aPresContext->Document(), target, timing, effectOptions);
 
   aBuilder.SetKeyframes(*effect, Move(keyframes));
 
   RefPtr<CSSAnimation> animation =
     new CSSAnimation(aPresContext->Document()->GetScopeObject(), animationName);
   animation->SetOwningElement(
     OwningElementRef(*aTarget.mElement, aTarget.mPseudoType));
 
--- a/layout/style/nsAnimationManager.h
+++ b/layout/style/nsAnimationManager.h
@@ -22,17 +22,16 @@ struct nsStyleDisplay;
 class ServoCSSAnimationBuilder;
 
 namespace mozilla {
 class ComputedStyle;
 namespace css {
 class Declaration;
 } /* namespace css */
 namespace dom {
-class KeyframeEffectReadOnly;
 class Promise;
 } /* namespace dom */
 
 enum class CSSPseudoElementType : uint8_t;
 struct NonOwningAnimationTarget;
 
 namespace dom {
 
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -36,17 +36,16 @@
 #include "mozilla/RestyleManager.h"
 #include "nsDOMMutationObserver.h"
 
 using mozilla::TimeStamp;
 using mozilla::TimeDuration;
 using mozilla::dom::Animation;
 using mozilla::dom::AnimationPlayState;
 using mozilla::dom::CSSTransition;
-using mozilla::dom::KeyframeEffectReadOnly;
 
 using namespace mozilla;
 using namespace mozilla::css;
 
 double
 ElementPropertyTransition::CurrentValuePortion() const
 {
   MOZ_ASSERT(!GetLocalTime().IsNull(),
--- a/layout/style/nsTransitionManager.h
+++ b/layout/style/nsTransitionManager.h
@@ -28,25 +28,25 @@ struct StyleTransition;
 } // namespace mozilla
 
 /*****************************************************************************
  * Per-Element data                                                          *
  *****************************************************************************/
 
 namespace mozilla {
 
-struct ElementPropertyTransition : public dom::KeyframeEffectReadOnly
+struct ElementPropertyTransition : public dom::KeyframeEffect
 {
   ElementPropertyTransition(nsIDocument* aDocument,
                             Maybe<OwningAnimationTarget>& aTarget,
                             const TimingParams &aTiming,
                             AnimationValue aStartForReversingTest,
                             double aReversePortion,
                             const KeyframeEffectParams& aEffectOptions)
-    : dom::KeyframeEffectReadOnly(aDocument, aTarget, aTiming, aEffectOptions)
+    : dom::KeyframeEffect(aDocument, aTarget, aTiming, aEffectOptions)
     , mStartForReversingTest(aStartForReversingTest)
     , mReversePortion(aReversePortion)
   { }
 
   ElementPropertyTransition* AsTransition() override { return this; }
   const ElementPropertyTransition* AsTransition() const override
   {
     return this;
--- a/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/idlharness.html.ini
+++ b/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/idlharness.html.ini
@@ -1,18 +1,6 @@
 [idlharness.html]
   [KeyframeEffect interface: existence and properties of interface object]
     expected: FAIL
 
   [KeyframeEffect interface: existence and properties of interface prototype object]
     expected: FAIL
-
-  [KeyframeEffect interface: operation getKeyframes()]
-    expected: FAIL
-
-  [KeyframeEffect interface: attribute target]
-    expected: FAIL
-
-  [KeyframeEffect interface: attribute iterationComposite]
-    expected: FAIL
-
-  [KeyframeEffect interface: attribute composite]
-    expected: FAIL