Bug 564991. Part 22: Mark scrolled elements as inactive after a timeout. r=mats
authorRobert O'Callahan <robert@ocallahan.org>
Fri, 16 Jul 2010 09:08:05 +1200
changeset 47750 9cc11c490ec2d9a9faf58dc003208077e509fafb
parent 47749 1fa38a238c90bf57bfe87720575f63ccd9a61cfe
child 47751 e724a4858e54fd79beafdb3195f55db206b859ea
push id14413
push userrocallahan@mozilla.com
push dateThu, 15 Jul 2010 21:12:02 +0000
treeherderautoland@e1d7fd5255fd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmats
bugs564991
milestone2.0b2pre
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 564991. Part 22: Mark scrolled elements as inactive after a timeout. r=mats
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
xpcom/ds/nsExpirationTracker.h
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1267,16 +1267,36 @@ IsSmoothScrollingEnabled()
     nsresult rv = prefs->GetBoolPref(SMOOTH_SCROLL_PREF_NAME, &enabled);
     if (NS_SUCCEEDED(rv)) {
       return enabled;
     }
   }
   return PR_FALSE;
 }
 
+class ScrollFrameActivityTracker : public nsExpirationTracker<nsGfxScrollFrameInner,4> {
+public:
+  // Wait for 75-100ms between scrolls before we switch the appearance back to
+  // subpixel AA. That's 4 generations of 25ms each.
+  enum { TIMEOUT_MS = 25 };
+  ScrollFrameActivityTracker()
+    : nsExpirationTracker<nsGfxScrollFrameInner,4>(TIMEOUT_MS) {}
+  ~ScrollFrameActivityTracker() {
+    AgeAllGenerations();
+  }
+
+  virtual void NotifyExpired(nsGfxScrollFrameInner *aObject) {
+    RemoveObject(aObject);
+    aObject->mScrollingActive = PR_FALSE;
+    aObject->mOuter->InvalidateOverflowRect();
+  }
+};
+
+static ScrollFrameActivityTracker *gScrollFrameActivityTracker = nsnull;
+
 nsGfxScrollFrameInner::nsGfxScrollFrameInner(nsContainerFrame* aOuter,
                                              PRBool aIsRoot,
                                              PRBool aIsXUL)
   : mHScrollbarBox(nsnull),
     mVScrollbarBox(nsnull),
     mScrolledFrame(nsnull),
     mScrollCornerBox(nsnull),
     mOuter(aOuter),
