Bug 1022719 - Ignore additional touches when in overscrolled state. r=kats
authorBotond Ballo <botond@mozilla.com>
Tue, 10 Jun 2014 19:43:20 -0400
changeset 188744 4b5b1689e3a00605c087c17d3e59babc0b12f64a
parent 188743 2d592384ef8c106b65c47b209efecae400db4a57
child 188745 8b3794b7e1dc2f517a7666a9db11d889f247da5c
push id44899
push userbballo@mozilla.com
push dateSat, 14 Jun 2014 04:40:46 +0000
treeherdermozilla-inbound@4b5b1689e3a0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1022719
milestone33.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 1022719 - Ignore additional touches when in overscrolled state. 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/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 {