Bug 1201098 - Consume fling and overscroll velocity per-axis. r=botond
authorChris Lord <chrislord.net@gmail.com>
Wed, 16 Sep 2015 01:49:00 +0200
changeset 295645 5b9167138480b526b4c061919fecfd906dcadb7a
parent 295644 37c0c6cd603a20b69a62c6e99cfd6a902d20ba5d
child 295646 5e26aa247c9a292f5dc7003fb7ca2fd615e8a8c8
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond
bugs1201098
milestone43.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 1201098 - Consume fling and overscroll velocity per-axis. r=botond
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/OverscrollHandoffState.cpp
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1222,16 +1222,20 @@ APZCTreeManager::GetRootNode() const
  * @return true on success, false if aStartPoint or aEndPoint cannot be transformed into target's coordinate space
  */
 static bool
 TransformDisplacement(APZCTreeManager* aTreeManager,
                       AsyncPanZoomController* aSource,
                       AsyncPanZoomController* aTarget,
                       ParentLayerPoint& aStartPoint,
                       ParentLayerPoint& aEndPoint) {
+  if (aSource == aTarget) {
+    return true;
+  }
+
   // Convert start and end points to Screen coordinates.
   Matrix4x4 untransformToApzc = aTreeManager->GetScreenToApzcTransform(aSource).Inverse();
   ScreenPoint screenStart = TransformTo<ScreenPixel>(untransformToApzc, aStartPoint);
   ScreenPoint screenEnd = TransformTo<ScreenPixel>(untransformToApzc, aEndPoint);
 
 
   // Convert start and end points to aTarget's ParentLayer coordinates.
   Matrix4x4 transformToApzc = aTreeManager->GetScreenToApzcTransform(aTarget);
@@ -1241,118 +1245,137 @@ TransformDisplacement(APZCTreeManager* a
     return false;
   }
   aEndPoint = *endPoint;
   aStartPoint = *startPoint;
 
   return true;
 }
 