@@ -1302,16 +1322,24 @@ nsGfxScrollFrameInner::nsGfxScrollFrameI
     mMayHaveDirtyFixedChildren(PR_FALSE),
     mUpdateScrollbarAttributes(PR_FALSE),
     mScrollingActive(PR_FALSE)
 {
 }
 
 nsGfxScrollFrameInner::~nsGfxScrollFrameInner()
 {
+  if (mActivityExpirationState.IsTracked()) {
+    gScrollFrameActivityTracker->RemoveObject(this);
+  }
+  if (gScrollFrameActivityTracker &&
+      gScrollFrameActivityTracker->IsEmpty()) {
+    delete gScrollFrameActivityTracker;
+    gScrollFrameActivityTracker = nsnull;
+  }
   delete mAsyncScroll;
 }
 
 static nscoord
 Clamp(nscoord aLower, nscoord aVal, nscoord aUpper)
 {
   if (aVal < aLower)
     return aLower;
@@ -1557,16 +1585,51 @@ InvalidateFixedBackgroundFrames(nsIFrame
     return;
 
   nsRegion visibleRegion(aUpdateRect);
   list.ComputeVisibility(&builder, &visibleRegion, nsnull);
 
   InvalidateFixedBackgroundFramesFromList(&builder, list);
 }
 
+PRBool nsGfxScrollFrameInner::IsAlwaysActive() const
+{
+  // The root scrollframe for a non-chrome document which is the direct
+  // child of a chrome document is always treated as "active".
+  if (!mIsRoot)
+    return PR_FALSE;
+  nsPresContext* presContext = mOuter->PresContext();
+  if (presContext->IsChrome())
+    return PR_FALSE;
+  nsIFrame* rootFrame = mOuter->PresContext()->PresShell()->GetRootFrame();
+  nsIFrame* rootParent = nsLayoutUtils::GetCrossDocParentFrame(rootFrame);
+  return !rootParent || rootParent->PresContext()->IsChrome(); 
+}
+
+PRBool nsGfxScrollFrameInner::IsScrollingActive() const
+{
+  return mScrollingActive || IsAlwaysActive();
+}
+
+void nsGfxScrollFrameInner::MarkActive()
+{
+  if (IsAlwaysActive())
+    return;
+
+  mScrollingActive = PR_TRUE;
+  if (mActivityExpirationState.IsTracked()) {
+    gScrollFrameActivityTracker->MarkUsed(this);
+  } else {
+    if (!gScrollFrameActivityTracker) {
+      gScrollFrameActivityTracker = new ScrollFrameActivityTracker();
+    }
+    gScrollFrameActivityTracker->AddObject(this);
+  }
+}
+
 void nsGfxScrollFrameInner::ScrollVisual(nsIntPoint aPixDelta)
 {
   nsRootPresContext* rootPresContext =
     mOuter->PresContext()->GetRootPresContext();
   if (!rootPresContext) {
     return;
   }
 
@@ -1590,20 +1653,20 @@ void nsGfxScrollFrameInner::ScrollVisual
   if (nearestWidget) {
     nearestWidget->ConfigureChildren(configurations);
   }
   AdjustViewsAndWidgets(mScrolledFrame, PR_FALSE);
   // We need to call this after fixing up the widget and view positions
   // to be consistent with the view and frame hierarchy.
   PRUint32 flags = nsIFrame::INVALIDATE_REASON_SCROLL_REPAINT;
   nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(mOuter);
-  if (CanScrollWithBlitting(mOuter, displayRoot) && mScrollingActive) {
+  if (IsScrollingActive() && CanScrollWithBlitting(mOuter, displayRoot)) {
     flags |= nsIFrame::INVALIDATE_NO_THEBES_LAYERS;
   }
-  mScrollingActive = PR_TRUE;
+  MarkActive();
   mOuter->InvalidateWithFlags(mScrollPort, flags);
 
   if (flags & nsIFrame::INVALIDATE_NO_THEBES_LAYERS) {
     // XXX fix this to transform rectangle properly
     InvalidateFixedBackgroundFrames(displayRoot, mScrolledFrame,
                                     mScrolledFrame->GetRect() + mOuter->GetOffsetTo(displayRoot));
   }
 }
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -49,16 +49,17 @@
 #include "nsThreadUtils.h"
 #include "nsIReflowCallback.h"
 #include "nsBoxLayoutState.h"
 #include "nsQueryFrame.h"
 #include "nsCOMArray.h"
 #ifdef MOZ_SVG
 #include "nsSVGIntegrationUtils.h"
 #endif
+#include "nsExpirationTracker.h"
 
 class nsPresContext;
 class nsIPresShell;
 class nsIContent;
 class nsIAtom;
 class nsIDocument;
 class nsIScrollFrameInternal;
 class nsPresState;
@@ -208,30 +209,34 @@ public:
   PRUint32 GetScrollbarVisibility() const {
     return (mHasVerticalScrollbar ? nsIScrollableFrame::VERTICAL : 0) |
            (mHasHorizontalScrollbar ? nsIScrollableFrame::HORIZONTAL : 0);
   }
   nsMargin GetActualScrollbarSizes() const;
   nsMargin GetDesiredScrollbarSizes(nsBoxLayoutState* aState);
   PRBool IsLTR() const;
   PRBool IsScrollbarOnRight() const;
-  PRBool IsScrollingActive() { return mScrollingActive; }
+  PRBool IsScrollingActive() const;
   // adjust the scrollbar rectangle aRect to account for any visible resizer.
   // aHasResizer specifies if there is a content resizer, however this method
   // will also check if a widget resizer is present as well.
   void AdjustScrollbarRectForResizer(nsIFrame* aFrame, nsPresContext* aPresContext,
                                      nsRect& aRect, PRBool aHasResizer, PRBool aVertical);
   // returns true if a resizer should be visible
   PRBool HasResizer() {
       return mScrollCornerContent && mScrollCornerContent->Tag() == nsGkAtoms::resizer;
   }
   void LayoutScrollbars(nsBoxLayoutState& aState,
                         const nsRect& aContentArea,
                         const nsRect& aOldScrollArea);
 
+  PRBool IsAlwaysActive() const;
+  void MarkActive();
+  nsExpirationState* GetExpirationState() { return &mActivityExpirationState; }
+
   // owning references to the nsIAnonymousContentCreator-built content
   nsCOMPtr<nsIContent> mHScrollbarContent;
   nsCOMPtr<nsIContent> mVScrollbarContent;
   nsCOMPtr<nsIContent> mScrollCornerContent;
 
   nsRevocableEventPtr<ScrollEvent> mScrollEvent;
   nsRevocableEventPtr<AsyncScrollPortEvent> mAsyncScrollPortEvent;
   nsRevocableEventPtr<ScrolledAreaEvent> mScrolledAreaEvent;
@@ -248,16 +253,18 @@ public:
   // just the current scroll position. ScrollBy will choose its
   // destination based on this value.
   nsPoint mDestination;
   nsPoint mScrollPosAtLastPaint;
 
   nsPoint mRestorePos;
   nsPoint mLastPos;
 
+  nsExpirationState mActivityExpirationState;
+
   PRPackedBool mNeverHasVerticalScrollbar:1;
   PRPackedBool mNeverHasHorizontalScrollbar:1;
   PRPackedBool mHasVerticalScrollbar:1;
   PRPackedBool mHasHorizontalScrollbar:1;
   PRPackedBool mFrameIsUpdatingScrollbar:1;
   PRPackedBool mDidHistoryRestore:1;
   // Is this the scrollframe for the document's viewport?
   PRPackedBool mIsRoot:1;
--- a/xpcom/ds/nsExpirationTracker.h
+++ b/xpcom/ds/nsExpirationTracker.h
@@ -254,22 +254,30 @@ template <class T, PRUint32 K> class nsE
           mIndex = 0;
         }
         return nsnull;
       }
     };
     
     friend class Iterator;
 
