Bug 1201330 - Keep scroll handler induced layer activity active until the scroll frame becomes inactive. r=roc
authorMarkus Stange <mstange@themasta.com>
Fri, 30 Oct 2015 16:28:53 +0100
changeset 304966 1f3ea4c34877706356d43a1834228b1a7f91f18d
parent 304965 61217d41be19074ced225fcb09c281168ac7a6cd
child 304967 fec3431b0b127da40a84181bff9d039ad23050ee
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs1201330
milestone45.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 1201330 - Keep scroll handler induced layer activity active until the scroll frame becomes inactive. r=roc
layout/base/ActiveLayerTracker.cpp
layout/base/ActiveLayerTracker.h
layout/generic/nsGfxScrollFrame.cpp
mfbt/EnumSet.h
--- a/layout/base/ActiveLayerTracker.cpp
+++ b/layout/base/ActiveLayerTracker.cpp
@@ -92,34 +92,49 @@ public:
   nsIFrame* mFrame;
   nsIContent* mContent;
 
   nsExpirationState mState;
 
   // Previous scale due to the CSS transform property.
   Maybe<gfxSize> mPreviousTransformScale;
 
+  // The scroll frame during for which we most recently received a call to
+  // NotifyAnimatedFromScrollHandler.
+  nsWeakFrame mAnimatingScrollHandlerFrame;
+  // The set of activities that were triggered during
+  // mAnimatingScrollHandlerFrame's scroll event handler.
+  EnumSet<ActivityIndex> mScrollHandlerInducedActivity;
+
   // Number of restyle operations detected
   uint8_t mRestyleCounts[ACTIVITY_COUNT];
   bool mContentActive;
 };
 
 class LayerActivityTracker final : public nsExpirationTracker<LayerActivity,4> {
 public:
   // 75-100ms is a good timeout period. We use 4 generations of 25ms each.
   enum { GENERATION_MS = 100 };
   LayerActivityTracker()
     : nsExpirationTracker<LayerActivity,4>(GENERATION_MS,
                                            "LayerActivityTracker")
+    , mDestroying(false)
   {}
   ~LayerActivityTracker() {
+    mDestroying = true;
     AgeAllGenerations();
   }
 
   virtual void NotifyExpired(LayerActivity* aObject);
+
+public:
+  nsWeakFrame mCurrentScrollHandlerFrame;
+
+private:
+  bool mDestroying;
 };
 
 static LayerActivityTracker* gLayerActivityTracker = nullptr;
 
 LayerActivity::~LayerActivity()
 {
   if (mFrame || mContent) {
     NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker");
@@ -128,16 +143,23 @@ LayerActivity::~LayerActivity()
 }
 
 // Frames with this property have NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY set
 NS_DECLARE_FRAME_PROPERTY(LayerActivityProperty, DeleteValue<LayerActivity>)
 
 void
 LayerActivityTracker::NotifyExpired(LayerActivity* aObject)
 {
+  if (!mDestroying && aObject->mAnimatingScrollHandlerFrame.IsAlive()) {
+    // Reset the restyle counts, but let the layer activity survive.
+    PodArrayZero(aObject->mRestyleCounts);
+    MarkUsed(aObject);
+    return;
+  }
+
   RemoveObject(aObject);
 
   nsIFrame* f = aObject->mFrame;
   nsIContent* c = aObject->mContent;
   aObject->mFrame = nullptr;
   aObject->mContent = nullptr;
 
   MOZ_ASSERT((f == nullptr) != (c == nullptr),
@@ -289,44 +311,92 @@ ActiveLayerTracker::NotifyOffsetRestyle(
 ActiveLayerTracker::NotifyAnimated(nsIFrame* aFrame, nsCSSProperty aProperty)
 {
   LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
   uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
   // We know this is animated, so just hack the mutation count.
   mutationCount = 0xFF;
 }
 
+/* static */ void
+ActiveLayerTracker::NotifyAnimatedFromScrollHandler(nsIFrame* aFrame, nsCSSProperty aProperty,
+                                                    nsIFrame* aScrollFrame)
+{
+  if (aFrame->PresContext() != aScrollFrame->PresContext()) {
+    // Don't allow cross-document dependencies.
+    return;
+  }
+  LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+  LayerActivity::ActivityIndex activityIndex = LayerActivity::GetActivityIndexForProperty(aProperty);
+
+  if (layerActivity->mAnimatingScrollHandlerFrame.GetFrame() != aScrollFrame) {
+    // Discard any activity of a different scroll frame. We only track the
+    // most recent scroll handler induced activity.
+    layerActivity->mScrollHandlerInducedActivity.clear();
+    layerActivity->mAnimatingScrollHandlerFrame = aScrollFrame;
+  }
+
+  layerActivity->mScrollHandlerInducedActivity += activityIndex;
+}
+
 static bool
 IsPresContextInScriptAnimationCallback(nsPresContext* aPresContext)
 {
   if (aPresContext->RefreshDriver()->IsInRefresh()) {
     return true;
   }
   // Treat timeouts/setintervals as scripted animation callbacks for our
   // purposes.
   nsPIDOMWindow* win = aPresContext->Document()->GetInnerWindow();
   return win && win->IsRunningTimeout();
 }
 
 /* static */ void
 ActiveLayerTracker::NotifyInlineStyleRuleModified(nsIFrame* aFrame,
                                                   nsCSSProperty aProperty)
 {
-  if (!IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
-    return;
+  if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
+    NotifyAnimated(aFrame, aProperty);
   }
-  NotifyAnimated(aFrame, aProperty);
+  if (gLayerActivityTracker &&
+      gLayerActivityTracker->mCurrentScrollHandlerFrame.IsAlive()) {
+    NotifyAnimatedFromScrollHandler(aFrame, aProperty,
+      gLayerActivityTracker->mCurrentScrollHandlerFrame.GetFrame());
+  }
 }
 
 /* static */ bool
 ActiveLayerTracker::IsStyleMaybeAnimated(nsIFrame* aFrame, nsCSSProperty aProperty)
 {
   return IsStyleAnimated(nullptr, aFrame, aProperty);
 }
 
+static bool
+CheckScrollInducedActivity(LayerActivity* aLayerActivity,
+                           LayerActivity::ActivityIndex aActivityIndex,
+                           nsDisplayListBuilder* aBuilder)
+{
+  if (!aLayerActivity->mScrollHandlerInducedActivity.contains(aActivityIndex) ||
+      !aLayerActivity->mAnimatingScrollHandlerFrame.IsAlive()) {
+    return false;
+  }
+
+  nsIScrollableFrame* scrollFrame =
+    do_QueryFrame(aLayerActivity->mAnimatingScrollHandlerFrame.GetFrame());
+  if (scrollFrame && (!aBuilder || scrollFrame->IsScrollingActive(aBuilder))) {
+    return true;
+  }
+
+  // The scroll frame has been destroyed or has become inactive. Clear it from
+  // the layer activity so that it can expire.
+  aLayerActivity->mAnimatingScrollHandlerFrame = nullptr;
+  aLayerActivity->mScrollHandlerInducedActivity.clear();
+  return false;
+}
+
 /* static */ bool
 ActiveLayerTracker::IsStyleAnimated(nsDisplayListBuilder* aBuilder,
                                     nsIFrame* aFrame, nsCSSProperty aProperty)
 {
   // TODO: Add some abuse restrictions
   if ((aFrame->StyleDisplay()->mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM) &&
       aProperty == eCSSProperty_transform &&
       (!aBuilder || aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
@@ -335,17 +405,21 @@ ActiveLayerTracker::IsStyleAnimated(nsDi
   if ((aFrame->StyleDisplay()->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY) &&
       aProperty == eCSSProperty_opacity &&
       (!aBuilder || aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
     return true;
   }
 
   LayerActivity* layerActivity = GetLayerActivity(aFrame);
   if (layerActivity) {
-    if (layerActivity->RestyleCountForProperty(aProperty) >= 2) {
+    LayerActivity::ActivityIndex activityIndex = LayerActivity::GetActivityIndexForProperty(aProperty);
+    if (layerActivity->mRestyleCounts[activityIndex] >= 2) {
+      return true;
+    }
+    if (CheckScrollInducedActivity(layerActivity, activityIndex, aBuilder)) {
       return true;
     }
   }
   if (aProperty == eCSSProperty_transform && aFrame->Combines3DTransformWithAncestors()) {
     return IsStyleAnimated(aBuilder, aFrame->GetParent(), aProperty);
   }
   return nsLayoutUtils::HasCurrentAnimationsForProperties(aFrame, &aProperty, 1);
 }
@@ -454,15 +528,24 @@ ActiveLayerTracker::NotifyContentChange(
 /* static */ bool
 ActiveLayerTracker::IsContentActive(nsIFrame* aFrame)
 {
   LayerActivity* layerActivity = GetLayerActivity(aFrame);
   return layerActivity && layerActivity->mContentActive;
 }
 
 /* static */ void
+ActiveLayerTracker::SetCurrentScrollHandlerFrame(nsIFrame* aFrame)
+{
+  if (!gLayerActivityTracker) {
+    gLayerActivityTracker = new LayerActivityTracker();
+  }
+  gLayerActivityTracker->mCurrentScrollHandlerFrame = aFrame;
+}
+
+/* static */ void
 ActiveLayerTracker::Shutdown()
 {
   delete gLayerActivityTracker;
   gLayerActivityTracker = nullptr;
 }
 
 } // namespace mozilla
--- a/layout/base/ActiveLayerTracker.h
+++ b/layout/base/ActiveLayerTracker.h
@@ -45,16 +45,22 @@ public:
    */
   static void NotifyOffsetRestyle(nsIFrame* aFrame);
   /**
    * Mark aFrame as being known to have an animation of aProperty.
    * Any such marking will time out after a short period.
    */
   static void NotifyAnimated(nsIFrame* aFrame, nsCSSProperty aProperty);
   /**
+   * Notify aFrame as being known to have an animation of aProperty through an
+   * inline style modification during aScrollFrame's scroll event handler.
+   */
+  static void NotifyAnimatedFromScrollHandler(nsIFrame* aFrame, nsCSSProperty aProperty,
+                                              nsIFrame* aScrollFrame);
+  /**
    * Notify that a property in the inline style rule of aFrame's element
    * has been modified.
    * This notification is incomplete --- not all modifications to inline
    * style will trigger this.
    */
   static void NotifyInlineStyleRuleModified(nsIFrame* aFrame, nsCSSProperty aProperty);
   /**
    * Return true if aFrame's aProperty style should be considered as being animated
@@ -102,13 +108,20 @@ public:
    * Mark aFrame's content as being active. This marking will time out after
    * a short period.
    */
   static void NotifyContentChange(nsIFrame* aFrame);
   /**
    * Return true if this frame's content is still marked as active.
    */
   static bool IsContentActive(nsIFrame* aFrame);
+
+  /**
+   * Called before and after a scroll event handler is executed, with the
+   * scrollframe or nullptr, respectively. This acts as a hint to treat
+   * inline style changes during the handler differently.
+   */
+  static void SetCurrentScrollHandlerFrame(nsIFrame* aFrame);
 };
 
 } // namespace mozilla
 
 #endif /* ACTIVELAYERTRACKER_H_ */
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -4201,16 +4201,17 @@ ScrollFrameHelper::ScrollEvent::Run()
   return NS_OK;
 }
 
 void
 ScrollFrameHelper::FireScrollEvent()
 {
   mScrollEvent.Forget();
 
+  ActiveLayerTracker::SetCurrentScrollHandlerFrame(mOuter);
   WidgetGUIEvent event(true, eScroll, nullptr);
   nsEventStatus status = nsEventStatus_eIgnore;
   nsIContent* content = mOuter->GetContent();
   nsPresContext* prescontext = mOuter->PresContext();
   // Fire viewport scroll events at the document (where they
   // will bubble to the window)
   if (mIsRoot) {
     nsIDocument* doc = content->GetCurrentDoc();
@@ -4218,16 +4219,17 @@ ScrollFrameHelper::FireScrollEvent()
       EventDispatcher::Dispatch(doc, prescontext, &event, nullptr,  &status);
     }
   } else {
     // scroll events fired at elements don't bubble (although scroll events
     // fired at documents do, to the window)
     event.mFlags.mBubbles = false;
     EventDispatcher::Dispatch(content, prescontext, &event, nullptr, &status);
   }
+  ActiveLayerTracker::SetCurrentScrollHandlerFrame(nullptr);
 }
 
 void
 ScrollFrameHelper::PostScrollEvent()
 {
   if (mScrollEvent.IsPending())
     return;
 
--- a/mfbt/EnumSet.h
+++ b/mfbt/EnumSet.h
@@ -123,16 +123,24 @@ public:
   EnumSet<T> operator-(const EnumSet<T> aEnumSet) const
   {
     EnumSet<T> result(*this);
     result -= aEnumSet;
     return result;
   }
 
   /**
+   * Clear
+   */
+  void clear()
+  {
+    mBitField = 0;
+  }
+
+  /**
    * Intersection
    */
   void operator&=(const EnumSet<T> aEnumSet)
   {
     mBitField &= aEnumSet.mBitField;
   }
 
   /**