-bool
+void
 APZCTreeManager::DispatchScroll(AsyncPanZoomController* aPrev,
-                                ParentLayerPoint aStartPoint,
-                                ParentLayerPoint aEndPoint,
+                                ParentLayerPoint& aStartPoint,
+                                ParentLayerPoint& aEndPoint,
                                 OverscrollHandoffState& aOverscrollHandoffState)
 {
   const OverscrollHandoffChain& overscrollHandoffChain = aOverscrollHandoffState.mChain;
   uint32_t overscrollHandoffChainIndex = aOverscrollHandoffState.mChainIndex;
   nsRefPtr<AsyncPanZoomController> next;
   // If we have reached the end of the overscroll handoff chain, there is
   // nothing more to scroll, so we ignore the rest of the pan gesture.
   if (overscrollHandoffChainIndex >= overscrollHandoffChain.Length()) {
     // Nothing more to scroll - ignore the rest of the pan gesture.
-    return false;
+    return;
   }
 
   next = overscrollHandoffChain.GetApzcAtIndex(overscrollHandoffChainIndex);
 
   if (next == nullptr || next->IsDestroyed()) {
-    return false;
+    return;
   }
 
   // Convert the start and end points from |aPrev|'s coordinate space to
-  // |next|'s coordinate space. Since |aPrev| may be the same as |next|
-  // (if |aPrev| is the APZC that is initiating the scroll and there is no
-  // scroll grabbing to grab the scroll from it), don't bother doing the
-  // transformations in that case.
-  if (next != aPrev) {
-    if (!TransformDisplacement(this, aPrev, next, aStartPoint, aEndPoint)) {
-      return false;
-    }
+  // |next|'s coordinate space.
+  if (!TransformDisplacement(this, aPrev, next, aStartPoint, aEndPoint)) {
+    return;
   }
 
   // Scroll |next|. If this causes overscroll, it will call DispatchScroll()
   // again with an incremented index.
-  return next->AttemptScroll(aStartPoint, aEndPoint, aOverscrollHandoffState);
+  if (!next->AttemptScroll(aStartPoint, aEndPoint, aOverscrollHandoffState)) {
+    // Transform |aStartPoint| and |aEndPoint| (which now represent the
+    // portion of the displacement that wasn't consumed by APZCs later
+    // in the handoff chain) back into |aPrev|'s coordinate space. This
+    // allows the caller (which is |aPrev|) to interpret the unconsumed
+    // displacement in its own coordinate space, and make use of it
+    // (e.g. by going into overscroll).
+    if (!TransformDisplacement(this, next, aPrev, aStartPoint, aEndPoint)) {
+      NS_WARNING("Failed to untransform scroll points during dispatch");
+    }
+  }
 }
 
-bool
+void
 APZCTreeManager::DispatchFling(AsyncPanZoomController* aPrev,
-                               ParentLayerPoint aVelocity,
+                               ParentLayerPoint& aVelocity,
                                nsRefPtr<const OverscrollHandoffChain> aOverscrollHandoffChain,
                                bool aHandoff)
 {
   nsRefPtr<AsyncPanZoomController> current;
   uint32_t aOverscrollHandoffChainLength = aOverscrollHandoffChain->Length();
   uint32_t startIndex;
 
   // 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).
   ParentLayerPoint startPoint;  // (0, 0)
   ParentLayerPoint endPoint;
-  ParentLayerPoint transformedVelocity = aVelocity;
+  ParentLayerPoint usedTransformedVelocity = aVelocity;
 
   if (aHandoff) {
     startIndex = aOverscrollHandoffChain->IndexOf(aPrev) + 1;
 
     // IndexOf will return aOverscrollHandoffChain->Length() if
     // |aPrev| is not found.
     if (startIndex >= aOverscrollHandoffChainLength) {
-      return false;
+      return;
     }
   } else {
     startIndex = 0;
   }
 
   for (; startIndex < aOverscrollHandoffChainLength; startIndex++) {
     current = aOverscrollHandoffChain->GetApzcAtIndex(startIndex);
 
     // Make sure the apcz about to be handled can be handled
     if (current == nullptr || current->IsDestroyed()) {
-      return false;
+      return;
     }
 
-    endPoint = startPoint + transformedVelocity;
+    endPoint = startPoint + usedTransformedVelocity;
 
     // Only transform when current apcz can be transformed with previous
     if (startIndex > 0) {
       if (!TransformDisplacement(this,
                             aOverscrollHandoffChain->GetApzcAtIndex(startIndex - 1),
                             current,
                             startPoint,
                             endPoint)) {
-          return false;
+        return;
       }
     }
 
-    transformedVelocity = endPoint - startPoint;
+    ParentLayerPoint transformedVelocity = endPoint - startPoint;
+    usedTransformedVelocity = transformedVelocity;
 
-    if (current->AttemptFling(transformedVelocity,
+    if (current->AttemptFling(usedTransformedVelocity,
                               aOverscrollHandoffChain,
                               aHandoff)) {
-      return true;
+      if (IsZero(usedTransformedVelocity)) {
+        aVelocity = ParentLayerPoint();
+        return;
+      }
+
+      // Subtract the proportion of used velocity from aVelocity
+      if (!FuzzyEqualsAdditive(transformedVelocity.x,
+                               usedTransformedVelocity.x, COORDINATE_EPSILON)) {
+        aVelocity.x = aVelocity.x *
+          (usedTransformedVelocity.x / transformedVelocity.x);
+      }
+      if (!FuzzyEqualsAdditive(transformedVelocity.y,
+                               usedTransformedVelocity.y, COORDINATE_EPSILON)) {
+        aVelocity.y = aVelocity.y *
+          (usedTransformedVelocity.y / transformedVelocity.y);
+      }
     }
   }
-
-  return false;
 }
 
 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
@@ -322,19 +322,20 @@ public:
    *   displacement between different points in transformed coordinates can
    *   represent different displacements in untransformed coordinates.
    * |aOverscrollHandoffChain| is the overscroll handoff chain used for
    *   determining the order in which scroll should be handed off between
    *   APZCs
    * |aOverscrollHandoffChainIndex| is the next position in the overscroll
    *   handoff chain that should be scrolled.
    *
-   * Returns true iff. some APZC accepted the scroll and scrolled.
-   * This is to allow the sending APZC to go into an overscrolled state if
-   * no APZC further up in the handoff chain accepted the overscroll.
+   * aStartPoint and aEndPoint will be modified depending on how much of the
+   * scroll each APZC consumes. This is to allow the sending APZC to go into
+   * an overscrolled state if no APZC further up in the handoff chain accepted
+   * the entire scroll.
    *
    * The way this method works is best illustrated with an example.
    * Consider three nested APZCs, A, B, and C, with C being the innermost one.
    * Say B is scroll-grabbing.
    * The touch events go to C because it's the innermost one (so e.g. taps
    * should go through C), but the overscroll handoff chain is B -> C -> A
    * because B is scroll-grabbing.
    * For convenience I'll refer to the three APZC objects as A, B, and C, and
@@ -347,19 +348,19 @@ public:
    *   - 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 DispatchFling().
    */
-  bool DispatchScroll(AsyncPanZoomController* aApzc,
-                      ParentLayerPoint aStartPoint,
-                      ParentLayerPoint aEndPoint,
+  void DispatchScroll(AsyncPanZoomController* aApzc,
+                      ParentLayerPoint& aStartPoint,
+                      ParentLayerPoint& aEndPoint,
                       OverscrollHandoffState& aOverscrollHandoffState);
 
   /**
    * This is a callback for AsyncPanZoomController to call when it wants to
    * 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.
    *
@@ -369,22 +370,23 @@ public:
    * @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.
+   * aVelocity will be modified depending on how much of that velocity has
+   * been consumed by APZCs in the overscroll hand-off chain. The caller can
+   * use this value to determine whether it should consume the excess velocity
+   * by going into an overscroll fling.
    */
-  bool DispatchFling(AsyncPanZoomController* aApzc,
-                     ParentLayerPoint aVelocity,
+  void DispatchFling(AsyncPanZoomController* aApzc,
+                     ParentLayerPoint& aVelocity,
                      nsRefPtr<const OverscrollHandoffChain> aOverscrollHandoffChain,
                      bool aHandoff);
 
   /*
    * Build the chain of APZCs that will handle overscroll for a pan starting at |aInitialTarget|.
    */
   nsRefPtr<const OverscrollHandoffChain> BuildOverscrollHandoffChain(const nsRefPtr<AsyncPanZoomController>& aInitialTarget);
 
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1573,19 +1573,19 @@ nsEventStatus AsyncPanZoomController::On
 
       CancelAnimation();
       SetState(WHEEL_SCROLL);
 
       OverscrollHandoffState handoffState(
           *mInputQueue->CurrentWheelBlock()->GetOverscrollHandoffChain(),
           distance,
           ScrollSource::Wheel);
-      CallDispatchScroll(aEvent.mLocalOrigin,
-                         aEvent.mLocalOrigin - delta,
-                         handoffState);
+      ParentLayerPoint startPoint = aEvent.mLocalOrigin;
+      ParentLayerPoint endPoint = aEvent.mLocalOrigin - delta;
+      CallDispatchScroll(startPoint, endPoint, handoffState);
 
       SetState(NOTHING);
       RequestContentRepaint();
 
       break;
     }
 
     case ScrollWheelInput::SCROLLMODE_SMOOTH: {
@@ -1721,19 +1721,19 @@ nsEventStatus AsyncPanZoomController::On
 
   HandlePanningUpdate(aEvent.mPanDisplacement);
 
   ScreenPoint panDistance(fabs(aEvent.mPanDisplacement.x), fabs(aEvent.mPanDisplacement.y));
   OverscrollHandoffState handoffState(
       *CurrentPanGestureBlock()->GetOverscrollHandoffChain(),
       panDistance,
       ScrollSource::Wheel);
-  CallDispatchScroll(aEvent.mLocalPanStartPoint,
-                     aEvent.mLocalPanStartPoint + aEvent.mLocalPanDisplacement,
-                     handoffState);
+  ParentLayerPoint startPoint = aEvent.mLocalPanStartPoint;
+  ParentLayerPoint endPoint = aEvent.mLocalPanStartPoint + aEvent.mLocalPanDisplacement;
+  CallDispatchScroll(startPoint, endPoint, handoffState);
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnPanEnd(const PanGestureInput& aEvent) {
   APZC_LOG("%p got a pan-end in state %d\n", this, mState);
 
   // Call into OnPan in order to process any delta included in this event.
@@ -2062,18 +2062,18 @@ nsEventStatus AsyncPanZoomController::St
 }
 
 void AsyncPanZoomController::UpdateWithTouchAtDevicePoint(const MultiTouchInput& aEvent) {
   ParentLayerPoint point = GetFirstTouchPoint(aEvent);
   mX.UpdateWithTouchAtDevicePoint(point.x, 0, aEvent.mTime);
   mY.UpdateWithTouchAtDevicePoint(point.y, 0, aEvent.mTime);
 }
 
-bool AsyncPanZoomController::AttemptScroll(const ParentLayerPoint& aStartPoint,
-                                           const ParentLayerPoint& aEndPoint,
+bool AsyncPanZoomController::AttemptScroll(ParentLayerPoint& aStartPoint,
+                                           ParentLayerPoint& aEndPoint,
                                            OverscrollHandoffState& aOverscrollHandoffState) {
 
   // "start - end" rather than "end - start" because e.g. moving your finger
   // down (*positive* direction along y axis) causes the vertical scroll offset
   // to *decrease* as the page follows your finger.
   ParentLayerPoint displacement = aStartPoint - aEndPoint;
 
   ParentLayerPoint overscroll;  // will be used outside monitor block
@@ -2094,103 +2094,124 @@ bool AsyncPanZoomController::AttemptScro
 
     if (!IsZero(adjustedDisplacement)) {
       ScrollBy(adjustedDisplacement / mFrameMetrics.GetZoom());
       ScheduleCompositeAndMaybeRepaint();
       UpdateSharedCompositorFrameMetrics();
     }
   }
 
+  // Adjust the start point to reflect the consumed portion of the scroll.
+  aStartPoint = aEndPoint + overscroll;
+
   // If we consumed the entire displacement as a normal scroll, great.
   if (IsZero(overscroll)) {
     return true;
   }
 
   if (AllowScrollHandoffInCurrentBlock()) {
     // If there is overscroll, first try to hand it off to an APZC later
     // in the handoff chain to consume (either as a normal scroll or as
     // overscroll).
     // Note: "+ overscroll" rather than "- overscroll" because "overscroll"
     // is what's left of "displacement", and "displacement" is "start - end".
     ++aOverscrollHandoffState.mChainIndex;
-    if (CallDispatchScroll(aEndPoint + overscroll, aEndPoint,
-                           aOverscrollHandoffState)) {
+    CallDispatchScroll(aStartPoint, aEndPoint, aOverscrollHandoffState);
+
+    overscroll = aStartPoint - aEndPoint;
+    if (IsZero(overscroll)) {
       return true;
     }
   }
 
   // If there is no APZC later in the handoff chain that accepted the
   // overscroll, try to accept it ourselves. We only accept it if we
   // are pannable.
   APZC_LOG("%p taking overscroll during panning\n", this);
-  return OverscrollForPanning(overscroll, aOverscrollHandoffState.mPanDistance);
+  OverscrollForPanning(overscroll, aOverscrollHandoffState.mPanDistance);
+  aStartPoint = aEndPoint + overscroll;
+
+  return IsZero(overscroll);
 }
 
-bool AsyncPanZoomController::OverscrollForPanning(ParentLayerPoint aOverscroll,
+void AsyncPanZoomController::OverscrollForPanning(ParentLayerPoint& aOverscroll,
                                                   const ScreenPoint& aPanDistance) {
   // Only allow entering overscroll along an axis if the pan distance along
   // that axis is greater than the pan distance along the other axis by a
   // configurable factor. If we are already overscrolled, don't check this.
   if (!IsOverscrolled()) {
     if (aPanDistance.x < gfxPrefs::APZMinPanDistanceRatio() * aPanDistance.y) {
       aOverscroll.x = 0;
     }
     if (aPanDistance.y < gfxPrefs::APZMinPanDistanceRatio() * aPanDistance.x) {
       aOverscroll.y = 0;
     }
   }
 
-  return OverscrollBy(aOverscroll);
+  OverscrollBy(aOverscroll);
 }
 
-bool AsyncPanZoomController::OverscrollBy(const ParentLayerPoint& aOverscroll) {
+void AsyncPanZoomController::OverscrollBy(ParentLayerPoint& aOverscroll) {
   if (!gfxPrefs::APZOverscrollEnabled()) {
-    return false;
+    return;
   }
 
   ReentrantMonitorAutoEnter lock(mMonitor);
   // Do not go into overscroll in a direction in which we have no room to
   // scroll to begin with.
   bool xCanScroll = mX.CanScroll();
   bool yCanScroll = mY.CanScroll();
-  if (xCanScroll) {
+
+  bool xConsumed = FuzzyEqualsAdditive(aOverscroll.x, 0.0f, COORDINATE_EPSILON);
+  if (xCanScroll && !xConsumed) {
     mX.OverscrollBy(aOverscroll.x);
-  }
-  if (yCanScroll) {
-    mY.OverscrollBy(aOverscroll.y);
+    aOverscroll.x = 0;
+    xConsumed = true;
   }
-  if (xCanScroll || yCanScroll) {
-    ScheduleComposite();
-    return true;
+
+  bool yConsumed = FuzzyEqualsAdditive(aOverscroll.y, 0.0f, COORDINATE_EPSILON);
+  if (yCanScroll && !yConsumed) {
+    mY.OverscrollBy(aOverscroll.y);
+    aOverscroll.y = 0;
+    yConsumed = true;
   }
-  // TODO(botond): If one of the x- or y-overscroll was not accepted, we
-  // may want to propagate that one to an APZC earlier in the handoff chain.
-  return false;
+
+  if ((xCanScroll && xConsumed) || (yCanScroll && yConsumed)) {
+    ScheduleComposite();
+  }
 }
 
 nsRefPtr<const OverscrollHandoffChain> AsyncPanZoomController::BuildOverscrollHandoffChain() {
   if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
     return treeManagerLocal->BuildOverscrollHandoffChain(this);
   }
 
   // This APZC IsDestroyed(). To avoid callers having to special-case this
   // scenario, just build a 1-element chain containing ourselves.
   OverscrollHandoffChain* result = new OverscrollHandoffChain;
   result->Add(this);
   return result;
 }
 
-void AsyncPanZoomController::AcceptFling(const ParentLayerPoint& aVelocity,
+void AsyncPanZoomController::AcceptFling(ParentLayerPoint& aVelocity,
                                          const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                                          bool aHandoff) {
+  ReentrantMonitorAutoEnter lock(mMonitor);
+
   // We may have a pre-existing velocity for whatever reason (for example,
   // a previously handed off fling). We don't want to clobber that.
   APZC_LOG("%p accepting fling with velocity %s\n", this, Stringify(aVelocity).c_str());
-  mX.SetVelocity(mX.GetVelocity() + aVelocity.x);
-  mY.SetVelocity(mY.GetVelocity() + aVelocity.y);
+  if (mX.CanScroll()) {
+    mX.SetVelocity(mX.GetVelocity() + aVelocity.x);
+    aVelocity.x = 0;
+  }
+  if (mY.CanScroll()) {
+    mY.SetVelocity(mY.GetVelocity() + aVelocity.y);
+    aVelocity.y = 0;
+  }
   SetState(FLING);
   FlingAnimation *fling = new FlingAnimation(*this,
       aOverscrollHandoffChain,
       !aHandoff);  // only apply acceleration if this is an initial fling
 
   float friction = gfxPrefs::APZFlingFriction();
   ParentLayerPoint velocity(mX.GetVelocity(), mY.GetVelocity());
   ParentLayerPoint predictedDelta;
@@ -2222,41 +2243,39 @@ void AsyncPanZoomController::AcceptFling
       controller->RequestFlingSnap(mFrameMetrics.GetScrollId(),
                                    predictedDestination);
     }
   }
 
   StartAnimation(fling);
 }
 
-bool AsyncPanZoomController::AttemptFling(ParentLayerPoint aVelocity,
+bool AsyncPanZoomController::AttemptFling(ParentLayerPoint& aVelocity,
                                           const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                                           bool aHandoff) {
   // If we are pannable, take over the fling ourselves.
   if (IsPannable()) {
     AcceptFling(aVelocity,
                 aOverscrollHandoffChain,
                 aHandoff);
     return true;
   }
 
   return false;
 }
 
 void AsyncPanZoomController::HandleFlingOverscroll(const ParentLayerPoint& aVelocity,
                                                    const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain) {
   APZCTreeManager* treeManagerLocal = GetApzcTreeManager();
-  if (!(treeManagerLocal && treeManagerLocal->DispatchFling(this,
-                                                            aVelocity,
-                                                            aOverscrollHandoffChain,
-                                                            true /* handoff */))) {
-    // No one wanted the fling, so we "take it" ourselves by entering an
-    // overscroll animation starting with the fling's velocity.
-    if (IsPannable() && gfxPrefs::APZOverscrollEnabled()) {
-      StartOverscrollAnimation(aVelocity);
+  ParentLayerPoint velocity = aVelocity;
+  if (treeManagerLocal) {
+    treeManagerLocal->DispatchFling(this, velocity, aOverscrollHandoffChain,
+                                    true /* handoff */);
+    if (!IsZero(velocity) && IsPannable() && gfxPrefs::APZOverscrollEnabled()) {
+      StartOverscrollAnimation(velocity);
     }
   }
 }
 
 void AsyncPanZoomController::HandleSmoothScrollOverscroll(const ParentLayerPoint& aVelocity) {
   // We must call BuildOverscrollHandoffChain from this deferred callback
   // function in order to avoid a deadlock when acquiring the tree lock.
   HandleFlingOverscroll(aVelocity, BuildOverscrollHandoffChain());
@@ -2279,29 +2298,29 @@ void AsyncPanZoomController::StartSmooth
                                            gfxPrefs::ScrollBehaviorDampingRatio()));
 }
 
 void AsyncPanZoomController::StartOverscrollAnimation(const ParentLayerPoint& aVelocity) {
   SetState(OVERSCROLL_ANIMATION);
   StartAnimation(new OverscrollAnimation(*this, aVelocity));
 }
 
-bool AsyncPanZoomController::CallDispatchScroll(const ParentLayerPoint& aStartPoint,
-                                                const ParentLayerPoint& aEndPoint,
+void AsyncPanZoomController::CallDispatchScroll(ParentLayerPoint& aStartPoint,
+                                                ParentLayerPoint& aEndPoint,
                                                 OverscrollHandoffState& aOverscrollHandoffState) {
   // Make a local copy of the tree manager pointer and check if it's not
   // null before calling DispatchScroll(). This is necessary because
   // Destroy(), which nulls out mTreeManager, could be called concurrently.
   APZCTreeManager* treeManagerLocal = GetApzcTreeManager();
   if (!treeManagerLocal) {
-    return false;
+    return;
   }
-  return treeManagerLocal->DispatchScroll(this,
-                                          aStartPoint, aEndPoint,
-                                          aOverscrollHandoffState);
+  treeManagerLocal->DispatchScroll(this,
+                                   aStartPoint, aEndPoint,
+                                   aOverscrollHandoffState);
 }
 
 void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) {
   ParentLayerPoint prevTouchPoint(mX.GetPos(), mY.GetPos());
   ParentLayerPoint touchPoint = GetFirstTouchPoint(aEvent);
 
   ScreenPoint panDistance = ToScreenCoordinates(
       ParentLayerPoint(mX.PanDistance(touchPoint.x),
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -850,25 +850,26 @@ private:
 private:
   /* ===================================================================
    * The functions and members in this section are used to manage
    * fling animations, smooth scroll animations, and overscroll
    * during a fling or smooth scroll.
    */
 public:
   /**
-   * Attempt a fling with the given velocity. If we are not pannable, tehe fling
+   * Attempt a fling with the given velocity. If we are not pannable, the 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.
+   * mTreeManager->DispatchFling(). Returns true iff. the entire velocity of
+   * the fling was consumed by this APZC. aVelocity is modified to contain any
+   * unused, residual velocity.
    * |aHandoff| should be true iff. the fling was handed off from a previous
    *            APZC, and determines whether acceleration is applied to the
    *            fling.
    */
-  bool AttemptFling(ParentLayerPoint aVelocity,
+  bool AttemptFling(ParentLayerPoint& aVelocity,
                     const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                     bool aHandoff);
 
 private:
   friend class FlingAnimation;
   friend class OverscrollAnimation;
   friend class SmoothScrollAnimation;
   friend class WheelScrollAnimation;
@@ -884,17 +885,17 @@ private:
   // later in the handoff chain, or if there are no takers, continuing the
   // fling and entering an overscrolled state.
   void HandleFlingOverscroll(const ParentLayerPoint& aVelocity,
                              const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain);
 
   void HandleSmoothScrollOverscroll(const ParentLayerPoint& aVelocity);
 
   // Helper function used by TakeOverFling() and HandleFlingOverscroll().
-  void AcceptFling(const ParentLayerPoint& aVelocity,
+  void AcceptFling(ParentLayerPoint& aVelocity,
                    const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                    bool aHandoff);
 
   // Start an overscroll animation with the given initial velocity.
   void StartOverscrollAnimation(const ParentLayerPoint& aVelocity);
 
   void StartSmoothScroll(ScrollSource aSource);
 
@@ -962,19 +963,21 @@ public:
    * |aOverscrollHandoffChain| and |aOverscrollHandoffChainIndex| are used by
    * the tree manager to keep track of which APZC to hand off the overscroll
    * to; this function increments the chain and the index and passes it on to
    * APZCTreeManager::DispatchScroll() in the event of overscroll.
    * Returns true iff. this APZC, or an APZC further down the
    * handoff chain, accepted the scroll (possibly entering an overscrolled
    * state). If this returns false, the caller APZC knows that it should enter
    * an overscrolled state itself if it can.
+   * aStartPoint and aEndPoint are modified depending on how much of the
+   * scroll gesture was consumed by APZCs in the handoff chain.
    */
-  bool AttemptScroll(const ParentLayerPoint& aStartPoint,
-                     const ParentLayerPoint& aEndPoint,
+  bool AttemptScroll(ParentLayerPoint& aStartPoint,
+                     ParentLayerPoint& aEndPoint,
                      OverscrollHandoffState& aOverscrollHandoffState);
 
   void FlushRepaintForOverscrollHandoff();
 
   /**
    * If overscrolled, start a snap-back animation and return true.
    * Otherwise return false.
    */
@@ -1002,35 +1005,34 @@ public:
   nsRefPtr<const OverscrollHandoffChain> BuildOverscrollHandoffChain();
 
 private:
   /**
    * A helper function for calling APZCTreeManager::DispatchScroll().
    * Guards against the case where the APZC is being concurrently destroyed
    * (and thus mTreeManager is being nulled out).
    */
-  bool CallDispatchScroll(const ParentLayerPoint& aStartPoint,
-                          const ParentLayerPoint& aEndPoint,
+  void CallDispatchScroll(ParentLayerPoint& aStartPoint,
+                          ParentLayerPoint& aEndPoint,
                           OverscrollHandoffState& aOverscrollHandoffState);
 
   /**
    * A helper function for overscrolling during panning. This is a wrapper
    * around OverscrollBy() that also implements restrictions on entering
    * overscroll based on the pan angle.
    */
-  bool OverscrollForPanning(ParentLayerPoint aOverscroll,
+  void OverscrollForPanning(ParentLayerPoint& aOverscroll,
                             const ScreenPoint& aPanDistance);
 
   /**
    * Try to overscroll by 'aOverscroll'.
-   * If we are pannable, 'aOverscroll' is added to any existing overscroll,
-   * and the function returns true.
-   * Otherwise, nothing happens and the function return false.
+   * If we are pannable on a particular axis, that component of 'aOverscroll'
+   * is transferred to any existing overscroll.
    */
-  bool OverscrollBy(const ParentLayerPoint& aOverscroll);
+  void OverscrollBy(ParentLayerPoint& aOverscroll);
 
 
   /* ===================================================================
    * The functions and members in this section are used to maintain the
    * area that this APZC instance is responsible for. This is used when
    * hit-testing to see which APZC instance should handle touch events.
    */
 public:
--- a/gfx/layers/apz/src/OverscrollHandoffState.cpp
+++ b/gfx/layers/apz/src/OverscrollHandoffState.cpp
@@ -102,33 +102,20 @@ OverscrollHandoffChain::ClearOverscroll(
 }
 
 void
 OverscrollHandoffChain::SnapBackOverscrolledApzc(const AsyncPanZoomController* aStart) const
 {
   uint32_t i = IndexOf(aStart);
   for (; i < Length(); ++i) {
     AsyncPanZoomController* apzc = mChain[i];
-    if (!apzc->IsDestroyed() && apzc->SnapBackIfOverscrolled()) {
-      // At most one APZC from |aStart| onwards can be overscrolled.
-      break;
+    if (!apzc->IsDestroyed()) {
+      apzc->SnapBackIfOverscrolled();
     }
   }
-
-  // In debug builds, verify our assumption that only one APZC from |aStart|
-  // onwards is overscrolled.
-#ifdef DEBUG
-  ++i;
-  for (; i < Length(); ++i) {
-    AsyncPanZoomController* apzc = mChain[i];
-    if (!apzc->IsDestroyed()) {
-      MOZ_ASSERT(!apzc->IsOverscrolled());
-    }
-  }
-#endif
 }
 
 bool
 OverscrollHandoffChain::CanBePanned(const AsyncPanZoomController* aApzc) const
 {
   // Find |aApzc| in the handoff chain.
   uint32_t i = IndexOf(aApzc);