+    PRBool IsEmpty() {
+      for (PRUint32 i = 0; i < K; ++i) {
+        if (!mGenerations[i].IsEmpty())
+          return PR_FALSE;
+      }
+      return PR_TRUE;
+    }
+
   protected:
     /**
      * This must be overridden to catch notifications. It is called whenever
      * we detect that an object has not been used for at least (K-1)*mTimerPeriod
-     * seconds. If timer events are not delayed, it will be called within
-     * roughly K*mTimerPeriod seconds after the last use. (Unless AgeOneGeneration
+     * milliseconds. If timer events are not delayed, it will be called within
+     * roughly K*mTimerPeriod milliseconds after the last use. (Unless AgeOneGeneration
      * or AgeAllGenerations have been called to accelerate the aging process.)
      * 
      * NOTE: These bounds ignore delays in timer firings due to actual work being
      * performed by the browser. We use a slack timer so there is always at least
      * mTimerPeriod milliseconds between firings, which gives us (K-1)*mTimerPeriod
      * as a pretty solid lower bound. The upper bound is rather loose, however.
      * If the maximum amount by which any given timer firing is delayed is D, then
      * the upper bound before NotifyExpired is called is K*(mTimerPeriod + D).
@@ -293,23 +301,20 @@ template <class T, PRUint32 K> class nsE
     PRUint32           mTimerPeriod;
     PRUint32           mNewestGeneration;
     PRPackedBool       mInAgeOneGeneration;
 
     static void TimerCallback(nsITimer* aTimer, void* aThis) {
       nsExpirationTracker* tracker = static_cast<nsExpirationTracker*>(aThis);
       tracker->AgeOneGeneration();
       // Cancel the timer if we have no objects to track
-      PRUint32 i;
-      for (i = 0; i < K; ++i) {
-        if (!tracker->mGenerations[i].IsEmpty())
-          return;
+      if (tracker->IsEmpty()) {
+        tracker->mTimer->Cancel();
+        tracker->mTimer = nsnull;
       }
-      tracker->mTimer->Cancel();
-      tracker->mTimer = nsnull;
     }
 
     nsresult CheckStartTimer() {
       if (mTimer || !mTimerPeriod)
         return NS_OK;
       mTimer = do_CreateInstance("@mozilla.org/timer;1");
       if (!mTimer)
         return NS_ERROR_OUT_OF_MEMORY;