Bug 942689 - Include modifer info in tap gesture events [r=roc,jimm,botond]
authorMatt Brubeck <mbrubeck@mozilla.com>
Mon, 25 Nov 2013 20:30:26 -0800
changeset 173060 c3110e9acf104a45940063c9551673bf54ab70f0
parent 173059 223dc2b3d90a05c086eae8bfdd58844fb3a573a2
child 173061 555b1691b0a4f445f34fcbfe6cf120ca0ba69e3b
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, jimm, botond
bugs942689
milestone28.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 942689 - Include modifer info in tap gesture events [r=roc,jimm,botond]
browser/metro/base/content/contenthandlers/Content.js
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
gfx/layers/ipc/AsyncPanZoomController.cpp
gfx/layers/ipc/GeckoContentController.h
gfx/layers/ipc/GestureEventListener.cpp
gfx/tests/gtest/TestAsyncPanZoomController.cpp
layout/ipc/RenderFrameParent.cpp
widget/InputData.h
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/AndroidJavaWrappers.cpp
widget/gonk/ParentProcessController.h
widget/windows/winrt/APZController.cpp
widget/windows/winrt/APZController.h
widget/xpwidgets/InputData.cpp
--- a/browser/metro/base/content/contenthandlers/Content.js
+++ b/browser/metro/base/content/contenthandlers/Content.js
@@ -220,17 +220,16 @@ let Content = {
     }
   },
 
   receiveMessage: function receiveMessage(aMessage) {
     if (this._debugEvents) Util.dumpLn("Content:", aMessage.name);
     let json = aMessage.json;
     let x = json.x;
     let y = json.y;
-    let modifiers = json.modifiers;
 
     switch (aMessage.name) {
       case "Browser:Blur":
         gFocusManager.clearFocus(content);
         break;
 
       case "Browser:CanUnload":
         let canUnload = docShell.contentViewer.permitUnload();
@@ -258,17 +257,17 @@ let Content = {
         break;
       }
 
       case "Browser:PanBegin":
         this._cancelTapHighlight();
         break;
 
       case "Gesture:SingleTap":
-        this._onSingleTap(json.x, json.y);
+        this._onSingleTap(json.x, json.y, json.modifiers);
         break;
 
       case "Gesture:DoubleTap":
         this._onDoubleTap(json.x, json.y);
         break;
     }
   },
 
@@ -368,20 +367,21 @@ let Content = {
         let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
         webNav.loadURI(content.location,
                        Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
                        null, null, null);
       }
     }
   },
 
