Bug 1042974 - Dispatch flings to the right scrollable element in the presence of scrollgrab. r=kats
authorBotond Ballo <botond@mozilla.com>
Mon, 18 Aug 2014 16:12:43 -0400
changeset 200711 b2326bbb0c28b91a38e40775d23761c47fd8ee5a
parent 200710 ac59c5f851dc2dab526d28e59384e450b42ae2aa
child 200712 6797cbbf30d777521af57f96d457a3c06996bb0a
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerskats
bugs1042974
milestone34.0a1
Bug 1042974 - Dispatch flings to the right scrollable element in the presence of scrollgrab. r=kats
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/APZCTreeManager.h
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
gfx/layers/apz/src/OverscrollHandoffChain.cpp
gfx/layers/apz/src/OverscrollHandoffChain.h
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -885,52 +885,62 @@ APZCTreeManager::DispatchScroll(AsyncPan
 
   // Scroll |next|. If this causes overscroll, it will call DispatchScroll()
   // again with an incremented index.
   return next->AttemptScroll(aStartPoint, aEndPoint, aOverscrollHandoffChain,
       aOverscrollHandoffChainIndex);
 }
 
 bool
-APZCTreeManager::HandOffFling(AsyncPanZoomController* aPrev, ScreenPoint aVelocity,
-                              nsRefPtr<const OverscrollHandoffChain> aOverscrollHandoffChain)
+APZCTreeManager::DispatchFling(AsyncPanZoomController* aPrev,
+                               ScreenPoint aVelocity,
+                               nsRefPtr<const OverscrollHandoffChain> aOverscrollHandoffChain,
+                               bool aHandoff)
 {
-  // Find |aPrev| in the handoff chain.
-  uint32_t i;
-  for (i = 0; i < aOverscrollHandoffChain->Length(); ++i) {
-    if (aOverscrollHandoffChain->GetApzcAtIndex(i) == aPrev) {
-      break;
+  nsRefPtr<AsyncPanZoomController> next;
+
+  // If the fling is being handed off, give it to the next APZC in the
+  // handoff chain after |aPrev|.
+  if (aHandoff) {
+    // Find |aPrev| in the handoff chain.
+    uint32_t i = aOverscrollHandoffChain->IndexOf(aPrev);
+
+    // Get the next APZC in the handoff chain, if any.
+    if (i + 1 < aOverscrollHandoffChain->Length()) {
+      next = aOverscrollHandoffChain->GetApzcAtIndex(i + 1);
     }
-  }
-
-  // Get the next APZC in the handoff chain, if any.
-  nsRefPtr<AsyncPanZoomController> next;
-  if (i + 1 < aOverscrollHandoffChain->Length()) {
-    next = aOverscrollHandoffChain->GetApzcAtIndex(i + 1);
+  // If the fling is being started, give it to the first APZC in the
+  // handoff chain.
+  } else {
+    if (aOverscrollHandoffChain->Length() != 0) {
+      next = aOverscrollHandoffChain->GetApzcAtIndex(0);
+    }
   }
 
   // Nothing to hand off fling to.
   if (next == nullptr || next->IsDestroyed()) {
     return false;
   }
 
   // The fling's velocity needs to be transformed from the screen coordinates
   // of |aPrev| to the screen coordinates of |next|. To transform a velocity
   // correctly, we need to convert it to a displacement. For now, we do this
   // by anchoring it to a start point of (0, 0).
   // TODO: For this to be correct in the presence of 3D transforms, we should
   // use the end point of the touch that started the fling as the start point
   // rather than (0, 0).
   ScreenPoint startPoint;  // (0, 0)
   ScreenPoint endPoint = startPoint + aVelocity;
-  TransformDisplacement(this, aPrev, next, startPoint, endPoint);
+  if (aPrev != next) {
+    TransformDisplacement(this, aPrev, next, startPoint, endPoint);
+  }
   ScreenPoint transformedVelocity = endPoint - startPoint;
 
   // Tell |next| to start a fling with the transformed velocity.
-  return next->TakeOverFling(transformedVelocity, aOverscrollHandoffChain);
+  return next->AttemptFling(transformedVelocity, aOverscrollHandoffChain, aHandoff);
 }
 
 bool
 APZCTreeManager::HitTestAPZC(const ScreenIntPoint& aPoint)
 {
   nsRefPtr<AsyncPanZoomController> target = GetTargetAPZC(aPoint, nullptr);
   return target != nullptr;
 }
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -293,36 +293,49 @@ public:
    *   - B.AttemptScroll() scrolls B. If there is overscroll, it calls TM.DispatchScroll() with index = 1.
    *   - TM.DispatchScroll() calls C.AttemptScroll() (since C is at index 1 in the chain)
    *   - C.AttemptScroll() scrolls C. If there is overscroll, it calls TM.DispatchScroll() with index = 2.
    *   - TM.DispatchScroll() calls A.AttemptScroll() (since A is at index 2 in the chain)
    *   - A.AttemptScroll() scrolls A. If there is overscroll, it calls TM.DispatchScroll() with index = 3.
    *   - TM.DispatchScroll() discards the rest of the scroll as there are no more elements in the chain.
    *
    * Note: this should be used for panning only. For handing off overscroll for
-   *       a fling, use HandOffFling().
+   *       a fling, use DispatchFling().
    */
   bool DispatchScroll(AsyncPanZoomController* aApzc,
                       ScreenPoint aStartPoint,
                       ScreenPoint aEndPoint,
                       const OverscrollHandoffChain& aOverscrollHandoffChain,
                       uint32_t aOverscrollHandoffChainIndex);
 
   /**
    * This is a callback for AsyncPanZoomController to call when it wants to
-   * hand off overscroll from a fling.
-   * @param aApzc the APZC that is handing off the fling
+   * start a fling in response to a touch-end event, or when it needs to hand
+   * off a fling to the next APZC. Note that because of scroll grabbing, the
+   * first APZC to fling may not be the one that is receiving the touch events.
+   *
+   * @param aApzc the APZC that wants to start or hand off the fling
    * @param aVelocity the current velocity of the fling, in |aApzc|'s screen
    *                  pixels per millisecond
-   * Returns true iff. another APZC accepted the handed-off fling. The caller
-   * (|aApzc|) uses this return value to determine whether it should consume
+   * @param aOverscrollHandoffChain the chain of APZCs along which the fling
+   *                                should be handed off
+   * @param aHandoff is true if |aApzc| is handing off an existing fling (in
+   *                 this case the fling is given to the next APZC in the
+   *                 handoff chain after |aApzc|), and false is |aApzc| wants
+   *                 start a fling (in this case the fling is given to the
+   *                 first APZC in the chain)
+   *
+   * Returns true iff. an APZC accepted the fling. In the case of fling handoff,
+   * the caller uses this return value to determine whether it should consume
    * the excess fling itself by going into an overscroll fling.
    */
-  bool HandOffFling(AsyncPanZoomController* aApzc, ScreenPoint aVelocity,
-                    nsRefPtr<const OverscrollHandoffChain> aOverscrollHandoffChain);
+  bool DispatchFling(AsyncPanZoomController* aApzc,
+                     ScreenPoint aVelocity,
+                     nsRefPtr<const OverscrollHandoffChain> aOverscrollHandoffChain,
+                     bool aHandoff);
 
   void SnapBackOverscrolledApzc(AsyncPanZoomController* aStart);
 
   /*
    * Build the chain of APZCs that will handle overscroll for a pan starting at |aInitialTarget|.
    */
   nsRefPtr<const OverscrollHandoffChain> BuildOverscrollHandoffChain(const nsRefPtr<AsyncPanZoomController>& aInitialTarget);
 protected:
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -424,19 +424,19 @@ public:
     MOZ_ASSERT(mOverscrollHandoffChain);
     TimeStamp now = GetFrameTime();
     ScreenPoint velocity(mApzc.mX.GetVelocity(), mApzc.mY.GetVelocity());
 
     // If the last fling was very recent and in the same direction as this one,
     // boost the velocity to be the sum of the two. Check separate axes separately
     // because we could have two vertical flings with small horizontal components
     // on the opposite side of zero, and we still want the y-fling to get accelerated.
-    // Note that the acceleration code is only applied on the APZC that receives the
-    // actual touch event; the accelerated velocities are then handed off using the
-    // normal HandOffFling codepath.
+    // Note that the acceleration code is only applied on the APZC that initiates
+    // the fling; the accelerated velocities are then handed off using the
+    // normal DispatchFling codepath.
     if (aApplyAcceleration && !mApzc.mLastFlingTime.IsNull()
         && (now - mApzc.mLastFlingTime).ToMilliseconds() < gfxPrefs::APZFlingAccelInterval()) {
       if (SameDirection(velocity.x, mApzc.mLastFlingVelocity.x)) {
         velocity.x = Accelerate(velocity.x, mApzc.mLastFlingVelocity.x);
         APZC_LOG("%p Applying fling x-acceleration from %f to %f (delta %f)\n",
                  &mApzc, mApzc.mX.GetVelocity(), velocity.x, mApzc.mLastFlingVelocity.x);
         mApzc.mX.SetVelocity(velocity.x);
       }
@@ -1142,27 +1142,41 @@ nsEventStatus AsyncPanZoomController::On
   case CROSS_SLIDING_X:
   case CROSS_SLIDING_Y:
     SetState(NOTHING);
     return nsEventStatus_eIgnore;
 
   case PANNING:
   case PANNING_LOCKED_X:
   case PANNING_LOCKED_Y:
+  {
     CurrentTouchBlock()->GetOverscrollHandoffChain()->FlushRepaints();
     mX.EndTouch(aEvent.mTime);
     mY.EndTouch(aEvent.mTime);
-    SetState(FLING);
+    ScreenPoint flingVelocity(mX.GetVelocity(), mY.GetVelocity());
+    // Clear our velocities; if DispatchFling() gives the fling to us,
+    // the fling velocity gets *added* to our existing velocity in
+    // AcceptFling().
+    mX.SetVelocity(0);
+    mY.SetVelocity(0);
+    // Clear our state so that we don't stay in the PANNING state
+    // if DispatchFling() gives the fling to somone else.
+    SetState(NOTHING);
     APZC_LOG("%p starting a fling animation\n", this);
-    StartAnimation(new FlingAnimation(*this,
+    // Make a local copy of the tree manager pointer and check that it's not
+    // null before calling DispatchFling(). This is necessary because Destroy(),
+    // which nulls out mTreeManager, could be called concurrently.
+    if (APZCTreeManager* treeManagerLocal = mTreeManager) {
+      treeManagerLocal->DispatchFling(this,
+                                      flingVelocity,
                                       CurrentTouchBlock()->GetOverscrollHandoffChain(),
-                                      true  /* apply acceleration */,
-                                      false /* allow overscroll */));
+                                      false /* not handoff */);
+    }
     return nsEventStatus_eConsumeNoDefault;
-
+  }
   case PINCHING:
     SetState(NOTHING);
     // Scale gesture listener should have handled this.
     NS_WARNING("Gesture listener should have handled pinching in OnTouchEnd.");
     return nsEventStatus_eIgnore;
 
   case SNAP_BACK:  // Should not receive a touch-move in the SNAP_BACK state
                    // as touch blocks that begin in an overscrolled state
@@ -1760,53 +1774,67 @@ nsRefPtr<const OverscrollHandoffChain> A
     OverscrollHandoffChain* result = new OverscrollHandoffChain;
     result->Add(this);
     return result;
   }
 }
 
 void AsyncPanZoomController::AcceptFling(const ScreenPoint& aVelocity,
                                          const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
+                                         bool aHandoff,
                                          bool aAllowOverscroll) {
   // We may have a pre-existing velocity for whatever reason (for example,
   // a previously handed off fling). We don't want to clobber that.
   mX.SetVelocity(mX.GetVelocity() + aVelocity.x);
   mY.SetVelocity(mY.GetVelocity() + aVelocity.y);
   SetState(FLING);
   StartAnimation(new FlingAnimation(*this,
-                                    aOverscrollHandoffChain,
-                                    false /* no acceleration */,
-                                    aAllowOverscroll));
+      aOverscrollHandoffChain,
+      !aHandoff,  // only apply acceleration if this is an initial fling
+      aAllowOverscroll));
 }
 
