Bug 898478 - Have AsyncPanZoomControllers pass overscroll caused by panning on to their parents. r=kats, a=koi+
authorBotond Ballo <botond@mozilla.com>
Tue, 20 Aug 2013 19:00:57 -0400
changeset 160391 5e11a411141e3cc8314e8a8b102af1e543c61c16
parent 160390 8977ee4184dcc3e9dc22a8f2610272b3bd1f1f55
child 160392 2350d1dd113370c7d25c67f6904d1578f04e2e18
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats, koi
bugs898478
milestone26.0a2
Bug 898478 - Have AsyncPanZoomControllers pass overscroll caused by panning on to their parents. r=kats, a=koi+
gfx/layers/composite/APZCTreeManager.cpp
gfx/layers/composite/APZCTreeManager.h
gfx/layers/ipc/AsyncPanZoomController.cpp
gfx/layers/ipc/AsyncPanZoomController.h
gfx/layers/ipc/Axis.cpp
gfx/layers/ipc/Axis.h
gfx/layers/ipc/GestureEventListener.cpp
widget/android/nsWindow.cpp
--- a/gfx/layers/composite/APZCTreeManager.cpp
+++ b/gfx/layers/composite/APZCTreeManager.cpp
@@ -238,21 +238,30 @@ APZCTreeManager::ReceiveInputEvent(const
           nsRefPtr<AsyncPanZoomController> apzc2 = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[i].mScreenPoint));
           mApzcForInputBlock = CommonAncestor(mApzcForInputBlock.get(), apzc2.get());
           APZC_LOG("Using APZC %p as the common ancestor\n", mApzcForInputBlock.get());
           // For now, we only ever want to do pinching on the root APZC for a given layers id. So
           // when we find the common ancestor of multiple points, also walk up to the root APZC.
           mApzcForInputBlock = RootAPZCForLayersId(mApzcForInputBlock);
           APZC_LOG("Using APZC %p as the root APZC for multi-touch\n", mApzcForInputBlock.get());
         }
+
+        // Cache transformToApzc so it can be used for future events in this block.
+        if (mApzcForInputBlock) {
+          GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToScreen);
+          mCachedTransformToApzcForInputBlock = transformToApzc;
+        }
       } else if (mApzcForInputBlock) {
         APZC_LOG("Re-using APZC %p as continuation of event block\n", mApzcForInputBlock.get());
       }
       if (mApzcForInputBlock) {
-        GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToScreen);
+        // Use the cached transform to compute the point to send to the APZC.
+        // This ensures that the sequence of touch points an APZC sees in an
+        // input block are all in the same coordinate space.
+        transformToApzc = mCachedTransformToApzcForInputBlock;
         MultiTouchInput inputForApzc(multiTouchInput);
         for (size_t i = 0; i < inputForApzc.mTouches.Length(); i++) {
           ApplyTransform(&(inputForApzc.mTouches[i].mScreenPoint), transformToApzc);
         }
         result = mApzcForInputBlock->ReceiveInputEvent(inputForApzc);
         // If we have an mApzcForInputBlock and it's the end of the touch sequence
         // then null it out so we don't keep a dangling reference and leak things.
         if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL ||
@@ -309,26 +318,38 @@ APZCTreeManager::ReceiveInputEvent(const
             GetTargetAPZC(ScreenPoint(point.x, point.y));
           mApzcForInputBlock = CommonAncestor(mApzcForInputBlock.get(), apzc2.get());
           APZC_LOG("Using APZC %p as the common ancestor\n", mApzcForInputBlock.get());
           // For now, we only ever want to do pinching on the root APZC for a given layers id. So
           // when we find the common ancestor of multiple points, also walk up to the root APZC.
           mApzcForInputBlock = RootAPZCForLayersId(mApzcForInputBlock);
           APZC_LOG("Using APZC %p as the root APZC for multi-touch\n", mApzcForInputBlock.get());
         }
+        if (mApzcForInputBlock) {
+          // Cache transformToApzc so it can be used for future events in this block.
+          GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToScreen);
+          mCachedTransformToApzcForInputBlock = transformToApzc;
+        }
       } else if (mApzcForInputBlock) {
         APZC_LOG("Re-using APZC %p as continuation of event block\n", mApzcForInputBlock.get());
       }
       if (mApzcForInputBlock) {
-        GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToScreen);
+        // For computing the input for the APZC, used the cached transform.
+        // This ensures that the sequence of touch points an APZC sees in an
+        // input block are all in the same coordinate space.
+        transformToApzc = mCachedTransformToApzcForInputBlock;
         MultiTouchInput inputForApzc(touchEvent);
         for (size_t i = 0; i < inputForApzc.mTouches.Length(); i++) {
           ApplyTransform(&(inputForApzc.mTouches[i].mScreenPoint), transformToApzc);
         }
 