-  _onSingleTap: function (aX, aY) {
+  _onSingleTap: function (aX, aY, aModifiers) {
     let utils = Util.getWindowUtils(content);
     for (let type of ["mousemove", "mousedown", "mouseup"]) {
-      utils.sendMouseEventToWindow(type, aX, aY, 0, 1, 0, true, 1.0, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
+      utils.sendMouseEventToWindow(type, aX, aY, 0, 1, aModifiers, true, 1.0,
+          Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
     }
   },
 
   _onDoubleTap: function (aX, aY) {
     if (this._isZoomedIn) {
       this._zoomOut();
       return;
     }
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -489,31 +489,32 @@ TabParent::UpdateDimensions(const nsRect
 void
 TabParent::UpdateFrame(const FrameMetrics& aFrameMetrics)
 {
   if (!mIsDestroyed) {
     unused << SendUpdateFrame(aFrameMetrics);
   }
 }
 
-void TabParent::HandleDoubleTap(const CSSIntPoint& aPoint)
+void TabParent::HandleDoubleTap(const CSSIntPoint& aPoint, int32_t aModifiers)
 {
   if (!mIsDestroyed) {
     unused << SendHandleDoubleTap(aPoint);
   }
 }
 
-void TabParent::HandleSingleTap(const CSSIntPoint& aPoint)
+void TabParent::HandleSingleTap(const CSSIntPoint& aPoint, int32_t aModifiers)
 {
+  // TODO Send the modifier data to TabChild for use in mouse events.
   if (!mIsDestroyed) {
     unused << SendHandleSingleTap(aPoint);
   }
 }
 
-void TabParent::HandleLongTap(const CSSIntPoint& aPoint)
+void TabParent::HandleLongTap(const CSSIntPoint& aPoint, int32_t aModifiers)
 {
   if (!mIsDestroyed) {
     unused << SendHandleLongTap(aPoint);
   }
 }
 
 void
 TabParent::Activate()
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -191,19 +191,19 @@ public:
 
     void LoadURL(nsIURI* aURI);
     // XXX/cjones: it's not clear what we gain by hiding these
     // message-sending functions under a layer of indirection and
     // eating the return values
     void Show(const nsIntSize& size);
     void UpdateDimensions(const nsRect& rect, const nsIntSize& size);
     void UpdateFrame(const layers::FrameMetrics& aFrameMetrics);
-    void HandleDoubleTap(const CSSIntPoint& aPoint);
-    void HandleSingleTap(const CSSIntPoint& aPoint);
-    void HandleLongTap(const CSSIntPoint& aPoint);
+    void HandleDoubleTap(const CSSIntPoint& aPoint, int32_t aModifiers);
+    void HandleSingleTap(const CSSIntPoint& aPoint, int32_t aModifiers);
+    void HandleLongTap(const CSSIntPoint& aPoint, int32_t aModifiers);
     void Activate();
     void Deactivate();
 
     bool MapEventCoordinatesForChildProcess(mozilla::WidgetEvent* aEvent);
     void MapEventCoordinatesForChildProcess(const LayoutDeviceIntPoint& aOffset,
                                             mozilla::WidgetEvent* aEvent);
 
     void SendMouseEvent(const nsAString& aType, float aX, float aY,
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -15,16 +15,17 @@
 #include "GestureEventListener.h"       // for GestureEventListener
 #include "InputData.h"                  // for MultiTouchInput, etc
 #include "Units.h"                      // for CSSRect, CSSPoint, etc
 #include "base/message_loop.h"          // for MessageLoop
 #include "base/task.h"                  // for NewRunnableMethod, etc
 #include "base/tracked.h"               // for FROM_HERE
 #include "gfxTypes.h"                   // for gfxFloat
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
+#include "mozilla/BasicEvents.h"        // for Modifiers, MODIFIER_*
 #include "mozilla/ClearOnShutdown.h"    // for ClearOnShutdown
 #include "mozilla/Constants.h"          // for M_PI
 #include "mozilla/EventForwards.h"      // for nsEventStatus_*
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/ReentrantMonitor.h"   // for ReentrantMonitorAutoEnter, etc
 #include "mozilla/StaticPtr.h"          // for StaticAutoPtr
 #include "mozilla/TimeStamp.h"          // for TimeDuration, TimeStamp
 #include "mozilla/dom/Touch.h"          // for Touch
@@ -38,16 +39,17 @@
 #include "mozilla/layers/Axis.h"        // for AxisX, AxisY, Axis, etc
 #include "mozilla/layers/GeckoContentController.h"
 #include "mozilla/layers/TaskThrottler.h"  // for TaskThrottler
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "nsAlgorithm.h"                // for clamped
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_WARNING
+#include "nsIDOMWindowUtils.h"          // for nsIDOMWindowUtils
 #include "nsISupportsImpl.h"
 #include "nsMathUtils.h"                // for NS_hypot
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsStyleConsts.h"
 #include "nsStyleStruct.h"              // for nsTimingFunction
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl, etc
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "nsTraceRefcnt.h"              // for MOZ_COUNT_CTOR, etc
@@ -64,16 +66,61 @@
            fm.mPresShellId, fm.mScrollId, \
            fm.mCompositionBounds.x, fm.mCompositionBounds.y, fm.mCompositionBounds.width, fm.mCompositionBounds.height, \
            fm.mDisplayPort.x, fm.mDisplayPort.y, fm.mDisplayPort.width, fm.mDisplayPort.height, \
            fm.mViewport.x, fm.mViewport.y, fm.mViewport.width, fm.mViewport.height, \
            fm.mScrollOffset.x, fm.mScrollOffset.y, \
            fm.mScrollableRect.x, fm.mScrollableRect.y, fm.mScrollableRect.width, fm.mScrollableRect.height, \
            fm.mDevPixelsPerCSSPixel.scale, fm.mResolution.scale, fm.mCumulativeResolution.scale, fm.mZoom.scale); \
 
+// Static helper functions
+namespace {
+
+int32_t
+WidgetModifiersToDOMModifiers(mozilla::Modifiers aModifiers)
+{
+  int32_t result = 0;
+  if (aModifiers & mozilla::MODIFIER_SHIFT) {
+    result |= nsIDOMWindowUtils::MODIFIER_SHIFT;
+  }
+  if (aModifiers & mozilla::MODIFIER_CONTROL) {
+    result |= nsIDOMWindowUtils::MODIFIER_CONTROL;
+  }
+  if (aModifiers & mozilla::MODIFIER_ALT) {
+    result |= nsIDOMWindowUtils::MODIFIER_ALT;
+  }
+  if (aModifiers & mozilla::MODIFIER_META) {
+    result |= nsIDOMWindowUtils::MODIFIER_META;
+  }
+  if (aModifiers & mozilla::MODIFIER_ALTGRAPH) {
+    result |= nsIDOMWindowUtils::MODIFIER_ALTGRAPH;
+  }
+  if (aModifiers & mozilla::MODIFIER_CAPSLOCK) {
+    result |= nsIDOMWindowUtils::MODIFIER_CAPSLOCK;
+  }
+  if (aModifiers & mozilla::MODIFIER_FN) {
+    result |= nsIDOMWindowUtils::MODIFIER_FN;
+  }
+  if (aModifiers & mozilla::MODIFIER_NUMLOCK) {
+    result |= nsIDOMWindowUtils::MODIFIER_NUMLOCK;
+  }
+  if (aModifiers & mozilla::MODIFIER_SCROLLLOCK) {
+    result |= nsIDOMWindowUtils::MODIFIER_SCROLLLOCK;
+  }
+  if (aModifiers & mozilla::MODIFIER_SYMBOLLOCK) {
+    result |= nsIDOMWindowUtils::MODIFIER_SYMBOLLOCK;
+  }
+  if (aModifiers & mozilla::MODIFIER_OS) {
+    result |= nsIDOMWindowUtils::MODIFIER_OS;
+  }
+  return result;
+}
+
+}
+
 using namespace mozilla::css;
 
 namespace mozilla {
 namespace layers {
 
 /**
  * Constant describing the tolerance in distance we use, multiplied by the
  * device DPI, before we start panning the screen. This is to prevent us from
@@ -704,58 +751,62 @@ nsEventStatus AsyncPanZoomController::On
 
 nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a long-press in state %d\n", this, mState);
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, mFrameMetrics.mZoom);
-    controller->HandleLongTap(gfx::RoundedToInt(point));
+    int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
+    controller->HandleLongTap(gfx::RoundedToInt(point), modifiers);
     return nsEventStatus_eConsumeNoDefault;
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a single-tap-up in state %d\n", this, mState);
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller && !mAllowZoom) {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, mFrameMetrics.mZoom);
-    controller->HandleSingleTap(gfx::RoundedToInt(point));
+    int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
+    controller->HandleSingleTap(gfx::RoundedToInt(point), modifiers);
     return nsEventStatus_eConsumeNoDefault;
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a single-tap-confirmed in state %d\n", this, mState);
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   // If zooming is disabled, we handle this in OnSingleTapUp
   if (controller && mAllowZoom) {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, mFrameMetrics.mZoom);
-    controller->HandleSingleTap(gfx::RoundedToInt(point));
+    int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
+    controller->HandleSingleTap(gfx::RoundedToInt(point), modifiers);
     return nsEventStatus_eConsumeNoDefault;
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a double-tap in state %d\n", this, mState);
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     if (mAllowZoom) {
       CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, mFrameMetrics.mZoom);
-      controller->HandleDoubleTap(gfx::RoundedToInt(point));
+      int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
+      controller->HandleDoubleTap(gfx::RoundedToInt(point), modifiers);
     }
 
     return nsEventStatus_eConsumeNoDefault;
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnCancelTap(const TapGestureInput& aEvent) {
--- a/gfx/layers/ipc/GeckoContentController.h
+++ b/gfx/layers/ipc/GeckoContentController.h
@@ -29,30 +29,30 @@ public:
   virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) = 0;
 
   /**
    * Requests handling of a double tap. |aPoint| is in CSS pixels, relative to
    * the current scroll offset. This should eventually round-trip back to
    * AsyncPanZoomController::ZoomToRect with the dimensions that we want to zoom
    * to.
    */
-  virtual void HandleDoubleTap(const CSSIntPoint& aPoint) = 0;
+  virtual void HandleDoubleTap(const CSSIntPoint& aPoint, int32_t aModifiers) = 0;
 
   /**
    * Requests handling a single tap. |aPoint| is in CSS pixels, relative to the
    * current scroll offset. This should simulate and send to content a mouse
    * button down, then mouse button up at |aPoint|.
    */
-  virtual void HandleSingleTap(const CSSIntPoint& aPoint) = 0;
+  virtual void HandleSingleTap(const CSSIntPoint& aPoint, int32_t aModifiers) = 0;
 
   /**
    * Requests handling a long tap. |aPoint| is in CSS pixels, relative to the
    * current scroll offset.
    */
-  virtual void HandleLongTap(const CSSIntPoint& aPoint) = 0;
+  virtual void HandleLongTap(const CSSIntPoint& aPoint, int32_t aModifiers) = 0;
 
   /**
    * Requests sending a mozbrowserasyncscroll domevent to embedder.
    * |aContentRect| is in CSS pixels, relative to the current cssPage.
    * |aScrollableSize| is the current content width/height in CSS pixels.
    */
   virtual void SendAsyncScrollDOMEvent(bool aIsRoot,
                                        const CSSRect &aContentRect,
--- a/gfx/layers/ipc/GestureEventListener.cpp
+++ b/gfx/layers/ipc/GestureEventListener.cpp
@@ -35,17 +35,17 @@ static const uint32_t MAX_TAP_TIME = 300
 static const float PINCH_START_THRESHOLD = 35.0f;
 
 GestureEventListener::GestureEventListener(AsyncPanZoomController* aAsyncPanZoomController)
   : mAsyncPanZoomController(aAsyncPanZoomController),
     mState(GESTURE_NONE),
     mSpanChange(0.0f),
     mTapStartTime(0),
     mLastTapEndTime(0),
-    mLastTouchInput(MultiTouchInput::MULTITOUCH_START, 0)
+    mLastTouchInput(MultiTouchInput::MULTITOUCH_START, 0, 0)
 {
 }
 
 GestureEventListener::~GestureEventListener()
 {
 }
 
 nsEventStatus GestureEventListener::HandleInputEvent(const InputData& aEvent)
@@ -221,31 +221,33 @@ nsEventStatus GestureEventListener::Hand
       // are off the screen.
     case GESTURE_WAITING_PINCH: {
       mSpanChange += fabsf(currentSpan - mPreviousSpan);
       if (mSpanChange > PINCH_START_THRESHOLD) {
         PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_START,
                                      aEvent.mTime,
                                      focusPoint,
                                      currentSpan,
-                                     currentSpan);
+                                     currentSpan,
+                                     aEvent.modifiers);
 
         mAsyncPanZoomController->HandleInputEvent(pinchEvent);
 
         mState = GESTURE_PINCH;
       }
 
       break;
     }
     case GESTURE_PINCH: {
       PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_SCALE,
                                    aEvent.mTime,
                                    focusPoint,
                                    currentSpan,
-                                   mPreviousSpan);
+                                   mPreviousSpan,
+                                   aEvent.modifiers);
 
       mAsyncPanZoomController->HandleInputEvent(pinchEvent);
       break;
     }
     default:
       // What?
       break;
     }
@@ -253,17 +255,18 @@ nsEventStatus GestureEventListener::Hand
     mPreviousSpan = currentSpan;
 
     rv = nsEventStatus_eConsumeNoDefault;
   } else if (mState == GESTURE_PINCH) {
     PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
                                  aEvent.mTime,
                                  ScreenPoint(),  // may change below
                                  1.0f,           // may change below
-                                 1.0f);          // may change below
+                                 1.0f,           // may change below
+                                 aEvent.modifiers);
 
     if (mTouches.Length() > 0) {
       // Pinch is changing to pan. APZC will start a pan at mFocusPoint
       // (which isn't really a focus point in this case...).
       pinchEvent.mFocusPoint = mTouches[0].mScreenPoint;
     } else {
       // Pinch is ending, no pan to follow. APZC will check for the spans
       // being negative.
@@ -281,29 +284,32 @@ nsEventStatus GestureEventListener::Hand
     mTouches.Clear();
   }
 
   return rv;
 }
 
 nsEventStatus GestureEventListener::HandleSingleTapUpEvent(const MultiTouchInput& aEvent)
 {
-  TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_UP, aEvent.mTime, aEvent.mTouches[0].mScreenPoint);
+  TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_UP, aEvent.mTime,
+      aEvent.mTouches[0].mScreenPoint, aEvent.modifiers);
   return mAsyncPanZoomController->HandleInputEvent(tapEvent);
 }
 
 nsEventStatus GestureEventListener::HandleSingleTapConfirmedEvent(const MultiTouchInput& aEvent)
 {
-  TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_CONFIRMED, aEvent.mTime, aEvent.mTouches[0].mScreenPoint);
+  TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_CONFIRMED, aEvent.mTime,
+      aEvent.mTouches[0].mScreenPoint, aEvent.modifiers);
   return mAsyncPanZoomController->HandleInputEvent(tapEvent);
 }
 
 nsEventStatus GestureEventListener::HandleLongTapEvent(const MultiTouchInput& aEvent)
 {
-  TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_LONG, aEvent.mTime, aEvent.mTouches[0].mScreenPoint);
+  TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_LONG, aEvent.mTime,
+      aEvent.mTouches[0].mScreenPoint, aEvent.modifiers);
   return mAsyncPanZoomController->HandleInputEvent(tapEvent);
 }
 
 nsEventStatus GestureEventListener::HandleTapCancel(const MultiTouchInput& aEvent)
 {
   mTapStartTime = 0;
 
   switch (mState)
@@ -320,17 +326,18 @@ nsEventStatus GestureEventListener::Hand
     break;
   }
 
   return nsEventStatus_eConsumeDoDefault;
 }
 
 nsEventStatus GestureEventListener::HandleDoubleTap(const MultiTouchInput& aEvent)
 {
-  TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_DOUBLE, aEvent.mTime, aEvent.mTouches[0].mScreenPoint);
+  TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_DOUBLE, aEvent.mTime,
+      aEvent.mTouches[0].mScreenPoint, aEvent.modifiers);
   return mAsyncPanZoomController->HandleInputEvent(tapEvent);
 }
 
 void GestureEventListener::TimeoutDoubleTap()
 {
   mDoubleTapTimeoutTask = nullptr;
   // If we haven't gotten another tap by now, reset the state and treat it as a
   // single tap. It couldn't have been a double tap.
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -20,19 +20,19 @@
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 using ::testing::_;
 
 class MockContentController : public GeckoContentController {
 public:
   MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
-  MOCK_METHOD1(HandleDoubleTap, void(const CSSIntPoint&));
-  MOCK_METHOD1(HandleSingleTap, void(const CSSIntPoint&));
-  MOCK_METHOD1(HandleLongTap, void(const CSSIntPoint&));
+  MOCK_METHOD2(HandleDoubleTap, void(const CSSIntPoint&, int32_t));
+  MOCK_METHOD2(HandleSingleTap, void(const CSSIntPoint&, int32_t));
+  MOCK_METHOD2(HandleLongTap, void(const CSSIntPoint&, int32_t));
   MOCK_METHOD3(SendAsyncScrollDOMEvent, void(bool aIsRoot, const CSSRect &aContentRect, const CSSSize &aScrollableSize));
   MOCK_METHOD2(PostDelayedTask, void(Task* aTask, int aDelayMs));
 };
 
 class TestAPZCContainerLayer : public ContainerLayer {
   public:
     TestAPZCContainerLayer()
       : ContainerLayer(nullptr, nullptr)
@@ -81,62 +81,65 @@ FrameMetrics TestFrameMetrics() {
 static
 void ApzcPan(AsyncPanZoomController* apzc, int& aTime, int aTouchStartY, int aTouchEndY) {
 
   const int TIME_BETWEEN_TOUCH_EVENT = 100;
   const int OVERCOME_TOUCH_TOLERANCE = 100;
   MultiTouchInput mti;
   nsEventStatus status;
 
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime);
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime, 0);
   aTime += TIME_BETWEEN_TOUCH_EVENT;
   // Make sure the move is large enough to not be handled as a tap
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY+OVERCOME_TOUCH_TOLERANCE), ScreenSize(0, 0), 0, 0));
   status = apzc->HandleInputEvent(mti);
   EXPECT_EQ(status, nsEventStatus_eConsumeNoDefault);
   // APZC should be in TOUCHING state
 
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime);
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, 0);
   aTime += TIME_BETWEEN_TOUCH_EVENT;
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY), ScreenSize(0, 0), 0, 0));
   status = apzc->HandleInputEvent(mti);
   EXPECT_EQ(status, nsEventStatus_eConsumeNoDefault);
   // APZC should be in PANNING, otherwise status != ConsumeNoDefault
 
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime);
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, 0);
   aTime += TIME_BETWEEN_TOUCH_EVENT;
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0));
   status = apzc->HandleInputEvent(mti);
   EXPECT_EQ(status, nsEventStatus_eConsumeNoDefault);
 
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime);
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime, 0);
   aTime += TIME_BETWEEN_TOUCH_EVENT;
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0));
   status = apzc->HandleInputEvent(mti);
 }
 
 static void
 ApzcPinch(AsyncPanZoomController* aApzc, int aFocusX, int aFocusY, float aScale) {
   aApzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_START,
                                             0,
                                             ScreenPoint(aFocusX, aFocusY),
                                             10.0,
-                                            10.0));
+                                            10.0,
+                                            0));
   aApzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE,
                                             0,
                                             ScreenPoint(aFocusX, aFocusY),
                                             10.0 * aScale,
