Bug 914829 - MetroInput should forward touch input to apz first, then to content. r=kats, tabraldes
☠☠ backed out by 4be36d56d323 ☠ ☠
authorJim Mathies <jmathies@mozilla.com>
Wed, 11 Sep 2013 13:09:46 -0500
changeset 146683 9fb279a95ce179bd283f230a38962f984a9e1287
parent 146682 3d36387df33f40a858bcd1bf5daa316070001772
child 146684 e19b14609e526b718e340b85837b38d106087d88
push idunknown
push userunknown
push dateunknown
reviewerskats, tabraldes
bugs914829
milestone26.0a1
Bug 914829 - MetroInput should forward touch input to apz first, then to content. r=kats, tabraldes
gfx/layers/composite/APZCTreeManager.cpp
widget/windows/winrt/MetroInput.cpp
widget/windows/winrt/MetroInput.h
widget/windows/winrt/MetroWidget.cpp
widget/windows/winrt/MetroWidget.h
--- a/gfx/layers/composite/APZCTreeManager.cpp
+++ b/gfx/layers/composite/APZCTreeManager.cpp
@@ -208,16 +208,17 @@ ApplyTransform(nsIntPoint* aPoint, const
   gfxPoint result = aMatrix.Transform(gfxPoint(aPoint->x, aPoint->y));
   aPoint->x = NS_lround(result.x);
   aPoint->y = NS_lround(result.y);
 }
 
 nsEventStatus
 APZCTreeManager::ReceiveInputEvent(const InputData& aEvent)
 {
+  nsEventStatus result = nsEventStatus_eIgnore;
   gfx3DMatrix transformToApzc;
   gfx3DMatrix transformToScreen;
   switch (aEvent.mInputType) {
     case MULTITOUCH_INPUT: {
       const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
       if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
         mApzcForInputBlock = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[0].mScreenPoint));
         for (size_t i = 1; i < multiTouchInput.mTouches.Length(); i++) {
@@ -233,48 +234,48 @@ APZCTreeManager::ReceiveInputEvent(const
         APZC_LOG("Re-using APZC %p as continuation of event block\n", mApzcForInputBlock.get());
       }
       if (mApzcForInputBlock) {
         GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToScreen);
         MultiTouchInput inputForApzc(multiTouchInput);
         for (size_t i = 0; i < inputForApzc.mTouches.Length(); i++) {
           ApplyTransform(&(inputForApzc.mTouches[i].mScreenPoint), transformToApzc);
         }
-        mApzcForInputBlock->ReceiveInputEvent(inputForApzc);
+        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 ||
             (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_END && multiTouchInput.mTouches.Length() == 1)) {
           mApzcForInputBlock = nullptr;
         }
       }
       break;
     } case PINCHGESTURE_INPUT: {
       const PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
       nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(pinchInput.mFocusPoint);
       if (apzc) {
         GetInputTransforms(apzc, transformToApzc, transformToScreen);
         PinchGestureInput inputForApzc(pinchInput);
         ApplyTransform(&(inputForApzc.mFocusPoint), transformToApzc);
-        apzc->ReceiveInputEvent(inputForApzc);
+        result = apzc->ReceiveInputEvent(inputForApzc);
       }
       break;
     } case TAPGESTURE_INPUT: {
       const TapGestureInput& tapInput = aEvent.AsTapGestureInput();
       nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(ScreenPoint(tapInput.mPoint));
       if (apzc) {
         GetInputTransforms(apzc, transformToApzc, transformToScreen);
         TapGestureInput inputForApzc(tapInput);
         ApplyTransform(&(inputForApzc.mPoint), transformToApzc);
-        apzc->ReceiveInputEvent(inputForApzc);
+        result = apzc->ReceiveInputEvent(inputForApzc);
       }
       break;
     }
   }
-  return nsEventStatus_eIgnore;
+  return result;
 }
 
 nsEventStatus
 APZCTreeManager::ReceiveInputEvent(const nsInputEvent& aEvent,
                                    nsInputEvent* aOutEvent)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