+        // For computing the event to pass back to Gecko, use the up-to-date transforms.
+        // This ensures that transformToApzc and transformToScreen are in sync
+        // (note that transformToScreen isn't cached).
+        GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToScreen);
         gfx3DMatrix outTransform = transformToApzc * transformToScreen;
         nsTouchEvent* outEvent = static_cast<nsTouchEvent*>(aOutEvent);
         for (size_t i = 0; i < outEvent->touches.Length(); i++) {
           ApplyTransform(&(outEvent->touches[i]->mRefPoint), outTransform);
         }
 
         nsEventStatus ret = mApzcForInputBlock->ReceiveInputEvent(inputForApzc);
 
@@ -454,16 +475,39 @@ APZCTreeManager::ClearTree()
   nsTArray< nsRefPtr<AsyncPanZoomController> > apzcsToDestroy;
   Collect(mRootApzc, &apzcsToDestroy);
   for (size_t i = 0; i < apzcsToDestroy.Length(); i++) {
     apzcsToDestroy[i]->Destroy();
   }
   mRootApzc = nullptr;
 }
 
+void
+APZCTreeManager::HandleOverscroll(AsyncPanZoomController* aChild, ScreenPoint aStartPoint, ScreenPoint aEndPoint)
+{
+  AsyncPanZoomController* parent = aChild->GetParent();
+  if (parent == nullptr)
+    return;
+
+  gfx3DMatrix transformToApzc;
+  gfx3DMatrix transformToScreen;  // ignored
+
+  // Convert start and end points to untransformed screen coordinates.
+  GetInputTransforms(aChild, transformToApzc, transformToScreen);
+  ApplyTransform(&aStartPoint, transformToApzc.Inverse());
+  ApplyTransform(&aEndPoint, transformToApzc.Inverse());
+
+  // Convert start and end points to parent's transformed screen coordinates.
+  GetInputTransforms(parent, transformToApzc, transformToScreen);
+  ApplyTransform(&aStartPoint, transformToApzc);
+  ApplyTransform(&aEndPoint, transformToApzc);
+
+  parent->AttemptScroll(aStartPoint, aEndPoint);
+}
+
 already_AddRefed<AsyncPanZoomController>
 APZCTreeManager::GetTargetAPZC(const ScrollableLayerGuid& aGuid)
 {
   MonitorAutoLock lock(mTreeLock);
   nsRefPtr<AsyncPanZoomController> target;
   // The root may have siblings, check those too
   for (AsyncPanZoomController* apzc = mRootApzc; apzc; apzc = apzc->GetPrevSibling()) {
     target = FindTargetAPZC(apzc, aGuid);
--- a/gfx/layers/composite/APZCTreeManager.h
+++ b/gfx/layers/composite/APZCTreeManager.h
@@ -5,16 +5,17 @@
 
 #ifndef mozilla_layers_APZCTreeManager_h
 #define mozilla_layers_APZCTreeManager_h
 
 #include <stdint.h>                     // for uint64_t, uint32_t
 #include "FrameMetrics.h"               // for FrameMetrics, etc
 #include "Units.h"                      // for CSSPoint, CSSRect, etc
 #include "gfxPoint.h"                   // for gfxPoint
+#include "gfx3DMatrix.h"                // for gfx3DMatrix
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2
 #include "mozilla/Monitor.h"            // for Monitor
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsEvent.h"                    // for nsEventStatus
 #include "nsISupportsImpl.h"
 #include "nsTraceRefcnt.h"              // for MOZ_COUNT_CTOR, etc
 
@@ -241,16 +242,28 @@ public:
    */
   static void SetDPI(float aDpiValue) { sDPI = aDpiValue; }
 
   /**
    * Returns the current dpi value in use.
    */
   static float GetDPI() { return sDPI; }
 
+  /**
+   * This is a callback for AsyncPanZoomController to call when a touch-move
+   * event causes overscroll. The overscroll will be passed on to the parent
+   * APZC. |aStartPoint| and |aEndPoint| are in |aAPZC|'s transformed screen
+   * coordinates (i.e. the same coordinates in which touch points are given to
+   * APZCs). The amount of the overscroll is represented by two points rather
+   * than a displacement because with certain 3D transforms, the same
+   * displacement between different points in transformed coordinates can
+   * represent different displacements in untransformed coordinates.
+   */
+  void HandleOverscroll(AsyncPanZoomController* aAPZC, ScreenPoint aStartPoint, ScreenPoint aEndPoint);
+
 protected:
   /**
    * Debug-build assertion that can be called to ensure code is running on the
    * compositor thread.
    */
   virtual void AssertOnCompositorThread();
 
 public:
@@ -298,16 +311,24 @@ private:
   mozilla::Monitor mTreeLock;
   nsRefPtr<AsyncPanZoomController> mRootApzc;
   /* This tracks the APZC that should receive all inputs for the current input event block.
    * This allows touch points to move outside the thing they started on, but still have the
    * touch events delivered to the same initial APZC. This will only ever be touched on the
    * input delivery thread, and so does not require locking.
    */
   nsRefPtr<AsyncPanZoomController> mApzcForInputBlock;
+  /* The transform from root screen coordinates into mApzcForInputBlock's
+   * screen coordinates, as returned through the 'aTransformToApzcOut' parameter
+   * of GetInputTransform(), at the start of the input block. This is cached
+   * because this transform can change over the course of the input block,
+   * but for some operations we need to use the initial tranform.
+   * Meaningless if mApzcForInputBlock is nullptr.
+   */
+  gfx3DMatrix mCachedTransformToApzcForInputBlock;
 
   static float sDPI;
 };
 
 }
 }
 
 #endif // mozilla_layers_PanZoomController_h
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -725,49 +725,69 @@ void AsyncPanZoomController::UpdateWithT
   if (timeDelta.ToMilliseconds() <= EPSILON) {
     return;
   }
 
   mX.UpdateWithTouchAtDevicePoint(point.x, timeDelta);
   mY.UpdateWithTouchAtDevicePoint(point.y, timeDelta);
 }
 