-                                            10.0));
+                                            10.0,
+                                            0));
   aApzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_END,
                                             0,
                                             ScreenPoint(aFocusX, aFocusY),
                                             // note: negative values here tell APZC
                                             //       not to turn the pinch into a pan
                                             -1.0,
-                                            -1.0));
+                                            -1.0,
+                                            0));
 }
 
 TEST(AsyncPanZoomController, Constructor) {
   // RefCounted class can't live in the stack
   nsRefPtr<MockContentController> mcc = new MockContentController();
   nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
   apzc->SetFrameMetrics(TestFrameMetrics());
 }
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -509,64 +509,67 @@ public:
     // We always need to post requests into the "UI thread" otherwise the
     // requests may get processed out of order.
     mUILoop->PostTask(
       FROM_HERE,
       NewRunnableMethod(this, &RemoteContentController::DoRequestContentRepaint,
                         aFrameMetrics));
   }
 
-  virtual void HandleDoubleTap(const CSSIntPoint& aPoint) MOZ_OVERRIDE
+  virtual void HandleDoubleTap(const CSSIntPoint& aPoint,
+                               int32_t aModifiers) MOZ_OVERRIDE
   {
     if (MessageLoop::current() != mUILoop) {
       // We have to send this message from the "UI thread" (main
       // thread).
       mUILoop->PostTask(
         FROM_HERE,
         NewRunnableMethod(this, &RemoteContentController::HandleDoubleTap,
-                          aPoint));
+                          aPoint, aModifiers));
       return;
     }
     if (mRenderFrame) {
       TabParent* browser = static_cast<TabParent*>(mRenderFrame->Manager());
-      browser->HandleDoubleTap(aPoint);
+      browser->HandleDoubleTap(aPoint, aModifiers);
     }
   }
 
