Bug 702463: Part 1 v8 - Smooth scroll - Use refresh observer instead of a timer
☠☠ backed out by 44693b7c6742 ☠ ☠
authorAvi Halachmi (:avih)
Sat, 31 Mar 2012 16:08:00 +0300
changeset 94250 4e515b6a5a41e05b66a046eb78f7221f466da9e3
parent 94249 5c6e93c9d2326865404f7708270e541582a04d10
child 94251 44693b7c674273694204c459f3902401e31e5466
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs702463
milestone14.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 702463: Part 1 v8 - Smooth scroll - Use refresh observer instead of a timer
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1307,41 +1307,42 @@ NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame
  
 //-------------------- Inner ----------------------
 
 #define SMOOTH_SCROLL_PREF_NAME "general.smoothScroll"
 
 const double kCurrentVelocityWeighting = 0.25;
 const double kStopDecelerationWeighting = 0.4;
 
-class nsGfxScrollFrameInner::AsyncScroll {
+// AsyncScroll has ref counting.
+class nsGfxScrollFrameInner::AsyncScroll : public nsARefreshObserver {
 public:
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::TimeDuration TimeDuration;
 
-  AsyncScroll():
-    mIsFirstIteration(true)
-    {}
+  AsyncScroll()
+    : mIsFirstIteration(true)
+    , mCallee(nsnull)
+  {}
 
   ~AsyncScroll() {
-    if (mScrollTimer) mScrollTimer->Cancel();
+    RemoveObserver();
   }
 
   nsPoint PositionAt(TimeStamp aTime);
   nsSize VelocityAt(TimeStamp aTime); // In nscoords per second
 
   void InitSmoothScroll(TimeStamp aTime, nsPoint aCurrentPos,
                         nsSize aCurrentVelocity, nsPoint aDestination,
                         nsIAtom *aOrigin);
 
   bool IsFinished(TimeStamp aTime) {
     return aTime > mStartTime + mDuration; // XXX or if we've hit the wall
   }
 
-  nsCOMPtr<nsITimer> mScrollTimer;
   TimeStamp mStartTime;
 
   // mPrevStartTime holds previous 3 timestamps for intervals averaging (to
   // reduce duration fluctuations). When AsyncScroll is constructed and no
   // previous timestamps are available (indicated with mIsFirstIteration),
   // initialize mPrevStartTime using imaginary previous timestamps with maximum
   // relevant intervals between them.
   TimeStamp mPrevStartTime[3];
@@ -1377,16 +1378,61 @@ protected:
 
   // Initializes the timing function in such a way that the current velocity is
   // preserved.
   void InitTimingFunction(nsSMILKeySpline& aTimingFunction,
                           nscoord aCurrentPos, nscoord aCurrentVelocity,
                           nscoord aDestination);
 
   void InitDuration(nsIAtom *aOrigin);
+
+// The next section is observer/callback management
+// Bodies of WillRefresh and RefreshDriver contain nsGfxScrollFrameInner specific code.
+public:
+  NS_INLINE_DECL_REFCOUNTING(AsyncScroll)
+
+  /*
+   * Set a refresh observer for smooth scroll iterations (and start observing).
+   * Should be used at most once during the lifetime of this object.
+   * Return value: true on success, false otherwise.
+   */
+  bool SetRefreshObserver(nsGfxScrollFrameInner *aCallee) {
+    NS_ASSERTION(aCallee && !mCallee, "AsyncScroll::SetRefreshObserver - Invalid usage.");
+
+    if (!RefreshDriver(aCallee)->AddRefreshObserver(this, Flush_Display)) {
+      return false;
+    }
+
+    mCallee = aCallee;
+    return true;
+  }
+
+  virtual void WillRefresh(mozilla::TimeStamp aTime) {
+    // The callback may release "this".
+    // We don't access members after returning, so no need for KungFuDeathGrip.
+    nsGfxScrollFrameInner::AsyncScrollCallback(mCallee, aTime);
+  }
+
+private:
+  nsGfxScrollFrameInner *mCallee;
+
+  nsRefreshDriver* RefreshDriver(nsGfxScrollFrameInner* aCallee) {
+    return aCallee->mOuter->PresContext()->RefreshDriver();
+  }
+
+  /* 
+   * The refresh driver doesn't hold a reference to its observers,
+   *   so releasing this object can (and is) used to remove the observer on DTOR.
+   * Currently, this object is released once the scrolling ends.
+   */
+  void RemoveObserver() {
+    if (mCallee) {
+      RefreshDriver(mCallee)->RemoveRefreshObserver(this, Flush_Display);
+    }
+  }
 };
 
 nsPoint
 nsGfxScrollFrameInner::AsyncScroll::PositionAt(TimeStamp aTime) {
   double progressX = mTimingFunctionX.GetSplineValue(ProgressAt(aTime));
   double progressY = mTimingFunctionY.GetSplineValue(ProgressAt(aTime));
   return nsPoint((1 - progressX) * mStartPos.x + progressX * mDestination.x,
                  (1 - progressY) * mStartPos.y + progressY * mDestination.y);
@@ -1590,17 +1636,16 @@ nsGfxScrollFrameInner::~nsGfxScrollFrame
   if (mActivityExpirationState.IsTracked()) {
     gScrollFrameActivityTracker->RemoveObject(this);
   }
   if (gScrollFrameActivityTracker &&
       gScrollFrameActivityTracker->IsEmpty()) {
     delete gScrollFrameActivityTracker;
     gScrollFrameActivityTracker = nsnull;
   }
-  delete mAsyncScroll;
 
   if (mScrollActivityTimer) {
     mScrollActivityTimer->Cancel();
     mScrollActivityTimer = nsnull;
   }
 }
 
 static nscoord
@@ -1617,38 +1662,33 @@ nsPoint
 nsGfxScrollFrameInner::ClampScrollPosition(const nsPoint& aPt) const
 {
   nsRect range = GetScrollRange();
   return nsPoint(Clamp(range.x, aPt.x, range.XMost()),
                  Clamp(range.y, aPt.y, range.YMost()));
 }
 
 /*
- * Callback function from timer used in nsGfxScrollFrameInner::ScrollTo
+ * Callback function from AsyncScroll, used in nsGfxScrollFrameInner::ScrollTo
  */
 void
-nsGfxScrollFrameInner::AsyncScrollCallback(nsITimer *aTimer, void* anInstance)
+nsGfxScrollFrameInner::AsyncScrollCallback(void* anInstance, mozilla::TimeStamp aTime)
 {
   nsGfxScrollFrameInner* self = static_cast<nsGfxScrollFrameInner*>(anInstance);
   if (!self || !self->mAsyncScroll)
     return;
 
   if (self->mAsyncScroll->mIsSmoothScroll) {
-    TimeStamp now = TimeStamp::Now();
-    nsPoint destination = self->mAsyncScroll->PositionAt(now);
-    if (self->mAsyncScroll->IsFinished(now)) {
-      delete self->mAsyncScroll;
+    nsPoint destination = self->mAsyncScroll->PositionAt(aTime);
+    if (self->mAsyncScroll->IsFinished(aTime)) {
       self->mAsyncScroll = nsnull;
     }
-
     self->ScrollToImpl(destination);
   } else {
-    delete self->mAsyncScroll;
     self->mAsyncScroll = nsnull;
-
     self->ScrollToImpl(self->mDestination);
   }
 }
 
 /*
  * this method wraps calls to ScrollToImpl(), either in one shot or incrementally,
  *  based on the setting of the smoothness scroll pref
  */
@@ -1661,17 +1701,16 @@ nsGfxScrollFrameInner::ScrollToWithOrigi
     mDestination = ClampScrollPosition(aScrollPosition);
   } else {
     mDestination = aScrollPosition;
   }
 
   if (aMode == nsIScrollableFrame::INSTANT) {
     // Asynchronous scrolling is not allowed, so we'll kill any existing
     // async-scrolling process and do an instant scroll
-    delete mAsyncScroll;
     mAsyncScroll = nsnull;
     ScrollToImpl(mDestination);
     return;
   }
 
   TimeStamp now = TimeStamp::Now();
   nsPoint currentPosition = GetScrollPosition();
   nsSize currentVelocity(0, 0);
@@ -1680,32 +1719,22 @@ nsGfxScrollFrameInner::ScrollToWithOrigi
 
   if (mAsyncScroll) {
     if (mAsyncScroll->mIsSmoothScroll) {
       currentPosition = mAsyncScroll->PositionAt(now);
       currentVelocity = mAsyncScroll->VelocityAt(now);
     }
   } else {
     mAsyncScroll = new AsyncScroll;
-    mAsyncScroll->mScrollTimer = do_CreateInstance("@mozilla.org/timer;1");
-    if (!mAsyncScroll->mScrollTimer) {
-      delete mAsyncScroll;
+    if (!mAsyncScroll->SetRefreshObserver(this)) {
       mAsyncScroll = nsnull;
-      // allocation failed. Scroll the normal way.
+      // Observer setup failed. Scroll the normal way.
       ScrollToImpl(mDestination);
       return;
     }
-    if (isSmoothScroll) {
-      mAsyncScroll->mScrollTimer->InitWithFuncCallback(
-        AsyncScrollCallback, this, 1000 / 60,
-        nsITimer::TYPE_REPEATING_SLACK);
-    } else {
-      mAsyncScroll->mScrollTimer->InitWithFuncCallback(
-        AsyncScrollCallback, this, 0, nsITimer::TYPE_ONE_SHOT);
-    }
   }
 
   mAsyncScroll->mIsSmoothScroll = isSmoothScroll;
 
   if (isSmoothScroll) {
     mAsyncScroll->InitSmoothScroll(now, currentPosition, currentVelocity,
                                    mDestination, aOrigin);
   }
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -174,17 +174,17 @@ public:
       mScrollPort.XMost() - mScrolledFrame->GetRect().XMost();
     pt.y = mScrollPort.y - mScrolledFrame->GetPosition().y;
     return pt;
   }
   nsRect GetScrollRange() const;
 
   nsPoint RestrictToDevPixels(const nsPoint& aPt, nsIntPoint* aPtDevPx, bool aShouldClamp) const;
   nsPoint ClampScrollPosition(const nsPoint& aPt) const;
-  static void AsyncScrollCallback(nsITimer *aTimer, void* anInstance);
+  static void AsyncScrollCallback(void* anInstance, mozilla::TimeStamp aTime);
   void ScrollTo(nsPoint aScrollPosition, nsIScrollableFrame::ScrollMode aMode) {
     ScrollToWithOrigin(aScrollPosition, aMode, nsGkAtoms::other);
   };
   void ScrollToImpl(nsPoint aScrollPosition);
   void ScrollVisual(nsPoint aOldScrolledFramePosition);
   void ScrollBy(nsIntPoint aDelta, nsIScrollableFrame::ScrollUnit aUnit,
                 nsIScrollableFrame::ScrollMode aMode, nsIntPoint* aOverflow, nsIAtom *aOrigin = nsnull);
   void ScrollToRestoredPosition();
@@ -283,17 +283,17 @@ public:
   nsRevocableEventPtr<AsyncScrollPortEvent> mAsyncScrollPortEvent;
   nsRevocableEventPtr<ScrolledAreaEvent> mScrolledAreaEvent;
   nsIBox* mHScrollbarBox;
   nsIBox* mVScrollbarBox;
   nsIFrame* mScrolledFrame;
   nsIBox* mScrollCornerBox;
   nsIBox* mResizerBox;
   nsContainerFrame* mOuter;
-  AsyncScroll* mAsyncScroll;
+  nsRefPtr<AsyncScroll> mAsyncScroll;
   nsTArray<nsIScrollPositionListener*> mListeners;
   nsRect mScrollPort;
   // Where we're currently scrolling to, if we're scrolling asynchronously.
   // If we're not in the middle of an asynchronous scroll then this is
   // just the current scroll position. ScrollBy will choose its
   // destination based on this value.
   nsPoint mDestination;
   nsPoint mScrollPosAtLastPaint;