+void AsyncPanZoomController::AttemptScroll(const ScreenPoint& aStartPoint,
+                                           const ScreenPoint& aEndPoint) {
+  // "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.
+  ScreenPoint displacement = aStartPoint - aEndPoint;
+
+  ScreenPoint overscroll;  // will be used outside monitor block
+  {
+    ReentrantMonitorAutoEnter lock(mMonitor);
+
+    CSSToScreenScale zoom = mFrameMetrics.mZoom;
+
+    // Inversely scale the offset by the resolution (when you're zoomed further in,
+    // a larger swipe should move you a shorter distance).
+    CSSPoint cssDisplacement = displacement / zoom;
+
+    CSSPoint cssOverscroll;
+    gfx::Point scrollOffset(mX.AdjustDisplacement(cssDisplacement.x, cssOverscroll.x),
+                            mY.AdjustDisplacement(cssDisplacement.y, cssOverscroll.y));
+    overscroll = cssOverscroll * zoom;
+
+    if (fabs(scrollOffset.x) > EPSILON || fabs(scrollOffset.y) > EPSILON) {
+      ScrollBy(CSSPoint::FromUnknownPoint(scrollOffset));
+      ScheduleComposite();
+
+      TimeDuration timePaintDelta = mPaintThrottler.TimeSinceLastRequest(GetFrameTime());
+      if (timePaintDelta.ToMilliseconds() > gPanRepaintInterval) {
+        RequestContentRepaint();
+      }
+    }
+  }
+
+  if (fabs(overscroll.x) > EPSILON || fabs(overscroll.y) > EPSILON) {
+    // "+ overscroll" rather than "- overscroll" for the same reason as above.
+    mTreeManager->HandleOverscroll(this, aEndPoint + overscroll, aEndPoint);
+  }
+}
+
 void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) {
+  SingleTouchData& touch = GetFirstSingleTouch(aEvent);
+  ScreenIntPoint prevTouchPoint(mX.GetPos(), mY.GetPos());
+  ScreenIntPoint touchPoint = touch.mScreenPoint;
   TimeDuration timeDelta = TimeDuration().FromMilliseconds(aEvent.mTime - mLastEventTime);
 
   // Probably a duplicate event, just throw it away.
   if (timeDelta.ToMilliseconds() <= EPSILON) {
     return;
   }
 
   UpdateWithTouchAtDevicePoint(aEvent);
 
-  {
-    ReentrantMonitorAutoEnter lock(mMonitor);
-
-    // We want to inversely scale it because when you're zoomed further in, a
-    // larger swipe should move you a shorter distance.
-    ScreenToCSSScale inverseResolution = mFrameMetrics.mZoom.Inverse();
-
-    gfx::Point displacement(mX.GetDisplacementForDuration(inverseResolution.scale,
-                                                          timeDelta),
-                            mY.GetDisplacementForDuration(inverseResolution.scale,
-                                                          timeDelta));
-    if (fabs(displacement.x) <= EPSILON && fabs(displacement.y) <= EPSILON) {
-      return;
-    }
-
-    ScrollBy(CSSPoint::FromUnknownPoint(displacement));
-    ScheduleComposite();
-
-    TimeDuration timePaintDelta = mPaintThrottler.TimeSinceLastRequest(GetFrameTime());
-    if (timePaintDelta.ToMilliseconds() > gPanRepaintInterval) {
-      RequestContentRepaint();
-    }
-  }
+  AttemptScroll(prevTouchPoint, touchPoint);
 }
 
 SingleTouchData& AsyncPanZoomController::GetFirstSingleTouch(const MultiTouchInput& aEvent) {
   return (SingleTouchData&)aEvent.mTouches[0];
 }
 
 bool AsyncPanZoomController::DoFling(const TimeDuration& aDelta) {
   if (mState != FLING) {
@@ -782,23 +802,26 @@ bool AsyncPanZoomController::DoFling(con
     // the zoom while accelerating.
     SetZoomAndResolution(mFrameMetrics.mZoom);
     SendAsyncScrollEvent();
     RequestContentRepaint();
     mState = NOTHING;
     return false;
   }
 
-  // We want to inversely scale it because when you're zoomed further in, a
-  // larger swipe should move you a shorter distance.
-  ScreenToCSSScale inverseResolution = mFrameMetrics.mZoom.Inverse();
+  CSSPoint overscroll; // overscroll is ignored for flings
+  ScreenPoint offset(aDelta.ToMilliseconds() * mX.GetVelocity(),
+                     aDelta.ToMilliseconds() * mY.GetVelocity());
 
+  // Inversely scale the offset by the resolution (when you're zoomed further in,
+  // a larger swipe should move you a shorter distance).
+  CSSPoint cssOffset = offset / mFrameMetrics.mZoom;
   ScrollBy(CSSPoint::FromUnknownPoint(gfx::Point(
-    mX.GetDisplacementForDuration(inverseResolution.scale, aDelta),
-    mY.GetDisplacementForDuration(inverseResolution.scale, aDelta)
+    mX.AdjustDisplacement(cssOffset.x, overscroll.x),
+    mY.AdjustDisplacement(cssOffset.y, overscroll.y)
   )));
   TimeDuration timePaintDelta = mPaintThrottler.TimeSinceLastRequest(GetFrameTime());
   if (timePaintDelta.ToMilliseconds() > gFlingRepaintInterval) {
     RequestContentRepaint();
   }
 
   return true;
 }
--- a/gfx/layers/ipc/AsyncPanZoomController.h
+++ b/gfx/layers/ipc/AsyncPanZoomController.h
@@ -11,16 +11,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/RefPtr.h"
 #include "InputData.h"
 #include "Axis.h"
 #include "TaskThrottler.h"
 #include "gfx3DMatrix.h"
+#include "nsEvent.h"
 
 #include "base/message_loop.h"
 
 namespace mozilla {
 namespace layers {
 
 struct ScrollableLayerGuid;
 class CompositorParent;
@@ -255,16 +256,27 @@ public:
 
   /**
    * Cancels any currently running animation. Note that all this does is set the
    * state of the AsyncPanZoomController back to NOTHING, but it is the
    * animation's responsibility to check this before advancing.
    */
   void CancelAnimation();
 
+  /**
+   * Attempt to scroll in response to a touch-move from |aStartPoint| to
+   * |aEndPoint|, which are in our (transformed) screen coordinates.
+   * Due to overscroll handling, there may not actually have been a touch-move
+   * at these points, but this function will scroll as if there had been.
+   * If this attempt causes overscroll (i.e. the layer cannot be scrolled
+   * by the entire amount requested), the overscroll is passed back to the
+   * tree manager via APZCTreeManager::HandleOverscroll().
+   */
+  void AttemptScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint);
+
 protected:
   /**
    * Helper method for touches beginning. Sets everything up for panning and any
    * multitouch gestures.
    */
   nsEventStatus OnTouchStart(const MultiTouchInput& aEvent);
 
   /**
--- a/gfx/layers/ipc/Axis.cpp
+++ b/gfx/layers/ipc/Axis.cpp
@@ -130,31 +130,32 @@ void Axis::UpdateWithTouchAtDevicePoint(
   }
 }
 
 void Axis::StartTouch(int32_t aPos) {
   mStartPos = aPos;
   mPos = aPos;
 }
 
-float Axis::GetDisplacementForDuration(float aScale, const TimeDuration& aDelta) {
+float Axis::AdjustDisplacement(float aDisplacement, float& aOverscrollAmountOut) {
   if (fabsf(mVelocity) < gVelocityThreshold) {
     mAcceleration = 0;
   }
 
   float accelerationFactor = GetAccelerationFactor();
-  float displacement = mVelocity * aScale * aDelta.ToMilliseconds() * accelerationFactor;
+  float displacement = aDisplacement * accelerationFactor;
   // If this displacement will cause an overscroll, throttle it. Can potentially
   // bring it to 0 even if the velocity is high.
   if (DisplacementWillOverscroll(displacement) != OVERSCROLL_NONE) {
     // No need to have a velocity along this axis anymore; it won't take us
     // anywhere, so we're just spinning needlessly.
     mVelocity = 0.0f;
     mAcceleration = 0;
-    displacement -= DisplacementWillOverscrollAmount(displacement);
+    aOverscrollAmountOut = DisplacementWillOverscrollAmount(displacement);
+    displacement -= aOverscrollAmountOut;
   }
   return displacement;
 }
 
 float Axis::PanDistance() {
   return fabsf(mPos - mStartPos);
 }
 
--- a/gfx/layers/ipc/Axis.h
+++ b/gfx/layers/ipc/Axis.h
@@ -63,26 +63,24 @@ public:
    * Notify this Axis that a touch has ended forcefully. Useful for stopping
    * flings when a user puts their finger down in the middle of one (i.e. to
    * stop a previous touch including its fling so that a new one can take its
    * place).
    */
   void CancelTouch();
 
   /**
-   * Gets displacement that should have happened since the previous touch.
-   * Note: Does not reset the displacement. It gets recalculated on the next
-   * UpdateWithTouchAtDevicePoint(), however it is not safe to assume this will
-   * be the same on every call. This also checks for page boundaries and will
-   * return an adjusted displacement to prevent the viewport from overscrolling
-   * the page rect. An example of where this might matter is when you call it,
-   * apply a displacement that takes you to the boundary of the page, then call
-   * it again. The result will be different in this case.
+   * Takes a requested displacement to the position of this axis, and adjusts
+   * it to account for acceleration  (which might increase the displacement)
+   * and overscroll (which might decrease the displacement; this is to prevent
+   * the viewport from overscrolling the page rect). If overscroll ocurred,
+   * its amount is written to |aOverscrollAmountOut|.
+   * The adjusted displacement is returned.
    */
-  float GetDisplacementForDuration(float aScale, const TimeDuration& aDelta);
+  float AdjustDisplacement(float aDisplacement, float& aOverscrollAmountOut);
 
   /**
    * Gets the distance between the starting position of the touch supplied in
    * startTouch() and the current touch from the last
    * updateWithTouchAtDevicePoint().
    */
   float PanDistance();
 
@@ -165,16 +163,18 @@ public:
 
   float GetOrigin();
   float GetCompositionLength();
   float GetPageStart();
   float GetPageLength();
   float GetCompositionEnd();
   float GetPageEnd();
 
+  int32_t GetPos() const { return mPos; }
+
   virtual float GetPointOffset(const CSSPoint& aPoint) = 0;
   virtual float GetRectLength(const CSSRect& aRect) = 0;
   virtual float GetRectOffset(const CSSRect& aRect) = 0;
 
 protected:
   int32_t mPos;
   int32_t mStartPos;
   float mVelocity;
--- a/gfx/layers/ipc/GestureEventListener.cpp
+++ b/gfx/layers/ipc/GestureEventListener.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "GestureEventListener.h"
 #include <math.h>                       // for fabsf
 #include <stddef.h>                     // for size_t
 #include "AsyncPanZoomController.h"     // for AsyncPanZoomController
+#include "mozilla/layers/APZCTreeManager.h"  // for APZCTreeManager
 #include "base/task.h"                  // for CancelableTask, etc
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
 #include "mozilla/mozalloc.h"           // for operator new
 #include "nsDebug.h"                    // for NS_WARN_IF_FALSE
 #include "nsMathUtils.h"                // for NS_hypot
 
 namespace mozilla {
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -35,16 +35,17 @@ using mozilla::unused;
 
 #include "gfxImageSurface.h"
 #include "gfxContext.h"
 
 #include "Layers.h"
 #include "LayerManagerOGL.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/AsyncCompositionManager.h"
+#include "mozilla/layers/APZCTreeManager.h"
 #include "GLContext.h"
 #include "GLContextProvider.h"
 
 #include "nsTArray.h"
 
 #include "AndroidBridge.h"
 #include "android_npapi.h"