Bug 1022719 - Ignore additional touches when in overscrolled state. r=kats, a=2.0+
authorBotond Ballo <botond@mozilla.com>
Tue, 10 Jun 2014 19:43:20 -0400
changeset 208111 76bc5db75c1f02ed1f5d566cf0bf66e6e97406cc
parent 208110 55a22e2c66eddeb29481c1859eaae72fa2985a9f
child 208112 994c5a699a04a1e9658ff3fbf439fc7606b308bb
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats, 2
bugs1022719
milestone32.0a2
Bug 1022719 - Ignore additional touches when in overscrolled state. r=kats, a=2.0+
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/GestureEventListener.cpp
gfx/layers/apz/src/GestureEventListener.h
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -35,16 +35,17 @@ namespace layers {
 float APZCTreeManager::sDPI = 160.0;
 
 // Pref that enables printing of the APZC tree for debugging.
 static bool gPrintApzcTree = false;
 
 APZCTreeManager::APZCTreeManager()
     : mTreeLock("APZCTreeLock"),
       mInOverscrolledApzc(false),
+      mRetainedTouchIdentifier(-1),
       mTouchCount(0),
       mApzcTreeLog("apzctree")
 {
   MOZ_ASSERT(NS_IsMainThread());
   AsyncPanZoomController::InitializeGlobalState();
   Preferences::AddBoolVarCache(&gPrintApzcTree, "apz.printtree", gPrintApzcTree);
   mApzcTreeLog.ConditionOnPref(&gPrintApzcTree);
 }
@@ -374,16 +375,26 @@ APZCTreeManager::ReceiveInputEvent(const
 {
   nsEventStatus result = nsEventStatus_eIgnore;
   gfx3DMatrix transformToApzc;
   gfx3DMatrix transformToGecko;
   switch (aEvent.mInputType) {
     case MULTITOUCH_INPUT: {
       const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
       if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
+        // If we are in an overscrolled state and a second finger goes down,
+        // ignore that second touch point completely. The touch-start for it is
+        // dropped completely; subsequent touch events until the touch-end for it
+        // will have this touch point filtered out.
+        if (mApzcForInputBlock && mApzcForInputBlock->IsOverscrolled()) {
+          if (mRetainedTouchIdentifier == -1) {
+            mRetainedTouchIdentifier = mApzcForInputBlock->GetLastTouchIdentifier();
+          }
+          return nsEventStatus_eConsumeNoDefault;
+        }
         // MULTITOUCH_START input contains all active touches of the current
         // session thus resetting mTouchCount.
         mTouchCount = multiTouchInput.mTouches.Length();
         mInOverscrolledApzc = false;
         mApzcForInputBlock = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[0].mScreenPoint),
                                            &mInOverscrolledApzc);
         if (multiTouchInput.mTouches.Length() == 1) {
           // If we have one touch point, this might be the start of a pan.
@@ -407,23 +418,53 @@ APZCTreeManager::ReceiveInputEvent(const
           mCachedTransformToApzcForInputBlock = transformToApzc;
         } else {
           // Reset the cached apz transform
           mCachedTransformToApzcForInputBlock = gfx3DMatrix();
         }
       } else if (mApzcForInputBlock) {
         APZCTM_LOG("Re-using APZC %p as continuation of event block\n", mApzcForInputBlock.get());
       }
+
+      // If we receive a touch-cancel, it means all touches are finished, so we
+      // can stop ignoring any that we were ignoring.
+      if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
+        mRetainedTouchIdentifier = -1;
+      }
+
       if (mApzcForInputBlock) {
         mApzcForInputBlock->GetGuid(aOutTargetGuid);
         // 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;
+
+        // Make a copy of the input event that we pass, with some modifications,
+        // to the target APZC.
         MultiTouchInput inputForApzc(multiTouchInput);
+
+        // If we are currently ignoring any touch points, filter them out from
+        // the set of touch points included in this event.
+        if (mRetainedTouchIdentifier != -1) {
+          for (size_t j = 0; j < inputForApzc.mTouches.Length(); ++j) {
+            if (inputForApzc.mTouches[j].mIdentifier != mRetainedTouchIdentifier) {
+              // TODO(botond): Once we get rid of ReceiveInputEvent(WidgetInputEvent),
+              // the signature of this function will change to take the InputData
+              // via non-const reference. We can then remove the touch point from
+              // multiTouchInput rather than the copy (inputForApzc), so that
+              // content doesn't get it either.
+              inputForApzc.mTouches.RemoveElementAt(j);
+              --j;
+            }
+          }
+          if (inputForApzc.mTouches.IsEmpty()) {
+            return nsEventStatus_eConsumeNoDefault;
+          }
+        }
+
         for (size_t i = 0; i < inputForApzc.mTouches.Length(); i++) {
           ApplyTransform(&(inputForApzc.mTouches[i].mScreenPoint), transformToApzc);
         }
         mApzcForInputBlock->ReceiveInputEvent(inputForApzc);
       }
       result = mInOverscrolledApzc ? nsEventStatus_eConsumeNoDefault
              : mApzcForInputBlock ? nsEventStatus_eConsumeDoDefault
              : nsEventStatus_eIgnore;
@@ -436,16 +477,17 @@ APZCTreeManager::ReceiveInputEvent(const
           NS_WARNING("Got an unexpected touchend/touchcancel");
           mTouchCount = 0;
         }
         // 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 (mTouchCount == 0) {
           mApzcForInputBlock = nullptr;
           mInOverscrolledApzc = false;
+          mRetainedTouchIdentifier = -1;
           ClearOverscrollHandoffChain();
         }
       }
       break;
     } case PANGESTURE_INPUT: {
       const PanGestureInput& panInput = aEvent.AsPanGestureInput();
       bool inOverscrolledApzc = false;
       nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(panInput.mPanStartPoint,
@@ -533,31 +575,63 @@ APZCTreeManager::ProcessTouchEvent(Widge
                                    ScrollableLayerGuid* aOutTargetGuid)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!aEvent.touches.Length()) {
     return nsEventStatus_eIgnore;
   }
   if (aEvent.message == NS_TOUCH_START) {
+    // If we are in an overscrolled state and a second finger goes down,
+    // ignore that second touch point completely. The touch-start for it is
+    // dropped completely; subsequent touch events until the touch-end for it
+    // will have this touch point filtered out.
+    if (mApzcForInputBlock && mApzcForInputBlock->IsOverscrolled()) {
+      if (mRetainedTouchIdentifier == -1) {
+        mRetainedTouchIdentifier = mApzcForInputBlock->GetLastTouchIdentifier();
+      }
+      return nsEventStatus_eConsumeNoDefault;
+    }
     // NS_TOUCH_START event contains all active touches of the current
     // session thus resetting mTouchCount.
     mTouchCount = aEvent.touches.Length();
     mInOverscrolledApzc = false;
     mApzcForInputBlock = GetTouchInputBlockAPZC(aEvent, &mInOverscrolledApzc);
     if (mApzcForInputBlock) {
       // Cache apz transform so it can be used for future events in this block.
       gfx3DMatrix transformToGecko;
       GetInputTransforms(mApzcForInputBlock, mCachedTransformToApzcForInputBlock, transformToGecko);
     } else {
       // Reset the cached apz transform
       mCachedTransformToApzcForInputBlock = gfx3DMatrix();
     }
   }
 
+  // If we receive a touch-cancel, it means all touches are finished, so we
+  // can stop ignoring any that we were ignoring.
+  if (aEvent.message == NS_TOUCH_CANCEL) {
+    mRetainedTouchIdentifier = -1;
+  }
+
+  // If we are currently ignoring any touch points, filter them out from the
+  // set of touch points included in this event. Note that we modify aEvent
+  // itself, so that the touch points are also filtered out when the caller
+  // passes the event on to content.
+  if (mRetainedTouchIdentifier != -1) {
+    for (size_t j = 0; j < aEvent.touches.Length(); ++j) {
+      if (aEvent.touches[j]->Identifier() != mRetainedTouchIdentifier) {
+        aEvent.touches.RemoveElementAt(j);
+        --j;
+      }
+    }
+    if (aEvent.touches.IsEmpty()) {
+      return nsEventStatus_eConsumeNoDefault;
+    }
+  }
+
   if (mApzcForInputBlock) {
     mApzcForInputBlock->GetGuid(aOutTargetGuid);
     // 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.
     gfx3DMatrix transformToApzc = mCachedTransformToApzcForInputBlock;
     MultiTouchInput inputForApzc(aEvent);
     for (size_t i = 0; i < inputForApzc.mTouches.Length(); i++) {
@@ -587,16 +661,17 @@ APZCTreeManager::ProcessTouchEvent(Widge
       mTouchCount -= aEvent.touches.Length();
     } else {
       NS_WARNING("Got an unexpected touchend/touchcancel");
       mTouchCount = 0;
     }
     if (mTouchCount == 0) {
       mApzcForInputBlock = nullptr;
       mInOverscrolledApzc = false;
+      mRetainedTouchIdentifier = -1;
       ClearOverscrollHandoffChain();
     }
   }
   return result;
 }
 
 void
 APZCTreeManager::TransformCoordinateToGecko(const ScreenIntPoint& aPoint,
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -372,16 +372,21 @@ private:
    * 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;
   /* Whether the current input event block is being ignored because the touch-start
    * was inside an overscrolled APZC.
    */
   bool mInOverscrolledApzc;
+  /* Sometimes we want to ignore all touches except one. In such cases, this
+   * is set to the identifier of the touch we are not ignoring; in other cases,
+   * this is set to -1.
+   */
+  int32_t mRetainedTouchIdentifier;
   /* The number of touch points we are tracking that are currently on the screen. */
   uint32_t mTouchCount;
   /* 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 transform.
    * Meaningless if mApzcForInputBlock is nullptr.
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1837,16 +1837,21 @@ void AsyncPanZoomController::FlushRepain
   UpdateSharedCompositorFrameMetrics();
 }
 
 bool AsyncPanZoomController::IsPannable() const {
   ReentrantMonitorAutoEnter lock(mMonitor);
   return mX.CanScroll() || mY.CanScroll();
 }
 
+int32_t AsyncPanZoomController::GetLastTouchIdentifier() const {
+  nsRefPtr<GestureEventListener> listener = GetGestureEventListener();
+  return listener ? listener->GetLastTouchIdentifier() : -1;
+}
+
 void AsyncPanZoomController::RequestContentRepaint() {
   RequestContentRepaint(mFrameMetrics);
 }
 
 void AsyncPanZoomController::RequestContentRepaint(FrameMetrics& aFrameMetrics) {
   aFrameMetrics.SetDisplayPortMargins(
     CalculatePendingDisplayPort(aFrameMetrics,
                                 GetVelocityVector(),
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -313,16 +313,23 @@ public:
    */
   bool HasScrollgrab() const { return mFrameMetrics.mHasScrollgrab; }
 
   /**
    * Returns whether this APZC has room to be panned (in any direction).
    */
   bool IsPannable() const;
 
+  /**
+   * Returns the identifier of the touch in the last touch event processed by
+   * this APZC. This should only be called when the last touch event contained
+   * only one touch.
+   */
+  int32_t GetLastTouchIdentifier() const;
+
 protected:
   enum PanZoomState {
     NOTHING,                  /* no touch-start events received */
     FLING,                    /* all touches removed, but we're still scrolling page */
     TOUCHING,                 /* one touch-start event received */
 
     PANNING,                  /* panning the frame */
     PANNING_LOCKED_X,         /* touch-start followed by move (i.e. panning with axis lock) X axis */
--- a/gfx/layers/apz/src/GestureEventListener.cpp
+++ b/gfx/layers/apz/src/GestureEventListener.cpp
@@ -101,16 +101,26 @@ nsEventStatus GestureEventListener::Hand
     mTouches.Clear();
     rv = HandleInputTouchCancel();
     break;
   }
 
   return rv;
 }
 
+int32_t GestureEventListener::GetLastTouchIdentifier() const
+{
+  if (mTouches.Length() != 1) {
+    NS_WARNING("GetLastTouchIdentifier() called when last touch event "
+               "did not have one touch");
+  }
+  return mTouches.IsEmpty() ? -1 : mTouches[0].mIdentifier;
+}
+
+
 nsEventStatus GestureEventListener::HandleInputTouchSingleStart()
 {
   switch (mState) {
   case GESTURE_NONE:
     SetState(GESTURE_FIRST_SINGLE_TOUCH_DOWN);
     mTouchStartPosition = mLastTouchInput.mTouches[0].mScreenPoint;
 
     CreateLongTapTimeoutTask();
--- a/gfx/layers/apz/src/GestureEventListener.h
+++ b/gfx/layers/apz/src/GestureEventListener.h
@@ -48,16 +48,23 @@ public:
 
   /**
    * General input handler for a touch event. If the touch event is not a part
    * of a gesture, then we pass it along to AsyncPanZoomController. Otherwise,
    * it gets consumed here and never forwarded along.
    */
   nsEventStatus HandleInputEvent(const MultiTouchInput& aEvent);
 
+  /**
+   * Returns the identifier of the touch in the last touch event processed by
+   * this GestureEventListener. This should only be called when the last touch
+   * event contained only one touch.
+   */
+  int32_t GetLastTouchIdentifier() const;
+
 private:
   // Private destructor, to discourage deletion outside of Release():
   ~GestureEventListener();
 
   /**
    * States of GEL finite-state machine.
    */
   enum GestureState {