Bug 771367 - Support compositor animation for pseudo-elements. r=dbaron
authorLee Salzman <lsalzman@mozilla.com>
Wed, 01 Jul 2015 11:55:51 -0400
changeset 251155 fa343e461c43ed55a1bea5ebfb9cbc134e6688e4
parent 251154 ba5ae82b4733ee5ede47524c1560c73d4b01581c
child 251156 12729d4f6730b1f60252c915915a3a0aeb48440c
push id13833
push userryanvm@gmail.com
push dateThu, 02 Jul 2015 20:00:55 +0000
treeherderfx-team@98101796b275 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs771367
milestone42.0a1
Bug 771367 - Support compositor animation for pseudo-elements. r=dbaron
layout/base/ActiveLayerTracker.cpp
layout/base/FrameLayerBuilder.cpp
layout/base/nsDisplayList.cpp
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
layout/style/AnimationCommon.cpp
layout/style/AnimationCommon.h
layout/style/nsAnimationManager.h
layout/style/nsTransitionManager.h
layout/xul/nsMenuPopupFrame.cpp
layout/xul/nsXULPopupManager.cpp
--- a/layout/base/ActiveLayerTracker.cpp
+++ b/layout/base/ActiveLayerTracker.cpp
@@ -281,22 +281,17 @@ ActiveLayerTracker::IsStyleAnimated(nsDi
   if (layerActivity) {
     if (layerActivity->RestyleCountForProperty(aProperty) >= 2) {
       return true;
     }
   }
   if (aProperty == eCSSProperty_transform && aFrame->Preserves3D()) {
     return IsStyleAnimated(aBuilder, aFrame->GetParent(), aProperty);
   }
-  nsIContent* content = aFrame->GetContent();
-  if (content) {
-    return nsLayoutUtils::HasCurrentAnimationsForProperties(content, &aProperty, 1);
-  }
-
-  return false;
+  return nsLayoutUtils::HasCurrentAnimationsForProperties(aFrame, &aProperty, 1);
 }
 
 /* static */ bool
 ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(nsIFrame* aFrame)
 {
   LayerActivity* layerActivity = GetLayerActivity(aFrame);
   if (layerActivity) {
     if (layerActivity->mLeftRestyleCount >= 2 ||
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -4768,21 +4768,20 @@ ChooseScaleAndSetTransform(FrameLayerBui
   }
 
   bool canDraw2D = transform.CanDraw2D(&transform2d);
   gfxSize scale;
   // XXX Should we do something for 3D transforms?
   if (canDraw2D) {
     // If the container's transform is animated off main thread, fix a suitable scale size
     // for animation
-    if (aContainerFrame->GetContent() &&
-        aContainerItem &&
+    if (aContainerItem &&
         aContainerItem->GetType() == nsDisplayItem::TYPE_TRANSFORM &&
         nsLayoutUtils::HasAnimationsForCompositor(
-          aContainerFrame->GetContent(), eCSSProperty_transform)) {
+          aContainerFrame, eCSSProperty_transform)) {
       // Use the size of the nearest widget as the maximum size.  This
       // is important since it might be a popup that is bigger than the
       // pres context's size.
       nsPresContext* presContext = aContainerFrame->PresContext();
       nsIWidget* widget = aContainerFrame->GetNearestWidget();
       nsSize displaySize;
       if (widget) {
         IntSize widgetSize = widget->GetClientSize();
@@ -4790,17 +4789,17 @@ ChooseScaleAndSetTransform(FrameLayerBui
         displaySize.width = NSIntPixelsToAppUnits(widgetSize.width, p2a);
         displaySize.height = NSIntPixelsToAppUnits(widgetSize.height, p2a);
       } else {
         displaySize = presContext->GetVisibleArea().Size();
       }
       // compute scale using the animation on the container (ignoring
       // its ancestors)
       scale = nsLayoutUtils::ComputeSuitableScaleForAnimation(
-                aContainerFrame->GetContent(), aVisibleRect.Size(),
+                aContainerFrame, aVisibleRect.Size(),
                 displaySize);
       // multiply by the scale inherited from ancestors
       scale.width *= aIncomingScale.mXScale;
       scale.height *= aIncomingScale.mYScale;
     } else {
       // Scale factors are normalized to a power of 2 to reduce the number of resolution changes
       scale = RoundToFloatPrecision(ThebesMatrix(transform2d).ScaleFactors(true));
       // For frames with a changing transform that's not just a translation,
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -510,24 +510,21 @@ nsDisplayListBuilder::AddAnimationsAndTr
   // any early returns since even if we don't add any animations to the
   // layer, we still need to mark it as up-to-date with regards to animations.
   // Otherwise, in RestyleManager we'll notice the discrepancy between the
   // animation generation numbers and update the layer indefinitely.
   uint64_t animationGeneration =
     RestyleManager::GetMaxAnimationGenerationForFrame(aFrame);
   aLayer->SetAnimationGeneration(animationGeneration);
 
-  nsIContent* content = aFrame->GetContent();
-  if (!content) {
-    return;
-  }
+  nsPresContext* presContext = aFrame->PresContext();
   AnimationCollection* transitions =
-    nsTransitionManager::GetAnimationsForCompositor(content, aProperty);
+    presContext->TransitionManager()->GetAnimationsForCompositor(aFrame, aProperty);
   AnimationCollection* animations =
-    nsAnimationManager::GetAnimationsForCompositor(content, aProperty);
+    presContext->AnimationManager()->GetAnimationsForCompositor(aFrame, aProperty);
 
   if (!animations && !transitions) {
     return;
   }
 
   // If the frame is not prerendered, bail out.
   // Do this check only during layer construction; during updating the
   // caller is required to check it appropriately.
@@ -718,18 +715,17 @@ void nsDisplayListBuilder::MarkOutOfFlow
       clipPtr = &clip;
     }
   }
 
   nsRect dirty = dirtyRectRelativeToDirtyFrame - aFrame->GetOffsetTo(aDirtyFrame);
   nsRect overflowRect = aFrame->GetVisualOverflowRect();
 
   if (aFrame->IsTransformed() &&
-      nsLayoutUtils::HasAnimationsForCompositor(aFrame->GetContent(),
-                                                eCSSProperty_transform)) {
+      nsLayoutUtils::HasAnimationsForCompositor(aFrame, eCSSProperty_transform)) {
    /**
     * Add a fuzz factor to the overflow rectangle so that elements only just
     * out of view are pulled into the display list, so they can be
     * prerendered if necessary.
     */
     overflowRect.Inflate(nsPresContext::CSSPixelsToAppUnits(32));
   }
 
@@ -3851,21 +3847,18 @@ IsItemTooSmallForActiveLayer(nsDisplayIt
 }
 
 bool
 nsDisplayOpacity::NeedsActiveLayer(nsDisplayListBuilder* aBuilder)
 {
   if (ActiveLayerTracker::IsStyleAnimated(aBuilder, mFrame, eCSSProperty_opacity) &&
       !IsItemTooSmallForActiveLayer(this))
     return true;
-  if (mFrame->GetContent()) {
-    if (nsLayoutUtils::HasAnimationsForCompositor(mFrame->GetContent(),
-                                                  eCSSProperty_opacity)) {
-      return true;
-    }
+  if (nsLayoutUtils::HasAnimationsForCompositor(mFrame, eCSSProperty_opacity)) {
+    return true;
   }
   return false;
 }
 
 void
 nsDisplayOpacity::ApplyOpacity(nsDisplayListBuilder* aBuilder,
                              float aOpacity,
                              const DisplayItemClip* aClip)
@@ -5082,19 +5075,17 @@ nsDisplayTransform::ShouldPrerenderTrans
                                                       nsIFrame* aFrame,
                                                       bool aLogAnimations)
 {
   // Elements whose transform has been modified recently, or which
   // have a compositor-animated transform, can be prerendered. An element
   // might have only just had its transform animated in which case
   // the ActiveLayerManager may not have been notified yet.
   if (!ActiveLayerTracker::IsStyleMaybeAnimated(aFrame, eCSSProperty_transform) &&
-      (!aFrame->GetContent() ||
-       !nsLayoutUtils::HasAnimationsForCompositor(aFrame->GetContent(),
-                                                  eCSSProperty_transform))) {
+      !nsLayoutUtils::HasAnimationsForCompositor(aFrame, eCSSProperty_transform)) {
     if (aLogAnimations) {
       nsCString message;
       message.AppendLiteral("Performance warning: Async animation disabled because frame was not marked active for transform animation");
       AnimationCollection::LogAsyncAnimationFailure(message,
                                                     aFrame->GetContent());
     }
     return false;
   }
@@ -5240,21 +5231,18 @@ nsDisplayTransform::GetLayerState(nsDisp
   if (!GetTransform().Is2D() || mFrame->Preserves3D()) {
     return LAYER_ACTIVE_FORCE;
   }
   // Here we check if the *post-transform* bounds of this item are big enough
   // to justify an active layer.
   if (ActiveLayerTracker::IsStyleAnimated(aBuilder, mFrame, eCSSProperty_transform) &&
       !IsItemTooSmallForActiveLayer(this))
     return LAYER_ACTIVE;
-  if (mFrame->GetContent()) {
-    if (nsLayoutUtils::HasAnimationsForCompositor(mFrame->GetContent(),
-                                                  eCSSProperty_transform)) {
-      return LAYER_ACTIVE;
-    }
+  if (nsLayoutUtils::HasAnimationsForCompositor(mFrame, eCSSProperty_transform)) {
+    return LAYER_ACTIVE;
   }
 
   const nsStyleDisplay* disp = mFrame->StyleDisplay();
   if ((disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM)) {
     return LAYER_ACTIVE;
   }
 
   // Expect the child display items to have this frame as their animated
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -367,91 +367,151 @@ TextAlignTrueEnabledPrefChangeCallback(c
   nsCSSProps::kTextAlignKTable[sIndexOfTrueInTextAlignTable] =
     isTextAlignTrueEnabled ? eCSSKeyword_true : eCSSKeyword_UNKNOWN;
   MOZ_ASSERT(sIndexOfTrueInTextAlignLastTable >= 0);
   nsCSSProps::kTextAlignLastKTable[sIndexOfTrueInTextAlignLastTable] =
     isTextAlignTrueEnabled ? eCSSKeyword_true : eCSSKeyword_UNKNOWN;
 }
 
 bool
-nsLayoutUtils::HasAnimationsForCompositor(nsIContent* aContent,
-                                          nsCSSProperty aProperty)
-{
-  return nsAnimationManager::GetAnimationsForCompositor(aContent, aProperty) ||
-         nsTransitionManager::GetAnimationsForCompositor(aContent, aProperty);
+nsLayoutUtils::GetAnimationContent(const nsIFrame* aFrame,
+                    nsIContent* &aContentResult,
+                    nsCSSPseudoElements::Type &aPseudoTypeResult)
+{
+  aPseudoTypeResult = nsCSSPseudoElements::ePseudo_NotPseudoElement;
+  aContentResult = aFrame->GetContent();
+  if (!aContentResult) {
+    return false;
+  }
+  if (aFrame->IsGeneratedContentFrame()) {
+    nsIFrame* parent = aFrame->GetParent();
+    if (parent->IsGeneratedContentFrame()) {
+      return false;
+    }
+    nsIAtom* name = aContentResult->NodeInfo()->NameAtom();
+    if (name == nsGkAtoms::mozgeneratedcontentbefore) {
+      aPseudoTypeResult = nsCSSPseudoElements::ePseudo_before;
+    } else if (name == nsGkAtoms::mozgeneratedcontentafter) {
+      aPseudoTypeResult = nsCSSPseudoElements::ePseudo_after;
+    } else {
+      return false;
+    }
+    aContentResult = aContentResult->GetParent();
+    if (!aContentResult) {
+      return false;
+    }
+  } else if (!aContentResult->MayHaveAnimations()) {
+    return false;
+  }
+  return true;
 }
 
 static AnimationCollection*
-GetAnimationsOrTransitions(nsIContent* aContent,
-                           nsIAtom* aAnimationProperty,
-                           nsCSSProperty aProperty)
-{
-  AnimationCollection* collection =
-    static_cast<AnimationCollection*>(aContent->GetProperty(
-        aAnimationProperty));
-  if (collection) {
-    bool propertyMatches = collection->HasAnimationOfProperty(aProperty);
-    if (propertyMatches) {
-      return collection;
-    }
-  }
-  return nullptr;
+GetAnimationCollection(nsIContent* aContent,
+                       nsCSSPseudoElements::Type aPseudoType,
+                       nsIAtom* aAnimationProperty)
+{
+  if (aPseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement) {
+    if (aAnimationProperty == nsGkAtoms::animationsProperty) {
+      aAnimationProperty =
+        aPseudoType == nsCSSPseudoElements::ePseudo_before ?
+          nsGkAtoms::animationsOfBeforeProperty :
+          nsGkAtoms::animationsOfAfterProperty;
+    } else if (aAnimationProperty == nsGkAtoms::transitionsProperty) {
+      aAnimationProperty =
+        aPseudoType == nsCSSPseudoElements::ePseudo_before ?
+          nsGkAtoms::transitionsOfBeforeProperty :
+          nsGkAtoms::transitionsOfAfterProperty;
+    } else {
+      MOZ_ASSERT(false, "unsupported animation property for pseudo-element");
+      return nullptr;
+    }
+  }
+  return static_cast<AnimationCollection*>(
+           aContent->GetProperty(aAnimationProperty));
 }
 
 bool
-nsLayoutUtils::HasAnimations(nsIContent* aContent,
-                             nsCSSProperty aProperty)
-{
-  if (!aContent->MayHaveAnimations())
-    return false;
-  return GetAnimationsOrTransitions(aContent, nsGkAtoms::animationsProperty,
-                                    aProperty) ||
-         GetAnimationsOrTransitions(aContent, nsGkAtoms::transitionsProperty,
-                                    aProperty);
+nsLayoutUtils::HasAnimationsForCompositor(const nsIFrame* aFrame,
+                                          nsCSSProperty aProperty)
+{
+  nsPresContext* presContext = aFrame->PresContext();
+  return presContext->AnimationManager()->GetAnimationsForCompositor(aFrame, aProperty) ||
+         presContext->TransitionManager()->GetAnimationsForCompositor(aFrame, aProperty);
 }
 
 bool
-nsLayoutUtils::HasCurrentAnimations(nsIContent* aContent,
-                                    nsIAtom* aAnimationProperty)
-{
-  if (!aContent->MayHaveAnimations())
+nsLayoutUtils::HasAnimations(const nsIFrame* aFrame,
+                             nsCSSProperty aProperty)
+{
+  nsIContent* content;
+  nsCSSPseudoElements::Type pseudoType;
+  if (!nsLayoutUtils::GetAnimationContent(aFrame, content, pseudoType)) {
     return false;
-
-  AnimationCollection* collection =
-    static_cast<AnimationCollection*>(
-      aContent->GetProperty(aAnimationProperty));
-  return (collection && collection->HasCurrentAnimations());
-}
-
-bool
-nsLayoutUtils::HasCurrentAnimationsForProperties(nsIContent* aContent,
-                                                 const nsCSSProperty* aProperties,
-                                                 size_t aPropertyCount)
-{
-  if (!aContent->MayHaveAnimations())
-    return false;
+  }
 
   static nsIAtom* const sAnimProps[] = { nsGkAtoms::transitionsProperty,
                                          nsGkAtoms::animationsProperty,
                                          nullptr };
   for (nsIAtom* const* animProp = sAnimProps; *animProp; animProp++) {
     AnimationCollection* collection =
-      static_cast<AnimationCollection*>(aContent->GetProperty(*animProp));
+      GetAnimationCollection(content, pseudoType, *animProp);
+    if (collection &&
+        collection->HasAnimationOfProperty(aProperty)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool
+nsLayoutUtils::HasCurrentAnimations(const nsIFrame* aFrame,
+                                    nsIAtom* aAnimationProperty)
+{
+  nsIContent* content;
+  nsCSSPseudoElements::Type pseudoType;
+  if (!nsLayoutUtils::GetAnimationContent(aFrame, content, pseudoType)) {
+    return false;
+  }
+
+  AnimationCollection* collection =
+    GetAnimationCollection(content, pseudoType, aAnimationProperty);
+  return (collection && collection->HasCurrentAnimations());
+}
+
+bool
+nsLayoutUtils::HasCurrentAnimationsForProperties(const nsIFrame* aFrame,
+                                                 const nsCSSProperty* aProperties,
+                                                 size_t aPropertyCount)
+{
+  nsIContent* content;
+  nsCSSPseudoElements::Type pseudoType;
+  if (!nsLayoutUtils::GetAnimationContent(aFrame, content, pseudoType)) {
+    return false;
+  }
+
+  static nsIAtom* const sAnimProps[] = { nsGkAtoms::transitionsProperty,
+                                         nsGkAtoms::animationsProperty,
+                                         nullptr };
+  for (nsIAtom* const* animProp = sAnimProps; *animProp; animProp++) {
+    AnimationCollection* collection =
+      GetAnimationCollection(content, pseudoType, *animProp);
     if (collection &&
         collection->HasCurrentAnimationsForProperties(aProperties,
                                                       aPropertyCount)) {
       return true;
     }
   }
 
   return false;
 }
 
 static gfxSize
-GetScaleForValue(const StyleAnimationValue& aValue, nsIFrame* aFrame)
+GetScaleForValue(const StyleAnimationValue& aValue, const nsIFrame* aFrame)
 {
   if (!aFrame) {
     NS_WARNING("No frame.");
     return gfxSize();
   }
   if (aValue.GetUnit() != StyleAnimationValue::eUnit_Transform) {
     NS_WARNING("Expected a transform.");
     return gfxSize();
@@ -486,73 +546,72 @@ GetSuitableScale(float aMaxScale, float 
   // We want to rasterize based on the largest scale used during the
   // transform animation, unless that would make us rasterize something
   // larger than the screen.  But we never want to go smaller than the
   // minimum scale over the animation.
   return std::max(std::min(aMaxScale, displayVisibleRatio), aMinScale);
 }
 
 static void
-GetMinAndMaxScaleForAnimationProperty(nsIContent* aContent,
+GetMinAndMaxScaleForAnimationProperty(const nsIFrame* aFrame,
                                       AnimationCollection* aAnimations,
                                       gfxSize& aMaxScale,
                                       gfxSize& aMinScale)
 {
   for (size_t animIdx = aAnimations->mAnimations.Length(); animIdx-- != 0; ) {
     dom::Animation* anim = aAnimations->mAnimations[animIdx];
     if (!anim->GetEffect() || anim->GetEffect()->IsFinishedTransition()) {
       continue;
     }
     dom::KeyframeEffectReadOnly* effect = anim->GetEffect();
     for (size_t propIdx = effect->Properties().Length(); propIdx-- != 0; ) {
       AnimationProperty& prop = effect->Properties()[propIdx];
       if (prop.mProperty == eCSSProperty_transform) {
         for (uint32_t segIdx = prop.mSegments.Length(); segIdx-- != 0; ) {
           AnimationPropertySegment& segment = prop.mSegments[segIdx];
-          gfxSize from = GetScaleForValue(segment.mFromValue,
-                                          aContent->GetPrimaryFrame());
+          gfxSize from = GetScaleForValue(segment.mFromValue, aFrame);
           aMaxScale.width = std::max<float>(aMaxScale.width, from.width);
           aMaxScale.height = std::max<float>(aMaxScale.height, from.height);
           aMinScale.width = std::min<float>(aMinScale.width, from.width);
           aMinScale.height = std::min<float>(aMinScale.height, from.height);
-          gfxSize to = GetScaleForValue(segment.mToValue,
-                                        aContent->GetPrimaryFrame());
+          gfxSize to = GetScaleForValue(segment.mToValue, aFrame);
           aMaxScale.width = std::max<float>(aMaxScale.width, to.width);
           aMaxScale.height = std::max<float>(aMaxScale.height, to.height);
           aMinScale.width = std::min<float>(aMinScale.width, to.width);
           aMinScale.height = std::min<float>(aMinScale.height, to.height);
         }
       }
     }
   }
 }
 
 gfxSize
-nsLayoutUtils::ComputeSuitableScaleForAnimation(nsIContent* aContent,
+nsLayoutUtils::ComputeSuitableScaleForAnimation(const nsIFrame* aFrame,
                                                 const nsSize& aVisibleSize,
                                                 const nsSize& aDisplaySize)
 {
   gfxSize maxScale(std::numeric_limits<gfxFloat>::min(),
                    std::numeric_limits<gfxFloat>::min());
   gfxSize minScale(std::numeric_limits<gfxFloat>::max(),
                    std::numeric_limits<gfxFloat>::max());
+  nsPresContext* presContext = aFrame->PresContext();
 
   AnimationCollection* animations =
-    nsAnimationManager::GetAnimationsForCompositor(aContent,
-                                                   eCSSProperty_transform);
+    presContext->AnimationManager()->GetAnimationsForCompositor(
+      aFrame, eCSSProperty_transform);
   if (animations) {
-    GetMinAndMaxScaleForAnimationProperty(aContent, animations,
+    GetMinAndMaxScaleForAnimationProperty(aFrame, animations,
                                           maxScale, minScale);
   }
 
   animations =
-    nsTransitionManager::GetAnimationsForCompositor(aContent,
-                                                    eCSSProperty_transform);
+    presContext->TransitionManager()->GetAnimationsForCompositor(
+      aFrame, eCSSProperty_transform);
   if (animations) {
-    GetMinAndMaxScaleForAnimationProperty(aContent, animations,
+    GetMinAndMaxScaleForAnimationProperty(aFrame, animations,
                                           maxScale, minScale);
   }
 
   if (maxScale.width == std::numeric_limits<gfxFloat>::min()) {
     // We didn't encounter a transform
     return gfxSize(1.0, 1.0);
   }
 
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2130,67 +2130,78 @@ public:
    *    (void)SizeOfTextRunsForFrames(rootFrame, nullptr, true);
    *    total = SizeOfTextRunsForFrames(rootFrame, mallocSizeOf, false);
    */
   static size_t SizeOfTextRunsForFrames(nsIFrame* aFrame,
                                         mozilla::MallocSizeOf aMallocSizeOf,
                                         bool clear);
 
   /**
-   * Returns true if the content node has animations or transitions that can be
+   * Given a frame with possibly animated content, finds the content node
+   * that contains its animations as well as the frame's pseudo-element type
+   * relative to the resulting content node. Returns true if animated content
+   * was found, otherwise it returns false and the output parameters are
+   * undefined.
+   */
+  static bool GetAnimationContent(const nsIFrame* aFrame,
+                                  nsIContent* &aContentResult,
+                                  nsCSSPseudoElements::Type &aPseudoTypeResult);
+
+  /**
+   * Returns true if the frame has animations or transitions that can be
    * performed on the compositor.
    */
-  static bool HasAnimationsForCompositor(nsIContent* aContent,
+  static bool HasAnimationsForCompositor(const nsIFrame* aFrame,
                                          nsCSSProperty aProperty);
 
   /**
-   * Returns true if the content node has animations or transitions for the
+   * Returns true if the frame has animations or transitions for the
    * property.
    */
-  static bool HasAnimations(nsIContent* aContent, nsCSSProperty aProperty);
+  static bool HasAnimations(const nsIFrame* aFrame, nsCSSProperty aProperty);
 
   /**
-   * Returns true if the content node has any current animations or transitions
+   * Returns true if the frame has any current animations or transitions
    * (depending on the value of |aAnimationProperty|).
    * A current animation is any animation that has not yet finished playing
    * including paused animations.
    */
-  static bool HasCurrentAnimations(nsIContent* aContent,
+  static bool HasCurrentAnimations(const nsIFrame* aFrame,
                                    nsIAtom* aAnimationProperty);
 
   /**
-   * Returns true if the content node has any current animations or transitions
+   * Returns true if the frame has any current animations or transitions
    * for any of the specified properties.
    */
-  static bool HasCurrentAnimationsForProperties(nsIContent* aContent,
+  static bool HasCurrentAnimationsForProperties(const nsIFrame* aFrame,
                                                 const nsCSSProperty* aProperties,
                                                 size_t aPropertyCount);
 
   /**
    * Checks if off-main-thread animations are enabled.
    */
   static bool AreAsyncAnimationsEnabled();
 
   /**
    * Checks if we should warn about animations that can't be async
    */
   static bool IsAnimationLoggingEnabled();
 
   /**
-   * Find a suitable scale for an element (aContent) over the course of any
+   * Find a suitable scale for a element (aFrame's content) over the course of any
    * animations and transitions of the CSS transform property on the
    * element that run on the compositor thread.
    * It will check the maximum and minimum scale during the animations and
    * transitions and return a suitable value for performance and quality.
    * Will return scale(1,1) if there are no such animations.
    * Always returns a positive value.
    * @param aVisibleSize is the size of the area we want to paint
    * @param aDisplaySize is the size of the display area of the pres context
    */
-  static gfxSize ComputeSuitableScaleForAnimation(nsIContent* aContent,
+  static gfxSize ComputeSuitableScaleForAnimation(const nsIFrame* aFrame,
                                                   const nsSize& aVisibleSize,
                                                   const nsSize& aDisplaySize);
 
   /**
    * Checks if we should forcibly use nearest pixel filtering for the
    * background.
    */
   static bool UseBackgroundNearestFiltering();
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1077,31 +1077,31 @@ nsIFrame::GetMarginRectRelativeToSelf() 
 
 bool
 nsIFrame::IsTransformed() const
 {
   return ((mState & NS_FRAME_MAY_BE_TRANSFORMED) &&
           (StyleDisplay()->HasTransform(this) ||
            IsSVGTransformed() ||
            (mContent &&
-            nsLayoutUtils::HasAnimationsForCompositor(mContent,
+            nsLayoutUtils::HasAnimationsForCompositor(this,
                                                       eCSSProperty_transform) &&
             IsFrameOfType(eSupportsCSSTransforms) &&
             mContent->GetPrimaryFrame() == this)));
 }
 
 bool
 nsIFrame::HasOpacityInternal(float aThreshold) const
 {
   MOZ_ASSERT(0.0 <= aThreshold && aThreshold <= 1.0, "Invalid argument");
   const nsStyleDisplay* displayStyle = StyleDisplay();
   return StyleDisplay()->mOpacity < aThreshold ||
          (displayStyle->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY) ||
          (mContent &&
-           nsLayoutUtils::HasAnimationsForCompositor(mContent,
+           nsLayoutUtils::HasAnimationsForCompositor(this,
                                                      eCSSProperty_opacity) &&
            mContent->GetPrimaryFrame() == this);
 }
 
 bool
 nsIFrame::IsSVGTransformed(gfx::Matrix *aOwnTransforms,
                            gfx::Matrix *aFromParentTransforms) const
 {
@@ -1941,17 +1941,17 @@ nsIFrame::BuildDisplayListForStackingCon
   // we're painting, and we're not animating opacity. Don't do this
   // if we're going to compute plugin geometry, since opacity-0 plugins
   // need to have display items built for them.
   bool needEventRegions = aBuilder->IsBuildingLayerEventRegions() &&
       StyleVisibility()->GetEffectivePointerEvents(this) != NS_STYLE_POINTER_EVENTS_NONE;
   if (disp->mOpacity == 0.0 && aBuilder->IsForPainting() &&
       !aBuilder->WillComputePluginGeometry() &&
       !(disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY) &&
-      !nsLayoutUtils::HasAnimations(mContent, eCSSProperty_opacity) &&
+      !nsLayoutUtils::HasAnimations(this, eCSSProperty_opacity) &&
       !needEventRegions) {
     return;
   }
 
   if (disp->mWillChangeBitField != 0) {
     aBuilder->AddToWillChangeBudget(this, GetSize());
   }
 
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -2586,17 +2586,17 @@ public:
   virtual bool IsSelfEmpty() = 0;
 
   /**
    * IsGeneratedContentFrame returns whether a frame corresponds to
    * generated content
    *
    * @return whether the frame correspods to generated content
    */
-  bool IsGeneratedContentFrame() {
+  bool IsGeneratedContentFrame() const {
     return (mState & NS_FRAME_GENERATED_CONTENT) != 0;
   }
 
   /**
    * IsPseudoFrame returns whether a frame is a pseudo frame (eg an
    * anonymous table-row frame created for a CSS table-cell without an
    * enclosing table-row.
    *
--- a/layout/style/AnimationCommon.cpp
+++ b/layout/style/AnimationCommon.cpp
@@ -129,25 +129,38 @@ CommonAnimationManager::NeedsRefresh() c
     if (static_cast<AnimationCollection*>(l)->mNeedsRefreshes) {
       return true;
     }
   }
   return false;
 }
 
 AnimationCollection*
-CommonAnimationManager::GetAnimationsForCompositor(nsIContent* aContent,
-                                                   nsIAtom* aElementProperty,
+CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame,
                                                    nsCSSProperty aProperty)
 {
-  if (!aContent->MayHaveAnimations())
+  nsIContent *content;
+  nsCSSPseudoElements::Type pseudoType;
+  if (!nsLayoutUtils::GetAnimationContent(aFrame, content, pseudoType)) {
     return nullptr;
+  }
+
+  nsIAtom *animProp;
+  if (pseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
+    animProp = GetAnimationsAtom();
+  } else if (pseudoType == nsCSSPseudoElements::ePseudo_before) {
+    animProp = GetAnimationsBeforeAtom();
+  } else if (pseudoType == nsCSSPseudoElements::ePseudo_after) {
+    animProp = GetAnimationsAfterAtom();
+  } else {
+    return nullptr;
+  }
 
   AnimationCollection* collection =
-    static_cast<AnimationCollection*>(aContent->GetProperty(aElementProperty));
+    static_cast<AnimationCollection*>(content->GetProperty(animProp));
   if (!collection ||
       !collection->HasAnimationOfProperty(aProperty) ||
       !collection->CanPerformOnCompositorThread(
         AnimationCollection::CanAnimate_AllowPartial)) {
     return nullptr;
   }
 
   // This animation can be done on the compositor.
@@ -332,21 +345,22 @@ CommonAnimationManager::GetAnimations(do
   if (!collection && aCreateIfNeeded) {
     // FIXME: Consider arena-allocating?
     collection = new AnimationCollection(aElement, propName, this);
     nsresult rv =
       aElement->SetProperty(propName, collection,
                             &AnimationCollection::PropertyDtor, false);
     if (NS_FAILED(rv)) {
       NS_WARNING("SetProperty failed");
-      delete collection;
+      // The collection must be destroyed via PropertyDtor, otherwise
+      // mCalledPropertyDtor assertion is triggered in destructor.
+      AnimationCollection::PropertyDtor(aElement, propName, collection, nullptr);
       return nullptr;
     }
-    if (propName == nsGkAtoms::animationsProperty ||
-        propName == nsGkAtoms::transitionsProperty) {
+    if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
       aElement->SetMayHaveAnimations();
     }
 
     AddElementCollection(collection);
   }
 
   return collection;
 }
@@ -572,31 +586,22 @@ AnimationCollection::IsCompositorAnimati
   void* prop = aFrame->Properties().Get(nsIFrame::RefusedAsyncAnimation());
   return bool(reinterpret_cast<intptr_t>(prop));
 }
 
 bool
 AnimationCollection::CanPerformOnCompositorThread(
   CanAnimateFlags aFlags) const
 {
-  nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
-  if (!frame) {
+  dom::Element* element = GetElementToRestyle();
+  if (!element) {
     return false;
   }
-
-  if (mElementProperty != nsGkAtoms::transitionsProperty &&
-      mElementProperty != nsGkAtoms::animationsProperty) {
-    if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
-      nsCString message;
-      message.AppendLiteral("Gecko bug: Async animation of pseudoelements"
-                            " not supported.  See bug 771367 (");
-      message.Append(nsAtomCString(mElementProperty));
-      message.Append(")");
-      LogAsyncAnimationFailure(message, mElement);
-    }
+  nsIFrame* frame = nsLayoutUtils::GetStyleFrame(element);
+  if (!frame) {
     return false;
   }
 
   for (size_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
     const Animation* anim = mAnimations[animIdx];
     if (!anim->IsPlaying()) {
       continue;
     }
@@ -623,17 +628,17 @@ AnimationCollection::CanPerformOnComposi
     const KeyframeEffectReadOnly* effect = anim->GetEffect();
     MOZ_ASSERT(effect, "A playing animation should have an effect");
 
     existsProperty = existsProperty || effect->Properties().Length() > 0;
 
     for (size_t propIdx = 0, propEnd = effect->Properties().Length();
          propIdx != propEnd; ++propIdx) {
       const AnimationProperty& prop = effect->Properties()[propIdx];
-      if (!CanAnimatePropertyOnCompositor(mElement,
+      if (!CanAnimatePropertyOnCompositor(element,
                                           prop.mProperty,
                                           aFlags) ||
           IsCompositorAnimationDisabledForFrame(frame)) {
         return false;
       }
     }
   }
 
@@ -834,20 +839,25 @@ AnimationCollection::CanThrottleTransfor
   }
 
   // If this animation can cause overflow, we can throttle some of the ticks.
   if (!mStyleRuleRefreshTime.IsNull() &&
       (aTime - mStyleRuleRefreshTime) < TimeDuration::FromMilliseconds(200)) {
     return true;
   }
 
+  dom::Element* element = GetElementToRestyle();
+  if (!element) {
+    return false;
+  }
+
   // If the nearest scrollable ancestor has overflow:hidden,
   // we don't care about overflow.
   nsIScrollableFrame* scrollable = nsLayoutUtils::GetNearestScrollableFrame(
-                                     nsLayoutUtils::GetStyleFrame(mElement));
+                                     nsLayoutUtils::GetStyleFrame(element));
   if (!scrollable) {
     return true;
   }
 
   ScrollbarStyles ss = scrollable->GetScrollbarStyles();
   if (ss.mVertical == NS_STYLE_OVERFLOW_HIDDEN &&
       ss.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN &&
       scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) {
@@ -855,17 +865,21 @@ AnimationCollection::CanThrottleTransfor
   }
 
   return false;
 }
 
 bool
 AnimationCollection::CanThrottleAnimation(TimeStamp aTime)
 {
-  nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
+  dom::Element* element = GetElementToRestyle();
+  if (!element) {
+    return false;
+  }
+  nsIFrame* frame = nsLayoutUtils::GetStyleFrame(element);
   if (!frame) {
     return false;
   }
 
 
   const auto& info = css::CommonAnimationManager::sLayerAnimationInfo;
   for (size_t i = 0; i < ArrayLength(info); i++) {
     auto record = info[i];
--- a/layout/style/AnimationCommon.h
+++ b/layout/style/AnimationCommon.h
@@ -156,29 +156,30 @@ protected:
   virtual nsIAtom* GetAnimationsAtom() = 0;
   virtual nsIAtom* GetAnimationsBeforeAtom() = 0;
   virtual nsIAtom* GetAnimationsAfterAtom() = 0;
 
   virtual bool IsAnimationManager() {
     return false;
   }
 
+
+public:
   // Return an AnimationCollection* if we have an animation for
-  // the element aContent and pseudo-element indicator aElementProperty
-  // that can be performed on the compositor thread (as defined by 
-  // AnimationCollection::CanPerformOnCompositorThread).
+  // the frame aFrame that can be performed on the compositor thread (as
+  // defined by AnimationCollection::CanPerformOnCompositorThread).
   //
-  // Note that this does not test whether the element's layer uses
+  // Note that this does not test whether the frame's layer uses
   // off-main-thread compositing, although it does check whether
   // off-main-thread compositing is enabled as a whole.
-  static AnimationCollection*
-  GetAnimationsForCompositor(nsIContent* aContent,
-                             nsIAtom* aElementProperty,
+  AnimationCollection*
+  GetAnimationsForCompositor(const nsIFrame* aFrame,
                              nsCSSProperty aProperty);
 
+protected:
   PRCList mElementCollections;
   nsPresContext *mPresContext; // weak (non-null from ctor to Disconnect)
   bool mIsObservingRefreshDriver;
 };
 
 /**
  * A style rule that maps property-StyleAnimationValue pairs.
  */
--- a/layout/style/nsAnimationManager.h
+++ b/layout/style/nsAnimationManager.h
@@ -158,23 +158,16 @@ class nsAnimationManager final
   : public mozilla::css::CommonAnimationManager
 {
 public:
   explicit nsAnimationManager(nsPresContext *aPresContext)
     : mozilla::css::CommonAnimationManager(aPresContext)
   {
   }
 
-  static mozilla::AnimationCollection*
-  GetAnimationsForCompositor(nsIContent* aContent, nsCSSProperty aProperty)
-  {
-    return mozilla::css::CommonAnimationManager::GetAnimationsForCompositor(
-      aContent, nsGkAtoms::animationsProperty, aProperty);
-  }
-
   void UpdateStyleAndEvents(mozilla::AnimationCollection* aEA,
                             mozilla::TimeStamp aRefreshTime,
                             mozilla::EnsureStyleRuleFlags aFlags);
   void QueueEvents(mozilla::AnimationCollection* aEA,
                    mozilla::EventArray &aEventsToDispatch);
 
   void MaybeUpdateCascadeResults(mozilla::AnimationCollection* aCollection);
 
--- a/layout/style/nsTransitionManager.h
+++ b/layout/style/nsTransitionManager.h
@@ -112,23 +112,16 @@ public:
   explicit nsTransitionManager(nsPresContext *aPresContext)
     : mozilla::css::CommonAnimationManager(aPresContext)
     , mInAnimationOnlyStyleUpdate(false)
   {
   }
 
   typedef mozilla::AnimationCollection AnimationCollection;
 
-  static AnimationCollection*
-  GetAnimationsForCompositor(nsIContent* aContent, nsCSSProperty aProperty)
-  {
-    return mozilla::css::CommonAnimationManager::GetAnimationsForCompositor(
-      aContent, nsGkAtoms::transitionsProperty, aProperty);
-  }
-
   /**
    * StyleContextChanged
    *
    * To be called from nsFrameManager::ReResolveStyleContext when the
    * style of an element has changed, to initiate transitions from
    * that style change.  For style contexts with :before and :after
    * pseudos, aElement is expected to be the generated before/after
    * element.
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -516,17 +516,17 @@ nsMenuPopupFrame::LayoutPopup(nsBoxLayou
   if (mIsOpenChanged) {
     mIsOpenChanged = false;
 
 #ifndef MOZ_WIDGET_GTK
     // If the animate attribute is set to open, check for a transition and wait
     // for it to finish before firing the popupshown event.
     if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::animate,
                               nsGkAtoms::open, eCaseMatters) &&
-        nsLayoutUtils::HasCurrentAnimations(mContent,
+        nsLayoutUtils::HasCurrentAnimations(this,
                                             nsGkAtoms::transitionsProperty)) {
       mPopupShownDispatcher = new nsXULPopupShownEvent(mContent, pc);
       mContent->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
                                        mPopupShownDispatcher, false, false);
       return;
     }
 #endif
 
--- a/layout/xul/nsXULPopupManager.cpp
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -1468,17 +1468,17 @@ nsXULPopupManager::FirePopupHidingEvent(
             (!animate.EqualsLiteral("cancel") || aIsCancel)) {
           presShell->FlushPendingNotifications(Flush_Layout);
 
           // Get the frame again in case the flush caused it to go away
           popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
           if (!popupFrame)
             return;
 
-          if (nsLayoutUtils::HasCurrentAnimations(aPopup,
+          if (nsLayoutUtils::HasCurrentAnimations(popupFrame,
                 nsGkAtoms::transitionsProperty)) {
             nsRefPtr<TransitionEnder> ender = new TransitionEnder(aPopup, aDeselectMenu);
             aPopup->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
                                            ender, false, false);
             return;
           }
         }
       }