Bug 1610981 - Eliminate usage of CSSPseudoElement from KeyframeEffect. r=birtles,smaug
authorBoris Chiou <boris.chiou@gmail.com>
Tue, 18 Feb 2020 20:44:14 +0000
changeset 514533 ebfed5eb1869b1bdcb566ae805e6ee742e0a4323
parent 514532 0544d8d971067235d3a1da242cd88000f666464a
child 514534 df47b5f850b6d79ae69ffdc668f30650faa7f860
push id37136
push useropoprus@mozilla.com
push dateWed, 19 Feb 2020 04:34:03 +0000
treeherdermozilla-central@28cf163158a6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbirtles, smaug
bugs1610981
milestone75.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 1610981 - Eliminate usage of CSSPseudoElement from KeyframeEffect. r=birtles,smaug Replace ElementOrCSSPseudoElement with Element and add PseudoElement (which is a DOMString) into KeyframeAnimationOptions and KeyframeEffect. Differential Revision: https://phabricator.services.mozilla.com/D62667
devtools/server/actors/animation.js
dom/animation/Animation.cpp
dom/animation/Animation.h
dom/animation/AnimationEffect.cpp
dom/animation/AnimationTarget.h
dom/animation/CSSPseudoElement.cpp
dom/animation/CSSPseudoElement.h
dom/animation/EffectCompositor.cpp
dom/animation/KeyframeEffect.cpp
dom/animation/KeyframeEffect.h
dom/animation/KeyframeEffectParams.h
dom/animation/test/chrome/test_animation_observers_async.html
dom/animation/test/chrome/test_animation_observers_sync.html
dom/animation/test/mozilla/file_disable_animations_api_get_animations.html
dom/base/Element.cpp
dom/base/Element.h
dom/base/MutationObservers.cpp
dom/base/nsDOMMutationObserver.cpp
dom/bindings/Errors.msg
dom/webidl/CSSPseudoElement.webidl
dom/webidl/KeyframeEffect.webidl
testing/web-platform/meta/css/css-animations/Document-getAnimations.tentative.html.ini
testing/web-platform/meta/css/css-animations/Element-getAnimations.tentative.html.ini
testing/web-platform/meta/css/css-transitions/Document-getAnimations.tentative.html.ini
testing/web-platform/meta/web-animations/idlharness.window.js.ini
testing/web-platform/meta/web-animations/interfaces/Animatable/animate.html.ini
testing/web-platform/meta/web-animations/interfaces/Animation/commitStyles.html.ini
testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/style-change-events.html.ini
testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/target.html.ini
testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html
testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/target.html
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -105,27 +105,29 @@ var AnimationPlayerActor = protocol.Acto
       this.observer.disconnect();
     }
     this.player = this.observer = this.walker = null;
 
     Actor.prototype.destroy.call(this);
   },
 
   get isPseudoElement() {
-    return !!this.player.effect.target.element;
+    return !!this.player.effect.pseudoElement;
   },
 
   get node() {
     if (!this.isPseudoElement) {
       return this.player.effect.target;
     }
 
-    const pseudo = this.player.effect.target;
-    const treeWalker = this.walker.getDocumentWalker(pseudo.element);
-    return pseudo.type === "::before"
+    const originatingElem = this.player.effect.target;
+    const treeWalker = this.walker.getDocumentWalker(originatingElem);
+    // FIXME: Bug 1615473: It's possible to use ::before, ::after, ::marker,
+    // ::first-line, or ::first-letter pseudo selector.
+    return this.player.effect.pseudoElement === "::before"
       ? treeWalker.firstChild()
       : treeWalker.lastChild();
   },
 
   get document() {
     return this.node.ownerDocument;
   },
 
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -57,43 +57,44 @@ JSObject* Animation::WrapObject(JSContex
 namespace {
 // A wrapper around nsAutoAnimationMutationBatch that looks up the
 // appropriate document from the supplied animation.
 class MOZ_RAII AutoMutationBatchForAnimation {
  public:
   explicit AutoMutationBatchForAnimation(
       const Animation& aAnimation MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    Maybe<NonOwningAnimationTarget> target = aAnimation.GetTargetForAnimation();
+    NonOwningAnimationTarget target = aAnimation.GetTargetForAnimation();
     if (!target) {
       return;
     }
 
     // For mutation observers, we use the OwnerDoc.
-    mAutoBatch.emplace(target->mElement->OwnerDoc());
+    mAutoBatch.emplace(target.mElement->OwnerDoc());
   }
 
  private:
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
   Maybe<nsAutoAnimationMutationBatch> mAutoBatch;
 };
 }  // namespace
 
 // ---------------------------------------------------------------------------
 //
 // Animation interface:
 //
 // ---------------------------------------------------------------------------
 
-Maybe<NonOwningAnimationTarget> Animation::GetTargetForAnimation() const {
+NonOwningAnimationTarget Animation::GetTargetForAnimation() const {
   AnimationEffect* effect = GetEffect();
+  NonOwningAnimationTarget target;
   if (!effect || !effect->AsKeyframeEffect()) {
-    return Nothing();
+    return target;
   }
-  return effect->AsKeyframeEffect()->GetTarget();
+  return effect->AsKeyframeEffect()->GetAnimationTarget();
 }
 
 /* static */
 already_AddRefed<Animation> Animation::Constructor(
     const GlobalObject& aGlobal, AnimationEffect* aEffect,
     const Optional<AnimationTimeline*>& aTimeline, ErrorResult& aRv) {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<Animation> animation = new Animation(global);
@@ -636,45 +637,45 @@ void Animation::CommitStyles(ErrorResult
 
   // Take an owning reference to the keyframe effect. This will ensure that
   // this Animation and the target element remain alive after flushing style.
   RefPtr<KeyframeEffect> keyframeEffect = mEffect->AsKeyframeEffect();
   if (!keyframeEffect) {
     return;
   }
 
-  Maybe<NonOwningAnimationTarget> target = keyframeEffect->GetTarget();
+  NonOwningAnimationTarget target = keyframeEffect->GetAnimationTarget();
   if (!target) {
     return;
   }
 
-  if (target->mPseudoType != PseudoStyleType::NotPseudo) {
+  if (target.mPseudoType != PseudoStyleType::NotPseudo) {
     aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
     return;
   }
 
   // Check it is an element with a style attribute
-  nsCOMPtr<nsStyledElement> styledElement = do_QueryInterface(target->mElement);
+  nsCOMPtr<nsStyledElement> styledElement = do_QueryInterface(target.mElement);
   if (!styledElement) {
     aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
     return;
   }
 
   // Flush style before checking if the target element is rendered since the
   // result could depend on pending style changes.
-  if (Document* doc = target->mElement->GetComposedDoc()) {
+  if (Document* doc = target.mElement->GetComposedDoc()) {
     doc->FlushPendingNotifications(FlushType::Style);
   }
-  if (!target->mElement->IsRendered()) {
+  if (!target.mElement->IsRendered()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsPresContext* presContext =
-      nsContentUtils::GetContextForContent(target->mElement);
+      nsContentUtils::GetContextForContent(target.mElement);
   if (!presContext) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   // Get the computed animation values
   UniquePtr<RawServoAnimationValueMap> animationValues =
       Servo_AnimationValueMap_Create().Consume();
@@ -683,31 +684,31 @@ void Animation::CommitStyles(ErrorResult
     NS_WARNING("Failed to compose animation style to commit");
     return;
   }
 
   // Calling SetCSSDeclaration will trigger attribute setting code.
   // Start the update now so that the old rule doesn't get used
   // between when we mutate the declaration and when we set the new
   // rule.
-  mozAutoDocUpdate autoUpdate(target->mElement->OwnerDoc(), true);
+  mozAutoDocUpdate autoUpdate(target.mElement->OwnerDoc(), true);
 
   // Get the inline style to append to
   RefPtr<DeclarationBlock> declarationBlock;
-  if (auto* existing = target->mElement->GetInlineStyleDeclaration()) {
+  if (auto* existing = target.mElement->GetInlineStyleDeclaration()) {
     declarationBlock = existing->EnsureMutable();
   } else {
     declarationBlock = new DeclarationBlock();
     declarationBlock->SetDirty();
   }
 
   // Prepare the callback
   MutationClosureData closureData;
   closureData.mClosure = nsDOMCSSAttributeDeclaration::MutationClosureFunction;
-  closureData.mElement = target->mElement;
+  closureData.mElement = target.mElement;
   DeclarationBlockMutationClosure beforeChangeClosure = {
       nsDOMCSSAttributeDeclaration::MutationClosureFunction,
       &closureData,
   };
 
   // Set the animated styles
   bool changed = false;
   nsCSSPropertyIDSet properties = keyframeEffect->GetPropertySet();
@@ -721,17 +722,17 @@ void Animation::CommitStyles(ErrorResult
     }
   }
 
   if (!changed) {
     return;
   }
 
   // Update inline style declaration
-  target->mElement->SetInlineStyleDeclaration(*declarationBlock, closureData);
+  target.mElement->SetInlineStyleDeclaration(*declarationBlock, closureData);
 }
 
 // ---------------------------------------------------------------------------
 //
 // JS wrappers for Animation interface:
 //
 // ---------------------------------------------------------------------------
 
@@ -1047,17 +1048,17 @@ bool Animation::IsReplaceable() const {
   // We only replace animations that are filling.
   if (GetEffect()->GetComputedTiming().mProgress.IsNull()) {
     return false;
   }
 
   // We should only replace animations with a target element (since otherwise
   // what other effects would we consider when determining if they are covered
   // or not?).
-  if (!GetEffect()->AsKeyframeEffect()->GetTarget()) {
+  if (!GetEffect()->AsKeyframeEffect()->GetAnimationTarget()) {
     return false;
   }
 
   return true;
 }
 
 bool Animation::IsRemovable() const {
   return ReplaceState() == AnimationReplaceState::Active && IsReplaceable();
@@ -1066,25 +1067,26 @@ bool Animation::IsRemovable() const {
 void Animation::ScheduleReplacementCheck() {
   MOZ_ASSERT(
       IsReplaceable(),
       "Should only schedule a replacement check for a replaceable animation");
 
   // If IsReplaceable() is true, the following should also hold
   MOZ_ASSERT(GetEffect());
   MOZ_ASSERT(GetEffect()->AsKeyframeEffect());
-  MOZ_ASSERT(GetEffect()->AsKeyframeEffect()->GetTarget());
 
-  Maybe<NonOwningAnimationTarget> target =
-      GetEffect()->AsKeyframeEffect()->GetTarget();
+  NonOwningAnimationTarget target =
+      GetEffect()->AsKeyframeEffect()->GetAnimationTarget();
+
+  MOZ_ASSERT(target);
 
   nsPresContext* presContext =
-      nsContentUtils::GetContextForContent(target->mElement);
+      nsContentUtils::GetContextForContent(target.mElement);
   if (presContext) {
-    presContext->EffectCompositor()->NoteElementForReducing(*target);
+    presContext->EffectCompositor()->NoteElementForReducing(target);
   }
 }
 
 void Animation::MaybeScheduleReplacementCheck() {
   if (!IsReplaceable()) {
     return;
   }
 
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -58,17 +58,17 @@ class Animation : public DOMEventTargetH
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Animation, DOMEventTargetHelper)
 
   nsIGlobalObject* GetParentObject() const { return GetOwnerGlobal(); }
 
   /**
    * Utility function to get the target (pseudo-)element associated with an
    * animation.
    */
-  Maybe<NonOwningAnimationTarget> GetTargetForAnimation() const;
+  NonOwningAnimationTarget GetTargetForAnimation() const;
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   virtual CSSAnimation* AsCSSAnimation() { return nullptr; }
   virtual const CSSAnimation* AsCSSAnimation() const { return nullptr; }
   virtual CSSTransition* AsCSSTransition() { return nullptr; }
   virtual const CSSTransition* AsCSSTransition() const { return nullptr; }
--- a/dom/animation/AnimationEffect.cpp
+++ b/dom/animation/AnimationEffect.cpp
@@ -68,18 +68,18 @@ void AnimationEffect::SetSpecifiedTiming
   if (mTiming == aTiming) {
     return;
   }
 
   mTiming = aTiming;
 
   if (mAnimation) {
     Maybe<nsAutoAnimationMutationBatch> mb;
-    if (AsKeyframeEffect() && AsKeyframeEffect()->GetTarget()) {
-      mb.emplace(AsKeyframeEffect()->GetTarget()->mElement->OwnerDoc());
+    if (AsKeyframeEffect() && AsKeyframeEffect()->GetAnimationTarget()) {
+      mb.emplace(AsKeyframeEffect()->GetAnimationTarget().mElement->OwnerDoc());
     }
 
     mAnimation->NotifyEffectTimingUpdated();
 
     if (mAnimation->IsRelevant()) {
       MutationObservers::NotifyAnimationChanged(mAnimation);
     }
 
--- a/dom/animation/AnimationTarget.h
+++ b/dom/animation/AnimationTarget.h
@@ -48,16 +48,22 @@ struct NonOwningAnimationTarget {
 
   explicit NonOwningAnimationTarget(const OwningAnimationTarget& aOther)
       : mElement(aOther.mElement), mPseudoType(aOther.mPseudoType) {}
 
   bool operator==(const NonOwningAnimationTarget& aOther) const {
     return mElement == aOther.mElement && mPseudoType == aOther.mPseudoType;
   }
 
+  NonOwningAnimationTarget& operator=(const OwningAnimationTarget& aOther) {
+    mElement = aOther.mElement;
+    mPseudoType = aOther.mPseudoType;
+    return *this;
+  }
+
   explicit operator bool() const { return !!mElement; }
 
   // mElement represents the parent element of a pseudo-element, not the
   // generated content element.
   dom::Element* MOZ_NON_OWNING_REF mElement = nullptr;
   PseudoStyleType mPseudoType = PseudoStyleType::NotPseudo;
 };
 
--- a/dom/animation/CSSPseudoElement.cpp
+++ b/dom/animation/CSSPseudoElement.cpp
@@ -40,41 +40,16 @@ ParentObject CSSPseudoElement::GetParent
   return mOriginatingElement->GetParentObject();
 }
 
 JSObject* CSSPseudoElement::WrapObject(JSContext* aCx,
                                        JS::Handle<JSObject*> aGivenProto) {
   return CSSPseudoElement_Binding::Wrap(aCx, this, aGivenProto);
 }
 
-void CSSPseudoElement::GetAnimations(const GetAnimationsOptions& aOptions,
-                                     nsTArray<RefPtr<Animation>>& aRetVal) {
-  Document* doc = mOriginatingElement->GetComposedDoc();
-  if (doc) {
-    // We don't need to explicitly flush throttled animations here, since
-    // updating the animation style of (pseudo-)elements will never affect the
-    // set of running animations and it's only the set of running animations
-    // that is important here.
-    doc->FlushPendingNotifications(
-        ChangesToFlush(FlushType::Style, false /* flush animations */));
-  }
-
-  Element::GetAnimationsUnsorted(mOriginatingElement, mPseudoType, aRetVal);
-  aRetVal.Sort(AnimationPtrComparator<RefPtr<Animation>>());
-}
-
-already_AddRefed<Animation> CSSPseudoElement::Animate(
-    JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
-    const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
-    ErrorResult& aError) {
-  Nullable<ElementOrCSSPseudoElement> target;
-  target.SetValue().SetAsCSSPseudoElement() = this;
-  return Element::Animate(target, aContext, aKeyframes, aOptions, aError);
-}
-
 /* static */
 already_AddRefed<CSSPseudoElement> CSSPseudoElement::GetCSSPseudoElement(
     dom::Element* aElement, PseudoStyleType aType) {
   if (!aElement) {
     return nullptr;
   }
 
   nsAtom* propName = CSSPseudoElement::GetCSSPseudoElementPropertyAtom(aType);
--- a/dom/animation/CSSPseudoElement.h
+++ b/dom/animation/CSSPseudoElement.h
@@ -49,23 +49,16 @@ class CSSPseudoElement final : public ns
     aRetVal.Append(
         nsDependentAtomString(nsCSSPseudoElements::GetPseudoAtom(mPseudoType)));
   }
   already_AddRefed<dom::Element> Element() const {
     RefPtr<dom::Element> retVal(mOriginatingElement);
     return retVal.forget();
   }
 
-  void GetAnimations(const GetAnimationsOptions& aOptions,
-                     nsTArray<RefPtr<Animation>>& aRetVal);
-  already_AddRefed<Animation> Animate(
-      JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
-      const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
-      ErrorResult& aError);
-
   // Given an element:pseudoType pair, returns the CSSPseudoElement stored as a
   // property on |aElement|. If there is no CSSPseudoElement for the specified
   // pseudo-type on element, a new CSSPseudoElement will be created and stored
   // on the element.
   static already_AddRefed<CSSPseudoElement> GetCSSPseudoElement(
       dom::Element* aElement, PseudoStyleType aType);
 
  private:
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -266,16 +266,22 @@ void EffectCompositor::RequestRestyle(do
 
 void EffectCompositor::PostRestyleForAnimation(dom::Element* aElement,
                                                PseudoStyleType aPseudoType,
                                                CascadeLevel aCascadeLevel) {
   if (!mPresContext) {
     return;
   }
 
+  // FIXME: Bug 1615083 KeyframeEffect::SetTarget() and
+  // KeyframeEffect::SetPseudoElement() may set a non-existing pseudo element,
+  // and we still have to update its style, based on the wpt. However, we don't
+  // have the generated element here, so we failed the wpt.
+  //
+  // See wpt for more info: web-animations/interfaces/KeyframeEffect/target.html
   dom::Element* element = GetElementToRestyle(aElement, aPseudoType);
   if (!element) {
     return;
   }
 
   RestyleHint hint = aCascadeLevel == CascadeLevel::Transitions
                          ? RestyleHint::RESTYLE_CSS_TRANSITIONS
                          : RestyleHint::RESTYLE_CSS_ANIMATIONS;
@@ -469,35 +475,35 @@ bool EffectCompositor::GetServoAnimation
 
 bool EffectCompositor::ComposeServoAnimationRuleForEffect(
     KeyframeEffect& aEffect, CascadeLevel aCascadeLevel,
     RawServoAnimationValueMap* aAnimationValues) {
   MOZ_ASSERT(aAnimationValues);
   MOZ_ASSERT(mPresContext && mPresContext->IsDynamic(),
              "Should not be in print preview");
 
-  Maybe<NonOwningAnimationTarget> target = aEffect.GetTarget();
+  NonOwningAnimationTarget target = aEffect.GetAnimationTarget();
   if (!target) {
     return false;
   }
 
   // Don't try to compose animations for elements in documents without a pres
   // shell (e.g. XMLHttpRequest documents).
-  if (!nsContentUtils::GetPresShellForContent(target->mElement)) {
+  if (!nsContentUtils::GetPresShellForContent(target.mElement)) {
     return false;
   }
 
   // GetServoAnimationRule is called as part of the regular style resolution
   // where the cascade results are updated in the pre-traversal as needed.
   // This function, however, is only called when committing styles so we
   // need to ensure the cascade results are up-to-date manually.
-  MaybeUpdateCascadeResults(target->mElement, target->mPseudoType);
+  MaybeUpdateCascadeResults(target.mElement, target.mPseudoType);
 
   EffectSet* effectSet =
-      EffectSet::GetEffectSet(target->mElement, target->mPseudoType);
+      EffectSet::GetEffectSet(target.mElement, target.mPseudoType);
 
   // Get a list of effects sorted by composite order up to and including
   // |aEffect|, even if it is not in the EffectSet.
   auto comparator = EffectCompositeOrderComparator();
   nsTArray<KeyframeEffect*> sortedEffectList(effectSet ? effectSet->Count() + 1
                                                        : 1);
   if (effectSet) {
     for (KeyframeEffect* effect : *effectSet) {
@@ -507,19 +513,19 @@ bool EffectCompositor::ComposeServoAnima
     }
     sortedEffectList.Sort(comparator);
   }
   sortedEffectList.AppendElement(&aEffect);
 
   ComposeSortedEffects(sortedEffectList, effectSet, aCascadeLevel,
                        aAnimationValues);
 
-  MOZ_ASSERT(effectSet ==
-                 EffectSet::GetEffectSet(target->mElement, target->mPseudoType),
-             "EffectSet should not change while composing style");
+  MOZ_ASSERT(
+      effectSet == EffectSet::GetEffectSet(target.mElement, target.mPseudoType),
+      "EffectSet should not change while composing style");
 
   return true;
 }
 
 /* static */ dom::Element* EffectCompositor::GetElementToRestyle(
     dom::Element* aElement, PseudoStyleType aPseudoType) {
   if (aPseudoType == PseudoStyleType::NotPseudo) {
     return aElement;
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -5,17 +5,16 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/KeyframeEffect.h"
 
 #include "FrameLayerBuilder.h"
 #include "mozilla/dom/Animation.h"
 #include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
 // For UnrestrictedDoubleOrKeyframeAnimationOptions;
-#include "mozilla/dom/CSSPseudoElement.h"
 #include "mozilla/dom/KeyframeEffectBinding.h"
 #include "mozilla/dom/MutationObservers.h"
 #include "mozilla/AnimationUtils.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/ComputedStyleInlines.h"
 #include "mozilla/EffectSet.h"
 #include "mozilla/FloatingPoint.h"  // For IsFinite
 #include "mozilla/LayerAnimationInfo.h"
@@ -647,74 +646,76 @@ void KeyframeEffect::SetIsRunningOnCompo
 }
 
 void KeyframeEffect::ResetIsRunningOnCompositor() {
   for (AnimationProperty& property : mProperties) {
     property.mIsRunningOnCompositor = false;
   }
 }
 
+static bool IsSupportedPseudoForWebAnimation(PseudoStyleType aType) {
+  // FIXME: Bug 1615469: Support first-line and first-letter for Web Animation.
+  return aType == PseudoStyleType::before || aType == PseudoStyleType::after ||
+         aType == PseudoStyleType::marker;
+}
+
 static const KeyframeEffectOptions& KeyframeEffectOptionsFromUnion(
     const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions) {
   MOZ_ASSERT(aOptions.IsKeyframeEffectOptions());
   return aOptions.GetAsKeyframeEffectOptions();
 }
 
 static const KeyframeEffectOptions& KeyframeEffectOptionsFromUnion(
     const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions) {
   MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions());
   return aOptions.GetAsKeyframeAnimationOptions();
 }
 
 template <class OptionsType>
 static KeyframeEffectParams KeyframeEffectParamsFromUnion(
-    const OptionsType& aOptions, CallerType aCallerType) {
+    const OptionsType& aOptions, CallerType aCallerType, ErrorResult& aRv) {
   KeyframeEffectParams result;
   if (aOptions.IsUnrestrictedDouble() ||
       // Ignore iterationComposite and composite if the corresponding pref is
       // not set. The default value 'Replace' will be used instead.
       !StaticPrefs::dom_animations_api_compositing_enabled()) {
     return result;
   }
 
   const KeyframeEffectOptions& options =
       KeyframeEffectOptionsFromUnion(aOptions);
   result.mIterationComposite = options.mIterationComposite;
   result.mComposite = options.mComposite;
-  return result;
-}
 
-/* static */
-OwningAnimationTarget KeyframeEffect::ConvertTarget(
-    const Nullable<ElementOrCSSPseudoElement>& aTarget) {
-  // Return value optimization.
-  OwningAnimationTarget result;
-
-  if (aTarget.IsNull()) {
+  result.mPseudoType = PseudoStyleType::NotPseudo;
+  if (DOMStringIsNull(options.mPseudoElement)) {
     return result;
   }
 
-  const ElementOrCSSPseudoElement& target = aTarget.Value();
-  MOZ_ASSERT(target.IsElement() || target.IsCSSPseudoElement(),
-             "Uninitialized target");
+  RefPtr<nsAtom> pseudoAtom =
+      nsCSSPseudoElements::GetPseudoAtom(options.mPseudoElement);
+  if (!pseudoAtom) {
+    aRv.ThrowTypeError<MSG_INVALID_PSEUDO_SELECTOR>(options.mPseudoElement);
+    return result;
+  }
 
-  if (target.IsElement()) {
-    result.mElement = &target.GetAsElement();
-  } else {
-    result.mElement = target.GetAsCSSPseudoElement().Element();
-    result.mPseudoType = target.GetAsCSSPseudoElement().GetType();
+  result.mPseudoType = nsCSSPseudoElements::GetPseudoType(
+      pseudoAtom, CSSEnabledState::ForAllContent);
+
+  if (!IsSupportedPseudoForWebAnimation(result.mPseudoType)) {
+    aRv.ThrowTypeError<MSG_UNSUPPORTED_PSEUDO_SELECTOR>(options.mPseudoElement);
   }
+
   return result;
 }
 
 template <class OptionsType>
 /* static */
 already_AddRefed<KeyframeEffect> KeyframeEffect::ConstructKeyframeEffect(
-    const GlobalObject& aGlobal,
-    const Nullable<ElementOrCSSPseudoElement>& aTarget,
+    const GlobalObject& aGlobal, Element* 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.
   //
@@ -722,27 +723,32 @@ already_AddRefed<KeyframeEffect> Keyfram
   // the target global, but the KeyframeEffect constructors are called in the
   // caller's compartment to access `aKeyframes` object.
   Document* doc = AnimationUtils::GetDocumentFromGlobal(aGlobal.Get());
   if (!doc) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
+  KeyframeEffectParams effectOptions =
+      KeyframeEffectParamsFromUnion(aOptions, aGlobal.CallerType(), aRv);
+  // An invalid Pseudo-element aborts all further steps.
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
   TimingParams timingParams =
       TimingParams::FromOptionsUnion(aOptions, doc, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  KeyframeEffectParams effectOptions =
-      KeyframeEffectParamsFromUnion(aOptions, aGlobal.CallerType());
-
   RefPtr<KeyframeEffect> effect = new KeyframeEffect(
-      doc, ConvertTarget(aTarget), std::move(timingParams), effectOptions);
+      doc, OwningAnimationTarget(aTarget, effectOptions.mPseudoType),
+      std::move(timingParams), effectOptions);
 
   effect->SetKeyframes(aGlobal.Context(), aKeyframes, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   return effect.forget();
 }
@@ -783,16 +789,60 @@ template <typename FrameEnumFunc>
 static void EnumerateContinuationsOrIBSplitSiblings(nsIFrame* aFrame,
                                                     FrameEnumFunc&& aFunc) {
   while (aFrame) {
     aFunc(aFrame);
     aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
   }
 }
 
+void KeyframeEffect::UpdateTarget(Element* aElement,
+                                  PseudoStyleType aPseudoType) {
+  OwningAnimationTarget newTarget(aElement, aPseudoType);
+
+  if (mTarget == newTarget) {
+    // Assign the same target, skip it.
+    return;
+  }
+
+  if (mTarget) {
+    UnregisterTarget();
+    ResetIsRunningOnCompositor();
+
+    RequestRestyle(EffectCompositor::RestyleType::Layer);
+
+    nsAutoAnimationMutationBatch mb(mTarget.mElement->OwnerDoc());
+    if (mAnimation) {
+      MutationObservers::NotifyAnimationRemoved(mAnimation);
+    }
+  }
+
+  mTarget = newTarget;
+
+  if (mTarget) {
+    UpdateTargetRegistration();
+    RefPtr<ComputedStyle> computedStyle = GetTargetComputedStyle(Flush::None);
+    if (computedStyle) {
+      UpdateProperties(computedStyle);
+    }
+
+    RequestRestyle(EffectCompositor::RestyleType::Layer);
+
+    nsAutoAnimationMutationBatch mb(mTarget.mElement->OwnerDoc());
+    if (mAnimation) {
+      MutationObservers::NotifyAnimationAdded(mAnimation);
+      mAnimation->ReschedulePendingTasks();
+    }
+  }
+
+  if (mAnimation) {
+    mAnimation->NotifyEffectTargetUpdated();
+  }
+}
+
 void KeyframeEffect::UpdateTargetRegistration() {
   if (!mTarget) {
     return;
   }
 
   bool isRelevant = mAnimation && mAnimation->IsRelevant();
 
   // Animation::IsRelevant() returns a cached value. It only updates when
@@ -891,28 +941,26 @@ void DumpAnimationProperties(
              NS_ConvertUTF16toUTF8(toValue).get());
     }
   }
 }
 #endif
 
 /* static */
 already_AddRefed<KeyframeEffect> KeyframeEffect::Constructor(
-    const GlobalObject& aGlobal,
-    const Nullable<ElementOrCSSPseudoElement>& aTarget,
+    const GlobalObject& aGlobal, Element* aTarget,
     JS::Handle<JSObject*> aKeyframes,
     const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
     ErrorResult& aRv) {
   return ConstructKeyframeEffect(aGlobal, aTarget, aKeyframes, aOptions, aRv);
 }
 
 /* static */
 already_AddRefed<KeyframeEffect> KeyframeEffect::Constructor(
-    const GlobalObject& aGlobal,
-    const Nullable<ElementOrCSSPseudoElement>& aTarget,
+    const GlobalObject& aGlobal, Element* aTarget,
     JS::Handle<JSObject*> aKeyframes,
     const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
     ErrorResult& aRv) {
   return ConstructKeyframeEffect(aGlobal, aTarget, aKeyframes, aOptions, aRv);
 }
 
 /* static */
 already_AddRefed<KeyframeEffect> KeyframeEffect::Constructor(
@@ -942,83 +990,41 @@ already_AddRefed<KeyframeEffect> Keyfram
   effect->mKeyframes = aSource.mKeyframes;
   effect->mProperties = aSource.mProperties;
   for (auto iter = aSource.mBaseValues.ConstIter(); !iter.Done(); iter.Next()) {
     effect->mBaseValues.Put(iter.Key(), iter.Data());
   }
   return effect.forget();
 }
 
-void KeyframeEffect::GetTarget(
-    Nullable<OwningElementOrCSSPseudoElement>& aRv) const {
-  if (!mTarget) {
-    aRv.SetNull();
-    return;
-  }
-
-  switch (mTarget.mPseudoType) {
-    case PseudoStyleType::before:
-    case PseudoStyleType::after:
-    case PseudoStyleType::marker:
-      aRv.SetValue().SetAsCSSPseudoElement() =
-          CSSPseudoElement::GetCSSPseudoElement(mTarget.mElement,
-                                                mTarget.mPseudoType);
-      break;
-
-    case PseudoStyleType::NotPseudo:
-      aRv.SetValue().SetAsElement() = mTarget.mElement;
-      break;
-
-    default:
-      MOZ_ASSERT_UNREACHABLE("Animation of unsupported pseudo-type");
-      aRv.SetNull();
-  }
-}
-
-void KeyframeEffect::SetTarget(
-    const Nullable<ElementOrCSSPseudoElement>& aTarget) {
-  OwningAnimationTarget newTarget = ConvertTarget(aTarget);
-  if (mTarget == newTarget) {
-    // Assign the same target, skip it.
+void KeyframeEffect::SetPseudoElement(const nsAString& aPseudoElement,
+                                      ErrorResult& aRv) {
+  PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
+  if (DOMStringIsNull(aPseudoElement)) {
+    UpdateTarget(mTarget.mElement, pseudoType);
     return;
   }
 
-  if (mTarget) {
-    UnregisterTarget();
-    ResetIsRunningOnCompositor();
-
-    RequestRestyle(EffectCompositor::RestyleType::Layer);
-
-    nsAutoAnimationMutationBatch mb(mTarget.mElement->OwnerDoc());
-    if (mAnimation) {
-      MutationObservers::NotifyAnimationRemoved(mAnimation);
-    }
+  // Note: GetPseudoAtom() also returns nullptr for the null string,
+  // so we handle null case before this.
+  RefPtr<nsAtom> pseudoAtom =
+      nsCSSPseudoElements::GetPseudoAtom(aPseudoElement);
+  if (!pseudoAtom) {
+    aRv.ThrowTypeError<MSG_INVALID_PSEUDO_SELECTOR>(aPseudoElement);
+    return;
   }
 
-  mTarget = newTarget;
-
-  if (mTarget) {
-    UpdateTargetRegistration();
-    RefPtr<ComputedStyle> computedStyle = GetTargetComputedStyle(Flush::None);
-    if (computedStyle) {
-      UpdateProperties(computedStyle);
-    }
-
-    RequestRestyle(EffectCompositor::RestyleType::Layer);
-
-    nsAutoAnimationMutationBatch mb(mTarget.mElement->OwnerDoc());
-    if (mAnimation) {
-      MutationObservers::NotifyAnimationAdded(mAnimation);
-      mAnimation->ReschedulePendingTasks();
-    }
+  pseudoType = nsCSSPseudoElements::GetPseudoType(
+      pseudoAtom, CSSEnabledState::ForAllContent);
+  if (!IsSupportedPseudoForWebAnimation(pseudoType)) {
+    aRv.ThrowTypeError<MSG_UNSUPPORTED_PSEUDO_SELECTOR>(aPseudoElement);
+    return;
   }
 
-  if (mAnimation) {
-    mAnimation->NotifyEffectTargetUpdated();
-  }
+  UpdateTarget(mTarget.mElement, pseudoType);
 }
 
 static void CreatePropertyValue(
     nsCSSPropertyID aProperty, float aOffset,
     const Maybe<ComputedTimingFunction>& aTimingFunction,
     const AnimationValue& aValue, dom::CompositeOperation aComposite,
     const RawServoStyleSet* aRawSet, AnimationPropertyValueDetails& aResult) {
   aResult.mOffset = aOffset;
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -34,28 +34,25 @@
 struct JSContext;
 class JSObject;
 class nsIContent;
 class nsIFrame;
 
 namespace mozilla {
 
 class AnimValuesStyleRule;
-enum class PseudoStyleType : uint8_t;
 class ErrorResult;
 struct AnimationRule;
 struct TimingParams;
 class EffectSet;
 class ComputedStyle;
 class PresShell;
 
 namespace dom {
-class ElementOrCSSPseudoElement;
 class GlobalObject;
-class OwningElementOrCSSPseudoElement;
 class UnrestrictedDoubleOrKeyframeAnimationOptions;
 class UnrestrictedDoubleOrKeyframeEffectOptions;
 enum class IterationCompositeOperation : uint8_t;
 enum class CompositeOperation : uint8_t;
 struct AnimationPropertyDetails;
 }  // namespace dom
 
 struct AnimationProperty {
@@ -122,49 +119,57 @@ class KeyframeEffect : public AnimationE
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   KeyframeEffect* AsKeyframeEffect() override { return this; }
 
   // KeyframeEffect interface
   static already_AddRefed<KeyframeEffect> Constructor(
-      const GlobalObject& aGlobal,
-      const Nullable<ElementOrCSSPseudoElement>& aTarget,
+      const GlobalObject& aGlobal, Element* aTarget,
       JS::Handle<JSObject*> aKeyframes,
       const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
       ErrorResult& aRv);
 
   static already_AddRefed<KeyframeEffect> Constructor(
       const GlobalObject& aGlobal, 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,
+      const GlobalObject& aGlobal, Element* 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);
+  already_AddRefed<Element> GetTarget() const {
+    RefPtr<Element> ret = mTarget.mElement;
+    return ret.forget();
+  }
+  NonOwningAnimationTarget GetAnimationTarget() const {
+    return NonOwningAnimationTarget(mTarget.mElement, mTarget.mPseudoType);
+  }
+  void GetPseudoElement(nsAString& aRetVal) const {
+    if (mTarget.mPseudoType == PseudoStyleType::NotPseudo) {
+      SetDOMStringToNull(aRetVal);
+      return;
     }
-    return result;
+    aRetVal = nsCSSPseudoElements::PseudoTypeAsString(mTarget.mPseudoType);
   }
-  // This method calls GetTargetComputedStyle which is not safe to use when
+
+  // These two setters call GetTargetComputedStyle which is not safe to use when
   // we are in the middle of updating style. If we need to use this when
   // updating style, we should pass the ComputedStyle into this method and use
   // that to update the properties rather than calling
   // GetComputedStyle.
-  void SetTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget);
+  void SetTarget(Element* aTarget) {
+    UpdateTarget(aTarget, mTarget.mPseudoType);
+  }
+  void SetPseudoElement(const nsAString& aPseudoElement, ErrorResult& aRv);
 
   void GetKeyframes(JSContext*& aCx, nsTArray<JSObject*>& aResult,
                     ErrorResult& aRv) const;
   void GetProperties(nsTArray<AnimationPropertyDetails>& aProperties,
                      ErrorResult& aRv) const;
 
   IterationCompositeOperation IterationComposite() const;
   void SetIterationComposite(
@@ -273,17 +278,17 @@ class KeyframeEffect : public AnimationE
   //
   // When returning true, |aPerformanceWarning| stores the reason why
   // we shouldn't run the transform animations.
   bool ShouldBlockAsyncTransformAnimations(
       const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet,
       AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) const;
   bool HasGeometricProperties() const;
   bool AffectsGeometry() const override {
-    return GetTarget() && HasGeometricProperties();
+    return mTarget && HasGeometricProperties();
   }
 
   Document* GetRenderedDocument() const;
   PresShell* GetPresShell() const;
 
   // Associates a warning with the animated property set on the specified frame
   // indicating why, for example, the property could not be animated on the
   // compositor. |aParams| and |aParamsLength| are optional parameters which
@@ -340,31 +345,30 @@ class KeyframeEffect : public AnimationE
       const ComputedTiming& aComputedTiming,
       IterationCompositeOperation aIterationComposite,
       const Nullable<double>& aProgressOnLastCompose,
       uint64_t aCurrentIterationOnLastCompose);
 
  protected:
   ~KeyframeEffect() override = default;
 
-  static OwningAnimationTarget ConvertTarget(
-      const Nullable<ElementOrCSSPseudoElement>& aTarget);
-
   template <class OptionsType>
   static already_AddRefed<KeyframeEffect> ConstructKeyframeEffect(
-      const GlobalObject& aGlobal,
-      const Nullable<ElementOrCSSPseudoElement>& aTarget,
+      const GlobalObject& aGlobal, Element* aTarget,
       JS::Handle<JSObject*> aKeyframes, const OptionsType& aOptions,
       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);
 
+  // Helper for SetTarget() and SetPseudoElement().
+  void UpdateTarget(Element* aElement, PseudoStyleType aPseudoType);
+
   // This effect is registered with its target element so long as:
   //
   // (a) It has a target element, and
   // (b) It is "relevant" (i.e. yet to finish but not idle, or finished but
   //     filling forwards)
   //
   // As a result, we need to make sure this gets called whenever anything
   // changes with regards to this effects's timing including changes to the
--- a/dom/animation/KeyframeEffectParams.h
+++ b/dom/animation/KeyframeEffectParams.h
@@ -3,20 +3,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_KeyframeEffectParams_h
 #define mozilla_KeyframeEffectParams_h
 
 #include "mozilla/dom/KeyframeEffectBinding.h"  // IterationCompositeOperation
+#include "mozilla/PseudoStyleType.h"            // PseudoStyleType
 
 namespace mozilla {
 
 struct KeyframeEffectParams {
   dom::IterationCompositeOperation mIterationComposite =
       dom::IterationCompositeOperation::Replace;
   dom::CompositeOperation mComposite = dom::CompositeOperation::Replace;
+  PseudoStyleType mPseudoType = PseudoStyleType::NotPseudo;
 };
 
 }  // namespace mozilla
 
 #endif  // mozilla_KeyframeEffectParams_h
--- a/dom/animation/test/chrome/test_animation_observers_async.html
+++ b/dom/animation/test/chrome/test_animation_observers_async.html
@@ -497,23 +497,24 @@ promise_test(t => {
   var docAnims = document.getAnimations();
   assert_equals(docAnims.length, 10, "total animations");
 
   var divAnimations = div.getAnimations();
   var childAAnimations = childA.getAnimations();
   var childBAnimations = childB.getAnimations();
 
   var divBeforeAnimations =
-    docAnims.filter(x => (x.effect.target.element == div &&
-                          x.effect.target.type == "::before"));
+    docAnims.filter(x => (x.effect.target == div &&
+                          x.effect.pseudoElement == "::before"));
   var divAfterAnimations =
-    docAnims.filter(x => (x.effect.target.element == div &&
-                          x.effect.target.type == "::after"));
+    docAnims.filter(x => (x.effect.target == div &&
+                          x.effect.pseudoElement == "::after"));
   var childBPseudoAnimations =
-    docAnims.filter(x => x.effect.target.element == childB);
+    docAnims.filter(x => (x.effect.target == childB &&
+                          x.effect.pseudoElement == "::before"));
 
   var seekRecords;
   // The order in which we get the corresponding records is currently
   // based on the order we visit these nodes when updating styles.
   //
   // That is because we don't do any document-level batching of animation
   // mutation records when we flush styles. We may introduce that in the
   // future but for now all we are interested in testing here is that the
--- a/dom/animation/test/chrome/test_animation_observers_sync.html
+++ b/dom/animation/test/chrome/test_animation_observers_sync.html
@@ -69,31 +69,16 @@ function assert_equals_records(actual, e
                        expected[i].added, desc, i, "addedAnimations");
     assert_record_list(actual[i].changedAnimations,
                        expected[i].changed, desc, i, "changedAnimations");
     assert_record_list(actual[i].removedAnimations,
                        expected[i].removed, desc, i, "removedAnimations");
   }
 }
 
-// Create a pseudo element
-function createPseudo(test, element, type) {
-  addStyle(test, { '@keyframes anim': '',
-                   ['.pseudo::' + type]: 'animation: anim 10s; ' +
-                                         'content: \'\';'  });
-  element.classList.add('pseudo');
-  var anims = document.getAnimations();
-  assert_true(anims.length >= 1);
-  var anim = anims[anims.length - 1];
-  assert_equals(anim.effect.target.element, element);
-  assert_equals(anim.effect.target.type, '::' + type);
-  anim.cancel();
-  return anim.effect.target;
-}
-
 function runTest() {
   [ { subtree: false },
     { subtree: true }
   ].forEach(aOptions => {
     test(t => {
       var div = addDiv(t);
       var observer =
         setupSynchronousObserver(t,
@@ -1513,20 +1498,21 @@ function runTest() {
     assert_equals_records(observer.takeRecords(),
       [{ added: [], changed: [], removed: [anim] },
        { added: [anim], changed: [], removed: [] }],
       "records after setting a different target");
   }, "set_animation_target");
 
   test(t => {
     var div = addDiv(t);
-    var pseudoTarget = createPseudo(t, div, 'before');
     var observer = setupSynchronousObserver(t, div, true);
 
-    var anim = pseudoTarget.animate({ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
+    var anim = div.animate({ opacity: [ 0, 1 ] },
+                           { duration: 200 * MS_PER_SEC,
+                             pseudoElement: '::before' });
 
     assert_equals_records(observer.takeRecords(),
       [{ added: [anim], changed: [], removed: [] }],
       "records after animation is added");
 
     anim.effect.updateTiming({ duration: 100 * MS_PER_SEC });
     assert_equals_records(observer.takeRecords(),
       [{ added: [], changed: [anim], removed: [] }],
@@ -1556,23 +1542,23 @@ function runTest() {
 
     anim.effect.updateTiming({ duration: "auto" });
     assert_equals_records(observer.takeRecords(),
       [], "records after assigning same value \"auto\"");
   }, "change_duration_and_currenttime_on_pseudo_elements");
 
   test(t => {
     var div = addDiv(t);
-    var pseudoTarget = createPseudo(t, div, 'before');
     var observer = setupSynchronousObserver(t, div, false);
 
     var anim = div.animate({ opacity: [ 0, 1 ] },
                            { duration: 100 * MS_PER_SEC });
-    var pAnim = pseudoTarget.animate({ opacity: [ 0, 1 ] },
-                                     { duration: 100 * MS_PER_SEC });
+    var pAnim = div.animate({ opacity: [ 0, 1 ] },
+                            { duration: 100 * MS_PER_SEC,
+                              pseudoElement: "::before" });
 
     assert_equals_records(observer.takeRecords(),
       [{ added: [anim], changed: [], removed: [] }],
       "records after animation is added");
 
     anim.finish();
     pAnim.finish();
 
--- a/dom/animation/test/mozilla/file_disable_animations_api_get_animations.html
+++ b/dom/animation/test/mozilla/file_disable_animations_api_get_animations.html
@@ -10,16 +10,11 @@ test(t => {
 }, 'Element.getAnimations() is not available when getAnimations pref is'
     + ' disabled');
 
 test(t => {
   assert_false('getAnimations' in document);
 }, 'Document.getAnimations() is not available when getAnimations pref is'
     + ' disabled');
 
-test(t => {
-  assert_false('CSSPseudoElement' in window);
-}, 'CSSPseudoElement interface is not available when getAnimations pref is'
-    + ' disabled');
-
 done();
 </script>
 </body>
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3240,61 +3240,39 @@ already_AddRefed<DOMMatrixReadOnly> Elem
   RefPtr<DOMMatrixReadOnly> result(matrix);
   return result.forget();
 }
 
 already_AddRefed<Animation> Element::Animate(
     JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
     const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
     ErrorResult& aError) {
-  Nullable<ElementOrCSSPseudoElement> target;
-  target.SetValue().SetAsElement() = this;
-  return Animate(target, aContext, aKeyframes, aOptions, aError);
-}
-
-/* static */
-already_AddRefed<Animation> Element::Animate(
-    const Nullable<ElementOrCSSPseudoElement>& aTarget, JSContext* aContext,
-    JS::Handle<JSObject*> aKeyframes,
-    const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
-    ErrorResult& aError) {
-  MOZ_ASSERT(!aTarget.IsNull() && (aTarget.Value().IsElement() ||
-                                   aTarget.Value().IsCSSPseudoElement()),
-             "aTarget should be initialized");
-
-  RefPtr<Element> referenceElement;
-  if (aTarget.Value().IsElement()) {
-    referenceElement = &aTarget.Value().GetAsElement();
-  } else {
-    referenceElement = aTarget.Value().GetAsCSSPseudoElement().Element();
-  }
-
-  nsCOMPtr<nsIGlobalObject> ownerGlobal = referenceElement->GetOwnerGlobal();
+  nsCOMPtr<nsIGlobalObject> ownerGlobal = GetOwnerGlobal();
   if (!ownerGlobal) {
     aError.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
   GlobalObject global(aContext, ownerGlobal->GetGlobalJSObject());
   MOZ_ASSERT(!global.Failed());
 
   // KeyframeEffect constructor doesn't follow the standard Xray calling
   // convention and needs to be called in caller's compartment.
   // This should match to RunConstructorInCallerCompartment attribute in
   // KeyframeEffect.webidl.
-  RefPtr<KeyframeEffect> effect = KeyframeEffect::Constructor(
-      global, aTarget, aKeyframes, aOptions, aError);
+  RefPtr<KeyframeEffect> effect =
+      KeyframeEffect::Constructor(global, this, aKeyframes, aOptions, aError);
   if (aError.Failed()) {
     return nullptr;
   }
 
   // Animation constructor follows the standard Xray calling convention and
   // needs to be called in the target element's realm.
   JSAutoRealm ar(aContext, global.Get());
 
-  AnimationTimeline* timeline = referenceElement->OwnerDoc()->Timeline();
+  AnimationTimeline* timeline = OwnerDoc()->Timeline();
   RefPtr<Animation> animation = Animation::Constructor(
       global, effect, Optional<AnimationTimeline*>(timeline), aError);
   if (aError.Failed()) {
     return nullptr;
   }
 
   if (aOptions.IsKeyframeAnimationOptions()) {
     animation->SetId(aOptions.GetAsKeyframeAnimationOptions().mId);
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -1305,24 +1305,16 @@ class Element : public FragmentOrElement
   already_AddRefed<DOMMatrixReadOnly> GetTransformToParent();
   already_AddRefed<DOMMatrixReadOnly> GetTransformToViewport();
 
   already_AddRefed<Animation> Animate(
       JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
       const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
       ErrorResult& aError);
 
-  // A helper method that factors out the common functionality needed by
-  // Element::Animate and CSSPseudoElement::Animate
-  static already_AddRefed<Animation> Animate(
-      const Nullable<ElementOrCSSPseudoElement>& aTarget, JSContext* aContext,
-      JS::Handle<JSObject*> aKeyframes,
-      const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
-      ErrorResult& aError);
-
   enum class Flush { Yes, No };
 
   MOZ_CAN_RUN_SCRIPT
   void GetAnimations(const GetAnimationsOptions& aOptions,
                      nsTArray<RefPtr<Animation>>& aAnimations,
                      Flush aFlush = Flush::Yes);
 
   static void GetAnimationsUnsorted(Element* aElement,
--- a/dom/base/MutationObservers.cpp
+++ b/dom/base/MutationObservers.cpp
@@ -217,26 +217,26 @@ void MutationObservers::NotifyContentRem
                              IsRemoveNotification::Yes);
 }
 }  // namespace mozilla
 
 void MutationObservers::NotifyAnimationMutated(
     dom::Animation* aAnimation, AnimationMutationType aMutatedType) {
   MOZ_ASSERT(aAnimation);
 
-  Maybe<NonOwningAnimationTarget> target = aAnimation->GetTargetForAnimation();
+  NonOwningAnimationTarget target = aAnimation->GetTargetForAnimation();
   if (!target) {
     return;
   }
 
   // A pseudo element and its parent element use the same owner doc.
-  Document* doc = target->mElement->OwnerDoc();
+  Document* doc = target.mElement->OwnerDoc();
   if (doc->MayHaveAnimationObservers()) {
     // we use the its parent element as the subject in DOM Mutation Observer.
-    Element* elem = target->mElement;
+    Element* elem = target.mElement;
     switch (aMutatedType) {
       case AnimationMutationType::Added:
         IMPL_ANIMATION_NOTIFICATION(AnimationAdded, elem, (aAnimation));
         break;
       case AnimationMutationType::Changed:
         IMPL_ANIMATION_NOTIFICATION(AnimationChanged, elem, (aAnimation));
         break;
       case AnimationMutationType::Removed:
--- a/dom/base/nsDOMMutationObserver.cpp
+++ b/dom/base/nsDOMMutationObserver.cpp
@@ -348,30 +348,30 @@ void nsAnimationReceiver::RecordAnimatio
     return;
   }
 
   mozilla::dom::KeyframeEffect* keyframeEffect = effect->AsKeyframeEffect();
   if (!keyframeEffect) {
     return;
   }
 
-  Maybe<NonOwningAnimationTarget> animationTarget = keyframeEffect->GetTarget();
+  NonOwningAnimationTarget animationTarget =
+      keyframeEffect->GetAnimationTarget();
   if (!animationTarget) {
     return;
   }
 
-  Element* elem = animationTarget->mElement;
+  Element* elem = animationTarget.mElement;
   if (!Animations() || !(Subtree() || elem == Target()) ||
       elem->ChromeOnlyAccess()) {
     return;
   }
 
   // Record animations targeting to a pseudo element only when subtree is true.
-  if (animationTarget->mPseudoType != PseudoStyleType::NotPseudo &&
-      !Subtree()) {
+  if (animationTarget.mPseudoType != PseudoStyleType::NotPseudo && !Subtree()) {
     return;
   }
 
   if (nsAutoAnimationMutationBatch::IsBatching()) {
     switch (aMutationType) {
       case eAnimationMutation_Added:
         nsAutoAnimationMutationBatch::AnimationAdded(aAnimation, elem);
         break;
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -78,16 +78,18 @@ MSG_DEF(MSG_INVALID_KEYFRAME_OFFSETS, 1,
 MSG_DEF(MSG_IS_NOT_PROMISE, 1, true, JSEXN_TYPEERR, "{0}Argument is not a Promise")
 MSG_DEF(MSG_SW_INSTALL_ERROR, 3, true, JSEXN_TYPEERR, "{0}ServiceWorker script at {1} for scope {2} encountered an error during installation.")
 MSG_DEF(MSG_SW_SCRIPT_THREW, 3, true, JSEXN_TYPEERR, "{0}ServiceWorker script at {1} for scope {2} threw an exception during script evaluation.")
 MSG_DEF(MSG_TYPEDARRAY_IS_SHARED, 1, false, JSEXN_TYPEERR, "{0} can't be a SharedArrayBuffer or an ArrayBufferView backed by a SharedArrayBuffer")
 MSG_DEF(MSG_CACHE_ADD_FAILED_RESPONSE, 4, true, JSEXN_TYPEERR, "{0}Cache got {1} response with bad status {2} while trying to add request {3}")
 MSG_DEF(MSG_SW_UPDATE_BAD_REGISTRATION, 3, true, JSEXN_TYPEERR, "{0}Failed to update the ServiceWorker for scope {1} because the registration has been {2} since the update was scheduled.")
 MSG_DEF(MSG_INVALID_DURATION_ERROR, 2, true, JSEXN_TYPEERR, "{0}Invalid duration '{1}'.")
 MSG_DEF(MSG_INVALID_EASING_ERROR, 2, true, JSEXN_TYPEERR, "{0}Invalid easing '{1}'.")
+MSG_DEF(MSG_INVALID_PSEUDO_SELECTOR, 2, true, JSEXN_TYPEERR, "{0}Invalid pseudo-selector '{1}'")
+MSG_DEF(MSG_UNSUPPORTED_PSEUDO_SELECTOR, 2, true, JSEXN_TYPEERR, "{0}Unsupported pseudo-selector '{1}'")
 MSG_DEF(MSG_TOKENLIST_NO_SUPPORTED_TOKENS, 3, true, JSEXN_TYPEERR, "{0}{1} attribute of <{2}> does not define any supported tokens")
 MSG_DEF(MSG_TIME_VALUE_OUT_OF_RANGE, 2, true, JSEXN_TYPEERR, "{0}{1} is outside the supported range for time values.")
 MSG_DEF(MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN, 2, true, JSEXN_TYPEERR, "{0}Request mode '{1}' was used, but request cache mode 'only-if-cached' can only be used with request mode 'same-origin'.")
 MSG_DEF(MSG_THRESHOLD_RANGE_ERROR, 1, true, JSEXN_RANGEERR, "{0}Threshold values must all be in the range [0, 1].")
 MSG_DEF(MSG_MATRIX_INIT_CONFLICTING_VALUE, 3, true, JSEXN_TYPEERR, "{0}Matrix init unexpectedly got different values for '{1}' and '{2}'.")
 MSG_DEF(MSG_MATRIX_INIT_EXCEEDS_2D, 2, true, JSEXN_TYPEERR, "{0}Matrix init has an unexpected 3D element '{1}' which cannot coexist with 'is2D: true'.")
 MSG_DEF(MSG_MATRIX_INIT_LENGTH_WRONG, 2, true, JSEXN_TYPEERR, "{0}Matrix init sequence must have a length of 6 or 16 (actual value: {1})")
 MSG_DEF(MSG_INVALID_MEDIA_VIDEO_CONFIGURATION, 1, true, JSEXN_TYPEERR, "{0}Invalid VideoConfiguration.")
--- a/dom/webidl/CSSPseudoElement.webidl
+++ b/dom/webidl/CSSPseudoElement.webidl
@@ -11,11 +11,8 @@
  */
 
 [Func="Document::IsWebAnimationsGetAnimationsEnabled",
  Exposed=Window]
 interface CSSPseudoElement {
   readonly attribute DOMString type;
   readonly attribute Element element;
 };
-
-// https://drafts.csswg.org/web-animations/#extensions-to-the-pseudoelement-interface
-CSSPseudoElement includes Animatable;
--- a/dom/webidl/KeyframeEffect.webidl
+++ b/dom/webidl/KeyframeEffect.webidl
@@ -13,32 +13,34 @@
 enum IterationCompositeOperation {
   "replace",
   "accumulate"
 };
 
 dictionary KeyframeEffectOptions : EffectTiming {
   IterationCompositeOperation iterationComposite = "replace";
   CompositeOperation          composite = "replace";
+  DOMString?                  pseudoElement = null;
 };
 
 // KeyframeEffect should run in the caller's compartment to do custom
 // processing on the `keyframes` object.
 [Func="Document::IsWebAnimationsEnabled",
  RunConstructorInCallerCompartment,
  Exposed=Window]
 interface KeyframeEffect : AnimationEffect {
   [Throws]
-  constructor((Element or CSSPseudoElement)? target,
+  constructor(Element? target,
               object? keyframes,
               optional (unrestricted double or KeyframeEffectOptions) options = {});
   [Throws]
   constructor(KeyframeEffect source);
 
-  attribute (Element or CSSPseudoElement)?  target;
+  attribute Element?                  target;
+  [SetterThrows] attribute DOMString? pseudoElement;
   [Pref="dom.animations-api.compositing.enabled"]
   attribute IterationCompositeOperation     iterationComposite;
   [Pref="dom.animations-api.compositing.enabled"]
   attribute CompositeOperation              composite;
   [Throws] sequence<object> getKeyframes();
   [Throws] void             setKeyframes(object? keyframes);
 };
 
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-animations/Document-getAnimations.tentative.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[Document-getAnimations.tentative.html]
-  [CSS Animations targetting (pseudo-)elements should have correct order after sorting]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-animations/Element-getAnimations.tentative.html.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[Element-getAnimations.tentative.html]
-  [{ subtree: true } on a leaf element returns the element's animations and its pseudo-elements' animations]
-    expected: FAIL
-
-  [{ subtree: true } on an element with a child returns animations from the element, its pseudo-elements, its child and its child pseudo-elements]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-transitions/Document-getAnimations.tentative.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[Document-getAnimations.tentative.html]
-  [CSS Transitions targetting (pseudo-)elements should have correct order after sorting]
-    expected: FAIL
-
--- a/testing/web-platform/meta/web-animations/idlharness.window.js.ini
+++ b/testing/web-platform/meta/web-animations/idlharness.window.js.ini
@@ -1,10 +1,4 @@
 [idlharness.window.html]
   [Animation interface: attribute timeline]
     expected: FAIL
 
-  [KeyframeEffect interface: attribute pseudoElement]
-    expected: FAIL
-
-  [KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "pseudoElement" with the proper type]
-    expected: FAIL
-
--- a/testing/web-platform/meta/web-animations/interfaces/Animatable/animate.html.ini
+++ b/testing/web-platform/meta/web-animations/interfaces/Animatable/animate.html.ini
@@ -3,20 +3,16 @@
     expected: FAIL
 
   [Element.animate() accepts a property-indexed keyframes specification with a CSS variable as the property]
     expected: FAIL
 
   [Element.animate() accepts a two property (one shorthand and one of its shorthand components) two value property-indexed keyframes specification]
     expected: FAIL
 
+  [animate() with pseudoElement parameter creates an Animation object for ::first-line]
+    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1615469
+
   [animate() with pseudoElement an Animation object targeting the correct pseudo-element for ::first-line]
     expected: FAIL
-
-  [animate() with pseudoElement an Animation object targeting the correct pseudo-element for ::marker]
-    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1615469
 
-  [animate() with pseudoElement without content creates an Animation object targeting the correct pseudo-element]
-    expected: FAIL
-
-  [animate() with pseudoElement an Animation object targeting the correct pseudo-element]
-    expected: FAIL
-
--- a/testing/web-platform/meta/web-animations/interfaces/Animation/commitStyles.html.ini
+++ b/testing/web-platform/meta/web-animations/interfaces/Animation/commitStyles.html.ini
@@ -1,10 +1,4 @@
 [commitStyles.html]
-  [Throws if the target element is a pseudo element]
-    expected: FAIL
-
-  [Checks the pseudo element condition before the not rendered condition]
-    expected: FAIL
-
   [Commits custom variables]
     expected: FAIL
 
deleted file mode 100644
--- a/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/style-change-events.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[style-change-events.html]
-  [All property keys are recognized]
-    expected: FAIL
-
--- a/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/target.html.ini
+++ b/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/target.html.ini
@@ -1,37 +1,25 @@
 [target.html]
-  [Change target from null to an existing pseudoElement setting pseudoElement first.]
-    expected: FAIL
-
   [Change target from an existing to a different non-existing pseudo-element by setting pseudoElement.]
     expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1615083
 
   [Change target from null to a non-existing pseudoElement setting target first.]
     expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1615083
 
   [Change target from an existing to a different non-existing pseudo-element by setting target.]
     expected: FAIL
-
-  [Change target from an existing to a different existing pseudo-element by setting target.]
-    expected: FAIL
-
-  [Change target from a non-existing to a different existing pseudo-element by setting target.]
-    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1615083
 
   [Change target from a non-existing to a different non-existing pseudo-element by setting target.]
     expected: FAIL
-
-  [Change target from a non-existing to a different existing pseudo-element by setting pseudoElement.]
-    expected: FAIL
-
-  [Change target from null to an existing pseudoElement setting target first.]
-    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1615083
 
   [Change target from null to a non-existing pseudoElement setting pseudoElement first.]
     expected: FAIL
-
-  [Change target from an existing to a different existing pseudo-element by setting pseudoElement.]
-    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1615083
 
   [Change target from a non-existing to a different non-existing pseudo-element by setting pseudoElement.]
     expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1615083
 
--- a/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html
+++ b/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html
@@ -260,25 +260,25 @@ test(t => {
 
 test(t => {
   const div = createDiv(t);
   div.classList.add('pseudo');
   div.style.display = 'list-item';
   getComputedStyle(div,"::marker").content; // Sync style
   const anim = div.animate(null, {pseudoElement: '::marker'});
   assert_class_string(anim, 'Animation', 'The returned object is an Animation for ::marker');
-}, 'animate() with pseudoElement parameter  creates an Animation object for ::marker');
+}, 'animate() with pseudoElement parameter creates an Animation object for ::marker');
 
 test(t => {
   const div = createDiv(t);
   div.classList.add('pseudo');
   div.textContent = 'foo';
   const anim = div.animate(null, {pseudoElement: '::first-line'});
   assert_class_string(anim, 'Animation', 'The returned object is an Animation for ::first-line');
-}, 'animate() with pseudoElement parameter  creates an Animation object for ::first-line');
+}, 'animate() with pseudoElement parameter creates an Animation object for ::first-line');
 
 test(t => {
   const div = createDiv(t);
   div.classList.add('pseudo');
   getComputedStyle(div,"::before").content; // Sync style
   const anim = div.animate(null, {pseudoElement: '::before'});
   assert_equals(anim.effect.target, div, 'The returned element has the correct target element');
   assert_equals(anim.effect.pseudoElement, '::before',
@@ -313,10 +313,24 @@ test(t => {
   div.textContent = 'foo';
   const anim = div.animate(null, {pseudoElement: '::first-line'});
   assert_equals(anim.effect.target, div, 'The returned element has the correct target element');
   assert_equals(anim.effect.pseudoElement, '::first-line',
                 'The returned Animation targets the correct selector');
 }, 'animate() with pseudoElement an Animation object targeting ' +
    'the correct pseudo-element for ::first-line');
 
+for (const pseudo of [
+  '',
+  'before',
+  '::abc',
+  '::placeholder',
+]) {
+  test(t => {
+    const div = createDiv(t);
+    assert_throws_js(TypeError, () => {
+      div.animate(null, {pseudoElement: pseudo});
+    });
+  }, `animate() with the invalid pseudoElement '${pseudo}' throws a TypeError`);
+}
+
 </script>
 </body>
--- a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/target.html
+++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/target.html
@@ -250,12 +250,23 @@ for (const hasContent of [true, false]){
                     'Value of 2nd element (currently targeted) after ' +
                     'changing the effect target');
     }, "Change target from " + (prevHasContent ? "an existing" : "a non-existing") +
         " to a different " + (hasContent ? "existing" : "non-existing") +
         " pseudo-element by setting pseudoElement.");
   }
 }
 
-
+for (const pseudo of [
+  '',
+  'before',
+  '::abc',
+  '::placeholder',
+]) {
+  test(t => {
+    const effect = new KeyframeEffect(null, gKeyFrames, 100 * MS_PER_SEC);
+    assert_throws_js(TypeError, () => effect.pseudoElement = pseudo );
+  }, `Changing pseudoElement to invalid pseudo-selector '${pseudo}' throws a ` +
+     `TypeError`);
+}
 
 </script>
 </body>