-  virtual void HandleSingleTap(const CSSIntPoint& aPoint) MOZ_OVERRIDE
+  virtual void HandleSingleTap(const CSSIntPoint& aPoint,
+                               int32_t aModifiers) MOZ_OVERRIDE
   {
     if (MessageLoop::current() != mUILoop) {
       // We have to send this message from the "UI thread" (main
       // thread).
       mUILoop->PostTask(
         FROM_HERE,
         NewRunnableMethod(this, &RemoteContentController::HandleSingleTap,
-                          aPoint));
+                          aPoint, aModifiers));
       return;
     }
     if (mRenderFrame) {
       TabParent* browser = static_cast<TabParent*>(mRenderFrame->Manager());
-      browser->HandleSingleTap(aPoint);
+      browser->HandleSingleTap(aPoint, aModifiers);
     }
   }
 
-  virtual void HandleLongTap(const CSSIntPoint& aPoint) MOZ_OVERRIDE
+  virtual void HandleLongTap(const CSSIntPoint& aPoint,
+                             int32_t aModifiers) MOZ_OVERRIDE
   {
     if (MessageLoop::current() != mUILoop) {
       // We have to send this message from the "UI thread" (main
       // thread).
       mUILoop->PostTask(
         FROM_HERE,
         NewRunnableMethod(this, &RemoteContentController::HandleLongTap,
-                          aPoint));
+                          aPoint, aModifiers));
       return;
     }
     if (mRenderFrame) {
       TabParent* browser = static_cast<TabParent*>(mRenderFrame->Manager());
-      browser->HandleLongTap(aPoint);
+      browser->HandleLongTap(aPoint, aModifiers);
     }
   }
 
   void ClearRenderFrame() { mRenderFrame = nullptr; }
 
   virtual void SendAsyncScrollDOMEvent(bool aIsRoot,
                                        const CSSRect& aContentRect,
                                        const CSSSize& aContentSize) MOZ_OVERRIDE