--- a/widget/windows/winrt/MetroInput.cpp
+++ b/widget/windows/winrt/MetroInput.cpp
@@ -175,16 +175,30 @@ namespace {
                     void *aTouchList)
   {
     nsTArray<nsRefPtr<Touch> > *touches =
               static_cast<nsTArray<nsRefPtr<Touch> > *>(aTouchList);
     touches->AppendElement(aData);
     aData->mChanged = false;
     return PL_DHASH_NEXT;
   }
+
+  // Helper for making sure event ptrs get freed.
+  class AutoDeleteEvent
+  {
+  public:
+    AutoDeleteEvent(nsGUIEvent* aPtr) :
+      mPtr(aPtr) {}
+    ~AutoDeleteEvent() {
+      if (mPtr) {
+        delete mPtr;
+      }
+    }
+    nsGUIEvent* mPtr;
+  };
 }
 
 namespace mozilla {
 namespace widget {
 namespace winrt {
 
 MetroInput::MetroInput(MetroWidget* aWidget,
                        UI::Core::ICoreWindow* aWindow)
@@ -427,16 +441,18 @@ MetroInput::OnPointerPressed(UI::Core::I
 
   if (mTouches.Count() == 1) {
     // If this is the first touchstart of a touch session reset some
     // tracking flags and dispatch the event with a custom callback
     // so we can check preventDefault result.
     mTouchStartDefaultPrevented = false;
     mTouchMoveDefaultPrevented = false;
     mIsFirstTouchMove = true;
+    mCancelable = true;
+    mTouchCancelSent = false;
     InitTouchEventTouchList(touchEvent);
     DispatchAsyncTouchEventWithCallback(touchEvent, &MetroInput::OnPointerPressedCallback);
   } else {
     InitTouchEventTouchList(touchEvent);
     DispatchAsyncTouchEventIgnoreStatus(touchEvent);
   }
 
   if (!mTouchStartDefaultPrevented) {
@@ -445,20 +461,22 @@ MetroInput::OnPointerPressed(UI::Core::I
   return S_OK;
 }
 
 void
 MetroInput::OnPointerPressedCallback()
 {
   nsEventStatus status = DeliverNextQueuedTouchEvent();
   mTouchStartDefaultPrevented = (nsEventStatus_eConsumeNoDefault == status);
-  // If content cancelled the first touchstart don't generate any gesture based
-  // input - clear the recognizer state without sending any events.
   if (mTouchStartDefaultPrevented) {
+    // If content canceled the first touchstart don't generate any gesture based
+    // input - clear the recognizer state without sending any events.
     mGestureRecognizer->CompleteGesture();
+    // Let the apz know content wants to consume touch events.
+    mWidget->ApzContentConsumingTouch();
   }
 }
 
 // This event is raised when the user moves the mouse, moves a pen that is
 // in contact with the surface, or moves a finger that is in contact with
 // a touch screen.
 HRESULT
 MetroInput::OnPointerMoved(UI::Core::ICoreWindow* aSender,
@@ -546,17 +564,24 @@ MetroInput::OnPointerMoved(UI::Core::ICo
 
   return S_OK;
 }
 
 void
 MetroInput::OnFirstPointerMoveCallback()
 {
   nsEventStatus status = DeliverNextQueuedTouchEvent();
+  mCancelable = false;
   mTouchMoveDefaultPrevented = (nsEventStatus_eConsumeNoDefault == status);
+  // Let the apz know whether content wants to consume touch events
+  if (mTouchMoveDefaultPrevented) {
+    mWidget->ApzContentConsumingTouch();
+  } else if (!mTouchMoveDefaultPrevented && !mTouchStartDefaultPrevented) {
+    mWidget->ApzContentIgnoringTouch();
+  }
 }
 
 // This event is raised when the user lifts the left mouse button, lifts a
 // pen from the surface, or lifts her/his finger from a touch screen.
 HRESULT
 MetroInput::OnPointerReleased(UI::Core::ICoreWindow* aSender,
                               UI::Core::IPointerEventArgs* aArgs)
 {
@@ -1082,30 +1107,80 @@ MetroInput::DispatchAsyncTouchEventIgnor
   nsCOMPtr<nsIRunnable> runnable =
     NS_NewRunnableMethod(this, &MetroInput::DeliverNextQueuedTouchEvent);
   NS_DispatchToCurrentThread(runnable);
 }
 
 nsEventStatus
 MetroInput::DeliverNextQueuedTouchEvent()
 {
+  nsEventStatus status;
   nsTouchEvent* event = static_cast<nsTouchEvent*>(mInputEventQueue.PopFront());
   MOZ_ASSERT(event);
-  nsEventStatus status;
+
+  AutoDeleteEvent wrap(event);
+
+  /*
+   * We go through states here and make different decisions in each:
+   *
+   * 1) delivering first touchpoint touchstart or its first touchmove
+   *  Our callers (OnFirstPointerMoveCallback, OnPointerPressedCallback) will
+   *  check our result and set mTouchStartDefaultPrevented or
+   *  mTouchMoveDefaultPrevented appropriately. Deliver touch events to the apz
+   *  (ignoring return result) and to content and return the content event
+   *  status result to our caller.
+   * 2) mTouchStartDefaultPrevented or mTouchMoveDefaultPrevented are true
+   *  Deliver touch directly to content and bypass the apz. Our callers
+   *  handle calling cancel for the touch sequence on the apz.
+   * 3) mTouchStartDefaultPrevented and mTouchMoveDefaultPrevented are false
+   *  Deliver events to the apz. If the apz returns eConsumeNoDefault dispatch
+   *  a touchcancel to content and do not deliver any additional events there.
+   *  (If the apz is doing something with the events we can save ourselves
+   *  the overhead of delivering dom events.)
+   */
+
+  // Check if content called preventDefault on touchstart or first touchmove. If so
+  // send directly to content, do not forward to the apz.
+  if (mTouchStartDefaultPrevented || mTouchMoveDefaultPrevented) {
+    // continue delivering events to content
+    mWidget->DispatchEvent(event, status);
+    return status;
+  }
+
+  // Forward event data to apz. If the apz consumes the event, don't forward to
+  // content if this is not a cancelable event.
+  status = mWidget->ApzReceiveInputEvent(event);
+  if (!mCancelable && status == nsEventStatus_eConsumeNoDefault) {
+    if (!mTouchCancelSent) {
+      mTouchCancelSent = true;
+      DispatchTouchCancel();
+    }
+    return status;
+  }
+
+  // Deliver event to content
   mWidget->DispatchEvent(event, status);
-  // Deliver to the apz if content has *not* cancelled touchstart or the first touchmove.
-  if (!mTouchStartDefaultPrevented && !mTouchMoveDefaultPrevented && MetroWidget::sAPZC) {
-    MultiTouchInput inputData(*event);
-    MetroWidget::sAPZC->ReceiveInputEvent(inputData);
-  }
-  delete event;
   return status;
 }
 
 void
+MetroInput::DispatchTouchCancel()
+{
+  LogFunction();
+  // From the spec: The touch point or points that were removed must be
+  // included in the changedTouches attribute of the TouchEvent, and must
+  // not be included in the touches and targetTouches attributes. 
+  // (We are 'removing' all touch points that have been sent to content
+  // thus far.)
+  nsTouchEvent touchEvent(true, NS_TOUCH_CANCEL, mWidget.Get());
+  InitTouchEventTouchList(&touchEvent);
+  mWidget->DispatchEvent(&touchEvent, sThrowawayStatus);
+}
+
+void
 MetroInput::DispatchAsyncTouchEventWithCallback(nsTouchEvent* aEvent, void (MetroInput::*Callback)())
 {
   aEvent->time = ::GetMessageTime();
   mModifierKeyState.Update();
   mModifierKeyState.InitInputEvent(*aEvent);
   mInputEventQueue.Push(aEvent);
   nsCOMPtr<nsIRunnable> runnable =
     NS_NewRunnableMethod(this, Callback);
--- a/widget/windows/winrt/MetroInput.h
+++ b/widget/windows/winrt/MetroInput.h
@@ -191,16 +191,18 @@ private:
   //   session, then no default actions associated with the _touchmove_ events
   //   will be dispatched.  However, it is still possible that additional
   //   events will be generated based on the touchstart and touchend events.
   //   For example, a set of mousemove, mousedown, and mouseup events might
   //   be sent if a tap is detected.
   bool mTouchStartDefaultPrevented;
   bool mTouchMoveDefaultPrevented;
   bool mIsFirstTouchMove;
+  bool mCancelable;
+  bool mTouchCancelSent;
 
   // In the old Win32 way of doing things, we would receive a WM_TOUCH event
   // that told us the state of every touchpoint on the touch surface.  If
   // multiple touchpoints had moved since the last update we would learn
   // about all their movement simultaneously.
   //
   // In the new WinRT way of doing things, we receive a separate
   // PointerPressed/PointerMoved/PointerReleased event for each touchpoint
@@ -265,14 +267,15 @@ private:
   nsEventStatus DeliverNextQueuedTouchEvent();
 
   // Misc. specialty async callbacks
   void OnPointerPressedCallback();
   void OnFirstPointerMoveCallback();
 
   // Sync event dispatching
   void DispatchEventIgnoreStatus(nsGUIEvent *aEvent);
+  void DispatchTouchCancel();
 
-   nsDeque mInputEventQueue;
+  nsDeque mInputEventQueue;
   static nsEventStatus sThrowawayStatus;
 };
 
 } } }
--- a/widget/windows/winrt/MetroWidget.cpp
+++ b/widget/windows/winrt/MetroWidget.cpp
@@ -26,16 +26,17 @@
 #include "BasicLayers.h"
 #include "FrameMetrics.h"
 #include "Windows.Graphics.Display.h"
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 #include "UIABridgePrivate.h"
 #include "WinMouseScrollHandler.h"
+#include "InputData.h"
 
 using namespace Microsoft::WRL;
 using namespace Microsoft::WRL::Wrappers;
 
 using namespace mozilla;
 using namespace mozilla::widget;
 using namespace mozilla::layers;
 using namespace mozilla::widget::winrt;
@@ -973,16 +974,49 @@ CompositorParent* MetroWidget::NewCompos
     if (NS_SUCCEEDED(rv)) {
       observerService->AddObserver(this, "scroll-offset-changed", false);
     }
   }
 
   return compositor;
 }
 
+void
+MetroWidget::ApzContentConsumingTouch()
+{
+  LogFunction();
+  if (!MetroWidget::sAPZC) {
+    return;
+  }
+  MetroWidget::sAPZC->ContentReceivedTouch(mRootLayerTreeId, true);
+}
+
+void
+MetroWidget::ApzContentIgnoringTouch()
+{
+  LogFunction();
+  if (!MetroWidget::sAPZC) {
+    return;
+  }
+  MetroWidget::sAPZC->ContentReceivedTouch(mRootLayerTreeId, false);
+}
+
+nsEventStatus
+MetroWidget::ApzReceiveInputEvent(nsTouchEvent* aEvent)
+{
+  MOZ_ASSERT(aEvent);
+
+  if (!MetroWidget::sAPZC) {
+    return nsEventStatus_eIgnore;
+  }
+
+  MultiTouchInput inputData(*aEvent);
+  return MetroWidget::sAPZC->ReceiveInputEvent(inputData);
+}
+
 LayerManager*
 MetroWidget::GetLayerManager(PLayerTransactionChild* aShadowManager,
                              LayersBackend aBackendHint,
                              LayerManagerPersistence aPersistence,
                              bool* aAllowRetaining)
 {
   bool retaining = true;
 
--- a/widget/windows/winrt/MetroWidget.h
+++ b/widget/windows/winrt/MetroWidget.h
@@ -191,16 +191,20 @@ public:
   void SetTaskbarPreview(nsITaskbarWindowPreview *preview) { }
   WindowHook& GetWindowHook() { return mWindowHook; }
 
   void SetView(FrameworkView* aView);
   void FindMetroWindow();
   virtual void SetTransparencyMode(nsTransparencyMode aMode);
   virtual nsTransparencyMode GetTransparencyMode();
 
+  // APZ related apis
+  void ApzContentConsumingTouch();
+  void ApzContentIgnoringTouch();
+  nsEventStatus ApzReceiveInputEvent(nsTouchEvent* aEvent);
   nsresult RequestContentScroll();
   void RequestContentRepaintImplMainThread();
 
 protected:
   friend class FrameworkView;
 
   struct OleInitializeWrapper {
     HRESULT const hr;