-bool AsyncPanZoomController::TakeOverFling(ScreenPoint aVelocity,
-                                           const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain) {
+bool AsyncPanZoomController::AttemptFling(ScreenPoint aVelocity,
+                                          const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
+                                          bool aHandoff) {
   // If we are pannable, take over the fling ourselves.
   if (IsPannable()) {
-    AcceptFling(aVelocity, aOverscrollHandoffChain, false /* do not allow overscroll */);
+    AcceptFling(aVelocity,
+                aOverscrollHandoffChain,
+                aHandoff,
+                false /* do not allow overscroll */);
     return true;
   }
 
   // Otherwise, hand the fling back to the tree manager to pass on to the
   // next APZC in the handoff chain. Had we started a fling animation in this
   // APZC, we would have done this hand-off on its first frame anyways, but
   // doing it here allows the tree manager to tell the previous APZC to enter
   // an overscroll fling if nothing further in the chain wants the fling.
   APZCTreeManager* treeManagerLocal = mTreeManager;
   return treeManagerLocal
-      && treeManagerLocal->HandOffFling(this, aVelocity, aOverscrollHandoffChain);
+      && treeManagerLocal->DispatchFling(this,
+                                         aVelocity,
+                                         aOverscrollHandoffChain,
+                                         true /* handoff */);
 }
 
 void AsyncPanZoomController::HandleFlingOverscroll(const ScreenPoint& aVelocity,
                                                    const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain) {
   APZCTreeManager* treeManagerLocal = mTreeManager;
-  if (!(treeManagerLocal && treeManagerLocal->HandOffFling(this, aVelocity, aOverscrollHandoffChain))) {
+  if (!(treeManagerLocal && treeManagerLocal->DispatchFling(this,
+                                                            aVelocity,
+                                                            aOverscrollHandoffChain,
+                                                            true /* handoff */))) {
     // No one wanted the fling, so we enter into an overscroll fling ourselves.
     if (IsPannable()) {
-      AcceptFling(aVelocity, aOverscrollHandoffChain, true /* allow overscroll */);
+      AcceptFling(aVelocity,
+                  aOverscrollHandoffChain,
+                  true /* handoff */,
+                  true /* allow overscroll */);
     }
   }
 }
 
 void AsyncPanZoomController::StartSnapBack() {
   SetState(SNAP_BACK);
   StartAnimation(new OverscrollSnapBackAnimation(*this));
 }
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -796,24 +796,27 @@ private:
 
 
   /* ===================================================================
    * The functions and members in this section are used to manage
    * fling animations and handling overscroll during a fling.
    */
 public:
   /**
-   * Take over a fling with the given velocity from another APZC. Used for
-   * during overscroll handoff for a fling. If we are not pannable, calls
-   * mTreeManager->HandOffFling() to hand the fling off further.
-   * Returns true iff. any APZC (whether this one or one further in the handoff
-   * chain accepted the fling).
+   * Attempt a fling with the given velocity. If we are not pannable, tehe fling
+   * is handed off to the next APZC in the handoff chain via
+   * mTreeManager->DspatchFling(). Returns true iff. any APZC (whether this
+   * one or one further in the handoff chain) accepted the fling.
+   * |aHandoff| should be true iff. the fling was handed off from a previous
+   *            APZC, and determines whether acceleration is applied to the
+   *            fling.
    */
-  bool TakeOverFling(ScreenPoint aVelocity,
-                     const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain);
+  bool AttemptFling(ScreenPoint aVelocity,
+                    const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
+                    bool aHandoff);
 
 private:
   friend class FlingAnimation;
   friend class OverscrollSnapBackAnimation;
   // The initial velocity of the most recent fling.
   ScreenPoint mLastFlingVelocity;
   // The time at which the most recent fling started.
   TimeStamp mLastFlingTime;
@@ -824,16 +827,17 @@ private:
   // later in the handoff chain, or if there are no takers, continuing the
   // fling and entering an overscrolled state.
   void HandleFlingOverscroll(const ScreenPoint& aVelocity,
                              const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain);
 
   // Helper function used by TakeOverFling() and HandleFlingOverscroll().
   void AcceptFling(const ScreenPoint& aVelocity,
                    const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
+                   bool aHandoff,
                    bool aAllowOverscroll);
 
   // Start a snap-back animation to relieve overscroll.
   void StartSnapBack();
 
 
   /* ===================================================================
    * The functions and members in this section are used to build a tree
--- a/gfx/layers/apz/src/OverscrollHandoffChain.cpp
+++ b/gfx/layers/apz/src/OverscrollHandoffChain.cpp
@@ -43,30 +43,42 @@ OverscrollHandoffChain::SortByScrollPrio
 
 const nsRefPtr<AsyncPanZoomController>&
 OverscrollHandoffChain::GetApzcAtIndex(uint32_t aIndex) const
 {
   MOZ_ASSERT(aIndex < Length());
   return mChain[aIndex];
 }
 
+uint32_t
+OverscrollHandoffChain::IndexOf(const AsyncPanZoomController* aApzc) const
+{
+  uint32_t i;
+  for (i = 0; i < Length(); ++i) {
+    if (mChain[i] == aApzc) {
+      break;
+    }
+  }
+  return i;
+}
+
 void
 OverscrollHandoffChain::FlushRepaints() const
 {
   MOZ_ASSERT(Length() > 0);
-  for (uint32_t i = 0; i < Length(); i++) {
+  for (uint32_t i = 0; i < Length(); ++i) {
     mChain[i]->FlushRepaintForOverscrollHandoff();
   }
 }
 
 void
 OverscrollHandoffChain::CancelAnimations() const
 {
   MOZ_ASSERT(Length() > 0);
-  for (uint32_t i = 0; i < Length(); i++) {
+  for (uint32_t i = 0; i < Length(); ++i) {
     mChain[i]->CancelAnimation();
   }
 }
 
 void
 OverscrollHandoffChain::SnapBackOverscrolledApzc() const
 {
   for (uint32_t i = 0; i < Length(); ++i) {
@@ -77,22 +89,17 @@ OverscrollHandoffChain::SnapBackOverscro
     }
   }
 }
 
 bool
 OverscrollHandoffChain::CanBePanned(const AsyncPanZoomController* aApzc) const
 {
   // Find |aApzc| in the handoff chain.
-  uint32_t i;
-  for (i = 0; i < Length(); ++i) {
-    if (mChain[i] == aApzc) {
-      break;
-    }
-  }
+  uint32_t i = IndexOf(aApzc);
 
   // See whether any APZC in the handoff chain starting from |aApzc|
   // has room to be panned.
   for (uint32_t j = i; j < Length(); ++j) {
     if (mChain[j]->IsPannable()) {
       return true;
     }
   }
--- a/gfx/layers/apz/src/OverscrollHandoffChain.h
+++ b/gfx/layers/apz/src/OverscrollHandoffChain.h
@@ -76,16 +76,18 @@ public:
   void Add(AsyncPanZoomController* aApzc);
   void SortByScrollPriority();
 
   /*
    * Methods for accessing the handoff chain.
    */
   uint32_t Length() const { return mChain.size(); }
   const nsRefPtr<AsyncPanZoomController>& GetApzcAtIndex(uint32_t aIndex) const;
+  // Returns Length() if |aApzc| is not on this chain.
+  uint32_t IndexOf(const AsyncPanZoomController* aApzc) const;
 
   /*
    * Convenience methods for performing operations on APZCs in the chain.
    */
 
   // Flush repaints all the way up the chain.
   void FlushRepaints() const;