--- a/widget/InputData.h
+++ b/widget/InputData.h
@@ -44,28 +44,31 @@ public:
   InputType mInputType;
   // Time in milliseconds that this data is relevant to. This only really
   // matters when this data is used as an event. We use uint32_t instead of
   // TimeStamp because it is easier to convert from WidgetInputEvent. The time
   // is platform-specific but it in the case of B2G and Fennec it is since
   // startup.
   uint32_t mTime;
 
+  Modifiers modifiers;
+
   INPUTDATA_AS_CHILD_TYPE(MultiTouchInput, MULTITOUCH_INPUT)
   INPUTDATA_AS_CHILD_TYPE(PinchGestureInput, PINCHGESTURE_INPUT)
   INPUTDATA_AS_CHILD_TYPE(TapGestureInput, TAPGESTURE_INPUT)
 
   InputData()
   {
   }
 
 protected:
-  InputData(InputType aInputType, uint32_t aTime)
+  InputData(InputType aInputType, uint32_t aTime, Modifiers aModifiers)
     : mInputType(aInputType),
-      mTime(aTime)
+      mTime(aTime),
+      modifiers(aModifiers)
   {
 
 
   }
 };
 
 /**
  * Data container for a single touch input. Similar to dom::Touch, but used in
@@ -143,18 +146,18 @@ public:
     MULTITOUCH_START,
     MULTITOUCH_MOVE,
     MULTITOUCH_END,
     MULTITOUCH_ENTER,
     MULTITOUCH_LEAVE,
     MULTITOUCH_CANCEL
   };
 
-  MultiTouchInput(MultiTouchType aType, uint32_t aTime)
-    : InputData(MULTITOUCH_INPUT, aTime),
+  MultiTouchInput(MultiTouchType aType, uint32_t aTime, Modifiers aModifiers)
+    : InputData(MULTITOUCH_INPUT, aTime, aModifiers),
       mType(aType)
   {
 
 
   }
 
   MultiTouchInput()
   {
@@ -188,18 +191,19 @@ public:
     PINCHGESTURE_SCALE,
     PINCHGESTURE_END
   };
 
   PinchGestureInput(PinchGestureType aType,
                     uint32_t aTime,
                     const ScreenPoint& aFocusPoint,
                     float aCurrentSpan,
-                    float aPreviousSpan)
-    : InputData(PINCHGESTURE_INPUT, aTime),
+                    float aPreviousSpan,
+                    Modifiers aModifiers)
+    : InputData(PINCHGESTURE_INPUT, aTime, aModifiers),
       mType(aType),
       mFocusPoint(aFocusPoint),
       mCurrentSpan(aCurrentSpan),
       mPreviousSpan(aPreviousSpan)
   {
 
 
   }
@@ -236,18 +240,21 @@ public:
   {
     TAPGESTURE_LONG,
     TAPGESTURE_UP,
     TAPGESTURE_CONFIRMED,
     TAPGESTURE_DOUBLE,
     TAPGESTURE_CANCEL
   };
 
-  TapGestureInput(TapGestureType aType, uint32_t aTime, const ScreenIntPoint& aPoint)
-    : InputData(TAPGESTURE_INPUT, aTime),
+  TapGestureInput(TapGestureType aType,
+                  uint32_t aTime,
+                  const ScreenIntPoint& aPoint,
+                  Modifiers aModifiers)
+    : InputData(TAPGESTURE_INPUT, aTime, aModifiers),
       mType(aType),
       mPoint(aPoint)
   {
 
 
   }
 
   TapGestureType mType;
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -1974,33 +1974,34 @@ AndroidBridge::RequestContentRepaint(con
 
     CSSToScreenScale resolution = aFrameMetrics.mZoom;
     ScreenRect dp = (aFrameMetrics.mDisplayPort + aFrameMetrics.mScrollOffset) * resolution;
 
     mNativePanZoomController->RequestContentRepaintWrapper(dp.x, dp.y, dp.width, dp.height, resolution.scale);
 }
 
 void
-AndroidBridge::HandleDoubleTap(const CSSIntPoint& aPoint)
+AndroidBridge::HandleDoubleTap(const CSSIntPoint& aPoint, int32_t aModifiers)
 {
     nsCString data = nsPrintfCString("{ \"x\": %d, \"y\": %d }", aPoint.x, aPoint.y);
     nsAppShell::gAppShell->PostEvent(AndroidGeckoEvent::MakeBroadcastEvent(
             NS_LITERAL_CSTRING("Gesture:DoubleTap"), data));
 }
 
 void
-AndroidBridge::HandleSingleTap(const CSSIntPoint& aPoint)
+AndroidBridge::HandleSingleTap(const CSSIntPoint& aPoint, int32_t aModifiers)
 {
+    // TODO Send the modifier data to Gecko for use in mouse events.
     nsCString data = nsPrintfCString("{ \"x\": %d, \"y\": %d }", aPoint.x, aPoint.y);
     nsAppShell::gAppShell->PostEvent(AndroidGeckoEvent::MakeBroadcastEvent(
             NS_LITERAL_CSTRING("Gesture:SingleTap"), data));
 }
 
 void
-AndroidBridge::HandleLongTap(const CSSIntPoint& aPoint)
+AndroidBridge::HandleLongTap(const CSSIntPoint& aPoint, int32_t aModifiers)
 {
     nsCString data = nsPrintfCString("{ \"x\": %d, \"y\": %d }", aPoint.x, aPoint.y);
     nsAppShell::gAppShell->PostEvent(AndroidGeckoEvent::MakeBroadcastEvent(
             NS_LITERAL_CSTRING("Gesture:LongPress"), data));
 }
 
 void
 AndroidBridge::SendAsyncScrollDOMEvent(bool aIsRoot,
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -401,19 +401,19 @@ private:
     // thread, which is the Java UI thread), so we don't need to do locking
     // to touch it
     nsTArray<DelayedTask*> mDelayedTaskQueue;
 
 public:
     NativePanZoomController* SetNativePanZoomController(jobject obj);
     // GeckoContentController methods
     void RequestContentRepaint(const mozilla::layers::FrameMetrics& aFrameMetrics) MOZ_OVERRIDE;
-    void HandleDoubleTap(const CSSIntPoint& aPoint) MOZ_OVERRIDE;
-    void HandleSingleTap(const CSSIntPoint& aPoint) MOZ_OVERRIDE;
-    void HandleLongTap(const CSSIntPoint& aPoint) MOZ_OVERRIDE;
+    void HandleDoubleTap(const CSSIntPoint& aPoint, int32_t aModifiers) MOZ_OVERRIDE;
+    void HandleSingleTap(const CSSIntPoint& aPoint, int32_t aModifiers) MOZ_OVERRIDE;
+    void HandleLongTap(const CSSIntPoint& aPoint, int32_t aModifiers) MOZ_OVERRIDE;
     void SendAsyncScrollDOMEvent(bool aIsRoot,
                                  const CSSRect& aContentRect,
                                  const CSSSize& aScrollableSize) MOZ_OVERRIDE;
     void PostDelayedTask(Task* aTask, int aDelayMs) MOZ_OVERRIDE;
     int64_t RunDelayedTasks();
 };
 
 class AutoJObject {
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -3,16 +3,17 @@
  * 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 "AndroidJavaWrappers.h"
 #include "AndroidBridge.h"
 #include "AndroidBridgeUtilities.h"
 #include "nsIDOMKeyEvent.h"
 #include "nsIWidget.h"
+#include "mozilla/BasicEvents.h"
 #include "mozilla/TouchEvents.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::widget::android;
 
 jclass AndroidGeckoEvent::jGeckoEventClass = 0;
 jfieldID AndroidGeckoEvent::jActionField = 0;
@@ -701,17 +702,29 @@ AndroidGeckoEvent::MakeMultiTouchInput(n
         }
         case AndroidMotionEvent::ACTION_OUTSIDE:
         case AndroidMotionEvent::ACTION_CANCEL: {
             type = MultiTouchInput::MULTITOUCH_CANCEL;
             break;
         }
     }
 
-    MultiTouchInput event(type, Time());
+    MultiTouchInput event(type, Time(), 0);
+    if (IsCtrlPressed()) {
+      event.modifiers |= MODIFIER_CONTROL;
+    }
+    if (IsAltPressed()) {
+      event.modifiers |= MODIFIER_ALT;
+    }
+    if (IsShiftPressed()) {
+      event.modifiers |= MODIFIER_SHIFT;
+    }
+    if (IsMetaPressed()) {
+      event.modifiers |= MODIFIER_META;
+    }
 
     if (type < 0) {
         // An event we don't know about
         return event;
     }
 
     const nsIntPoint& offset = widget->WidgetToScreenOffset();
     event.mTouches.SetCapacity(endIndex - startIndex);
--- a/widget/gonk/ParentProcessController.h
+++ b/widget/gonk/ParentProcessController.h
@@ -15,19 +15,19 @@ class ParentProcessController : public m
 {
     typedef mozilla::layers::FrameMetrics FrameMetrics;
 
 public:
     virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) MOZ_OVERRIDE;
     virtual void PostDelayedTask(Task* aTask, int aDelayMs) MOZ_OVERRIDE;
 
     // No-ops
-    virtual void HandleDoubleTap(const CSSIntPoint& aPoint) MOZ_OVERRIDE {}
-    virtual void HandleSingleTap(const CSSIntPoint& aPoint) MOZ_OVERRIDE {}
-    virtual void HandleLongTap(const CSSIntPoint& aPoint) MOZ_OVERRIDE {}
+    virtual void HandleDoubleTap(const CSSIntPoint& aPoint, int32_t aModifiers) MOZ_OVERRIDE {}
+    virtual void HandleSingleTap(const CSSIntPoint& aPoint, int32_t aModifiers) MOZ_OVERRIDE {}
+    virtual void HandleLongTap(const CSSIntPoint& aPoint, int32_t aModifiers) MOZ_OVERRIDE {}
     virtual void SendAsyncScrollDOMEvent(bool aIsRoot,
                                          const CSSRect &aContentRect,
                                          const CSSSize &aScrollableSize) MOZ_OVERRIDE {}
 };
 
 }
 }
 
--- a/widget/windows/winrt/APZController.cpp
+++ b/widget/windows/winrt/APZController.cpp
@@ -276,33 +276,35 @@ APZController::UpdateScrollOffset(const 
     WinUtils::Log("Skipping UpdateScrollOffset");
 #endif
     return;
   }
   sAPZC->UpdateScrollOffset(aScrollLayerId, aScrollOffset);
 }
 
 void
-APZController::HandleDoubleTap(const CSSIntPoint& aPoint)
+APZController::HandleDoubleTap(const CSSIntPoint& aPoint, int32_t aModifiers)
 {
-  NS_ConvertASCIItoUTF16 data(nsPrintfCString("{ \"x\": %d, \"y\": %d }",
-                              (int32_t)aPoint.x, (int32_t)aPoint.y));
+  NS_ConvertASCIItoUTF16 data(
+      nsPrintfCString("{ \"x\": %d, \"y\": %d, \"modifiers\": %d }",
+      (int32_t)aPoint.x, (int32_t)aPoint.y, aModifiers));
   MetroUtils::FireObserver("Gesture:DoubleTap", data.get());
 }
 
 void
-APZController::HandleSingleTap(const CSSIntPoint& aPoint)
+APZController::HandleSingleTap(const CSSIntPoint& aPoint, int32_t aModifiers)
 {
-  NS_ConvertASCIItoUTF16 data(nsPrintfCString("{ \"x\": %d, \"y\": %d }",
-                              (int32_t)aPoint.x, (int32_t)aPoint.y));
+  NS_ConvertASCIItoUTF16 data(
+      nsPrintfCString("{ \"x\": %d, \"y\": %d, \"modifiers\": %d }",
+      (int32_t)aPoint.x, (int32_t)aPoint.y, aModifiers));
   MetroUtils::FireObserver("Gesture:SingleTap", data.get());
 }
 
 void
-APZController::HandleLongTap(const CSSIntPoint& aPoint)
+APZController::HandleLongTap(const CSSIntPoint& aPoint, int32_t aModifiers)
 {
 }
 
 // requests that we send a mozbrowserasyncscroll domevent. not in use.
 void
 APZController::SendAsyncScrollDOMEvent(bool aIsRoot,
                                        const CSSRect &aContentRect,
                                        const CSSSize &aScrollableSize)
--- a/widget/windows/winrt/APZController.h
+++ b/widget/windows/winrt/APZController.h
@@ -27,19 +27,19 @@ class APZController :
 public:
   APZController() :
     mWidgetListener(nullptr)
   {
   }
 
   // GeckoContentController interface
   virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics);
-  virtual void HandleDoubleTap(const mozilla::CSSIntPoint& aPoint);
-  virtual void HandleSingleTap(const mozilla::CSSIntPoint& aPoint);
-  virtual void HandleLongTap(const mozilla::CSSIntPoint& aPoint);
+  virtual void HandleDoubleTap(const mozilla::CSSIntPoint& aPoint, int32_t aModifiers);
+  virtual void HandleSingleTap(const mozilla::CSSIntPoint& aPoint, int32_t aModifiers);
+  virtual void HandleLongTap(const mozilla::CSSIntPoint& aPoint, int32_t aModifiers);
   virtual void SendAsyncScrollDOMEvent(bool aIsRoot, const mozilla::CSSRect &aContentRect, const mozilla::CSSSize &aScrollableSize);
   virtual void PostDelayedTask(Task* aTask, int aDelayMs);
   virtual void HandlePanBegin();
   virtual void HandlePanEnd();
   
   void SetWidgetListener(nsIWidgetListener* aWidgetListener);
   void UpdateScrollOffset(const mozilla::layers::ScrollableLayerGuid& aScrollLayerId, CSSIntPoint& aScrollOffset);
 
@@ -58,9 +58,9 @@ public:
   static nsRefPtr<mozilla::layers::APZCTreeManager> sAPZC;
 
 private:
   nsIWidgetListener* mWidgetListener;
   ScrollableLayerGuid mLastScrollLayerGuid;
   CSSIntPoint mLastScrollOffset;
 };
 
-} } }
\ No newline at end of file
+} } }
--- a/widget/xpwidgets/InputData.cpp
+++ b/widget/xpwidgets/InputData.cpp
@@ -11,17 +11,17 @@
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TouchEvents.h"
 
 namespace mozilla {
 
 using namespace dom;
 
 MultiTouchInput::MultiTouchInput(const WidgetTouchEvent& aTouchEvent)
-  : InputData(MULTITOUCH_INPUT, aTouchEvent.time)
+  : InputData(MULTITOUCH_INPUT, aTouchEvent.time, aTouchEvent.modifiers)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(),
                     "Can only copy from WidgetTouchEvent on main thread");
 
   switch (aTouchEvent.message) {
     case NS_TOUCH_START:
       mType = MULTITOUCH_START;
       break;
@@ -69,17 +69,17 @@ MultiTouchInput::MultiTouchInput(const W
 
 // This conversion from WidgetMouseEvent to MultiTouchInput is needed because on
 // the B2G emulator we can only receive mouse events, but we need to be able
 // to pan correctly. To do this, we convert the events into a format that the
 // panning code can handle. This code is very limited and only supports
 // SingleTouchData. It also sends garbage for the identifier, radius, force
 // and rotation angle.
 MultiTouchInput::MultiTouchInput(const WidgetMouseEvent& aMouseEvent)
-  : InputData(MULTITOUCH_INPUT, aMouseEvent.time)
+  : InputData(MULTITOUCH_INPUT, aMouseEvent.time, aMouseEvent.modifiers)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(),
                     "Can only copy from WidgetMouseEvent on main thread");
   switch (aMouseEvent.message) {
   case NS_MOUSE_BUTTON_DOWN:
     mType = MULTITOUCH_START;
     break;
   case NS_MOUSE_MOVE: