Bug 796275 - Context menu fires on wrong target out of process. r=cjones, a=blocking-basecamp
authorAndrea Marchesini <amarchesini@mozilla.com>
Thu, 08 Nov 2012 14:35:02 -0500
changeset 116799 91a6abdf9526f42840b47787d59ef5042ec68378
parent 116798 e132143c1df4ce90dbee677f3c5b1b89f5bdcf13
child 116800 782795b933e0ee31d88ff4190bc19dc03acb8fa9
push id1708
push userakeybl@mozilla.com
push dateMon, 19 Nov 2012 21:10:21 +0000
treeherdermozilla-beta@27b14fe50103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscjones, blocking-basecamp
bugs796275
milestone18.0a2
Bug 796275 - Context menu fires on wrong target out of process. r=cjones, a=blocking-basecamp
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
gfx/layers/ipc/AsyncPanZoomController.cpp
gfx/layers/ipc/GeckoContentController.h
gfx/layers/ipc/GestureEventListener.cpp
gfx/layers/ipc/GestureEventListener.h
layout/ipc/RenderFrameParent.cpp
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -302,16 +302,23 @@ child:
     /**
      * Requests handling of a single tap. |point| is in CSS pixels, relative to
      * the scroll offset. This message is expected to send a "mousedown" and
      * "mouseup" series of events at this point.
      */
     HandleSingleTap(nsIntPoint point);
 
     /**
+     * Requests handling of a long tap. |point| is in CSS pixels, relative to
+     * the scroll offset. This message is expected to send a "contextmenu"
+     * events at this point.
+     */
+    HandleLongTap(nsIntPoint point);
+
+    /**
      * Sending an activate message moves focus to the child.
      */
     Activate();
 
     Deactivate();
 
     /**
      * @see nsIDOMWindowUtils sendMouseEvent.
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1246,16 +1246,32 @@ TabChild::RecvHandleSingleTap(const nsIn
 
   RecvMouseEvent(NS_LITERAL_STRING("mousedown"), aPoint.x, aPoint.y, 0, 1, 0, false);
   RecvMouseEvent(NS_LITERAL_STRING("mouseup"), aPoint.x, aPoint.y, 0, 1, 0, false);
 
   return true;
 }
 
 bool
+TabChild::RecvHandleLongTap(const nsIntPoint& aPoint)
+{
+  if (!mCx || !mTabChildGlobal) {
+    return true;
+  }
+
+  RecvMouseEvent(NS_LITERAL_STRING("contextmenu"), aPoint.x, aPoint.y,
+                 2 /* Right button */,
+                 1 /* Click count */,
+                 0 /* Modifiers */,
+                 false /* Ignore root scroll frame */);
+
+  return true;
+}
+
+bool
 TabChild::RecvActivate()
 {
   nsCOMPtr<nsIWebBrowserFocus> browser = do_QueryInterface(mWebNav);
   browser->Activate();
   return true;
 }
 
 bool TabChild::RecvDeactivate()
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -195,16 +195,17 @@ public:
                                     const mozilla::dom::StructuredCloneData& aData);
 
     virtual bool RecvLoadURL(const nsCString& uri);
     virtual bool RecvShow(const nsIntSize& size);
     virtual bool RecvUpdateDimensions(const nsRect& rect, const nsIntSize& size);
     virtual bool RecvUpdateFrame(const mozilla::layers::FrameMetrics& aFrameMetrics);
     virtual bool RecvHandleDoubleTap(const nsIntPoint& aPoint);
     virtual bool RecvHandleSingleTap(const nsIntPoint& aPoint);
+    virtual bool RecvHandleLongTap(const nsIntPoint& aPoint);
     virtual bool RecvActivate();
     virtual bool RecvDeactivate();
     virtual bool RecvMouseEvent(const nsString& aType,
                                 const float&    aX,
                                 const float&    aY,
                                 const int32_t&  aButton,
                                 const int32_t&  aClickCount,
                                 const int32_t&  aModifiers,
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -261,16 +261,21 @@ void TabParent::HandleDoubleTap(const ns
   unused << SendHandleDoubleTap(aPoint);
 }
 
 void TabParent::HandleSingleTap(const nsIntPoint& aPoint)
 {
   unused << SendHandleSingleTap(aPoint);
 }
 
+void TabParent::HandleLongTap(const nsIntPoint& aPoint)
+{
+  unused << SendHandleLongTap(aPoint);
+}
+
 void
 TabParent::Activate()
 {
     unused << SendActivate();
 }
 
 void
 TabParent::Deactivate()
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -156,16 +156,17 @@ public:
     // 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 nsIntPoint& aPoint);
     void HandleSingleTap(const nsIntPoint& aPoint);
+    void HandleLongTap(const nsIntPoint& aPoint);
     void Activate();
     void Deactivate();
 
     void SendMouseEvent(const nsAString& aType, float aX, float aY,
                         int32_t aButton, int32_t aClickCount,
                         int32_t aModifiers, bool aIgnoreRootScrollFrame);
     void SendKeyEvent(const nsAString& aType, int32_t aKeyCode,
                       int32_t aCharCode, int32_t aModifiers,
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -532,17 +532,27 @@ nsEventStatus AsyncPanZoomController::On
     ScheduleComposite();
     RequestContentRepaint();
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent) {
-  // XXX: Implement this.
+  if (mGeckoContentController) {
+    MonitorAutoLock monitor(mMonitor);
+
+    gfxFloat resolution = CalculateResolution(mFrameMetrics).width;
+    gfx::Point point = WidgetSpaceToCompensatedViewportSpace(
+      gfx::Point(aEvent.mPoint.x, aEvent.mPoint.y),
+      resolution);
+    mGeckoContentController->HandleLongTap(nsIntPoint(NS_lround(point.x),
+                                                      NS_lround(point.y)));
+    return nsEventStatus_eConsumeNoDefault;
+  }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEvent) {
   if (mGeckoContentController) {
     MonitorAutoLock monitor(mMonitor);
 
     gfxFloat resolution = CalculateResolution(mFrameMetrics).width;
--- a/gfx/layers/ipc/GeckoContentController.h
+++ b/gfx/layers/ipc/GeckoContentController.h
@@ -33,16 +33,22 @@ public:
 
   /**
    * 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 nsIntPoint& aPoint) = 0;
 
+  /**
+   * Requests handling a long tap. |aPoint| is in CSS pixels, relative to the
+   * current scroll offset.
+   */
+  virtual void HandleLongTap(const nsIntPoint& aPoint) = 0;
+
   GeckoContentController() {}
   virtual ~GeckoContentController() {}
 };
 
 }
 }
 
 #endif // mozilla_layers_GeckoContentController_h
--- a/gfx/layers/ipc/GestureEventListener.cpp
+++ b/gfx/layers/ipc/GestureEventListener.cpp
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "base/basictypes.h"
 #include "base/thread.h"
 
 #include "GestureEventListener.h"
 #include "AsyncPanZoomController.h"
 
+#include "mozilla/Preferences.h"
+
 namespace mozilla {
 namespace layers {
 
 /**
  * Maximum time for a touch on the screen and corresponding lift of the finger
  * to be considered a tap. This also applies to double taps, except that it is
  * used twice.
  */
@@ -42,16 +44,21 @@ GestureEventListener::~GestureEventListe
 
 nsEventStatus GestureEventListener::HandleInputEvent(const InputData& aEvent)
 {
   if (aEvent.mInputType != MULTITOUCH_INPUT) {
     return nsEventStatus_eIgnore;
   }
 
   const MultiTouchInput& event = static_cast<const MultiTouchInput&>(aEvent);
+
+  // Cache the current event since it may become the single or long tap that we
+  // send.
+  mLastTouchInput = event;
+
   switch (event.mType)
   {
   case MultiTouchInput::MULTITOUCH_START:
   case MultiTouchInput::MULTITOUCH_ENTER: {
     for (size_t i = 0; i < event.mTouches.Length(); i++) {
       bool foundAlreadyExistingTouch = false;
       for (size_t j = 0; j < mTouches.Length(); j++) {
         if (mTouches[j].mIdentifier == event.mTouches[i].mIdentifier) {
@@ -70,16 +77,24 @@ nsEventStatus GestureEventListener::Hand
     }
 
     size_t length = mTouches.Length();
     if (length == 1) {
       mTapStartTime = event.mTime;
       mTouchStartPosition = event.mTouches[0].mScreenPoint;
       if (mState == GESTURE_NONE) {
         mState = GESTURE_WAITING_SINGLE_TAP;
+
+        mLongTapTimeoutTask =
+          NewRunnableMethod(this, &GestureEventListener::TimeoutLongTap);
+
+        MessageLoop::current()->PostDelayedTask(
+          FROM_HERE,
+          mLongTapTimeoutTask,
+          Preferences::GetInt("ui.click_hold_context_menus.delay", 500));
       }
     } else if (length == 2) {
       // Another finger has been added; it can't be a tap anymore.
       HandleTapCancel(event);
     }
 
     break;
   }
@@ -124,27 +139,25 @@ nsEventStatus GestureEventListener::Hand
     if (event.mTime - mTapStartTime <= MAX_TAP_TIME) {
       if (mState == GESTURE_WAITING_DOUBLE_TAP) {
         mDoubleTapTimeoutTask->Cancel();
 
         // We were waiting for a double tap and it has arrived.
         HandleDoubleTap(event);
         mState = GESTURE_NONE;
       } else if (mState == GESTURE_WAITING_SINGLE_TAP) {
+        mLongTapTimeoutTask->Cancel();
+
         HandleSingleTapUpEvent(event);
 
         // We were not waiting for anything but a single tap has happened that
         // may turn into a double tap. Wait a while and if it doesn't turn into
         // a double tap, send a single tap instead.
         mState = GESTURE_WAITING_DOUBLE_TAP;
 
-        // Cache the current event since it may become the single tap that we
-        // send.
-        mLastTouchInput = event;
-
         mDoubleTapTimeoutTask =
           NewRunnableMethod(this, &GestureEventListener::TimeoutDoubleTap);
 
         MessageLoop::current()->PostDelayedTask(
           FROM_HERE,
           mDoubleTapTimeoutTask,
           MAX_TAP_TIME);
       }
@@ -255,23 +268,33 @@ nsEventStatus GestureEventListener::Hand
 }
 
 nsEventStatus GestureEventListener::HandleSingleTapConfirmedEvent(const MultiTouchInput& aEvent)
 {
   TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_CONFIRMED, aEvent.mTime, aEvent.mTouches[0].mScreenPoint);
   return mAsyncPanZoomController->ReceiveInputEvent(tapEvent);
 }
 
+nsEventStatus GestureEventListener::HandleLongTapEvent(const MultiTouchInput& aEvent)
+{
+  TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_LONG, aEvent.mTime, aEvent.mTouches[0].mScreenPoint);
+  return mAsyncPanZoomController->ReceiveInputEvent(tapEvent);
+}
+
 nsEventStatus GestureEventListener::HandleTapCancel(const MultiTouchInput& aEvent)
 {
   mTapStartTime = 0;
 
   switch (mState)
   {
   case GESTURE_WAITING_SINGLE_TAP:
+    mLongTapTimeoutTask->Cancel();
+    mState = GESTURE_NONE;
+    break;
+
   case GESTURE_WAITING_DOUBLE_TAP:
     mState = GESTURE_NONE;
     break;
   default:
     break;
   }
 
   return nsEventStatus_eConsumeDoDefault;
@@ -289,16 +312,26 @@ void GestureEventListener::TimeoutDouble
   // single tap. It couldn't have been a double tap.
   if (mState == GESTURE_WAITING_DOUBLE_TAP) {
     mState = GESTURE_NONE;
 
     HandleSingleTapConfirmedEvent(mLastTouchInput);
   }
 }
 
+void GestureEventListener::TimeoutLongTap()
+{
+  // If the tap has not been released, this is a long press.
+  if (mState == GESTURE_WAITING_SINGLE_TAP) {
+    mState = GESTURE_NONE;
+
+    HandleLongTapEvent(mLastTouchInput);
+  }
+}
+
 AsyncPanZoomController* GestureEventListener::GetAsyncPanZoomController() {
   return mAsyncPanZoomController;
 }
 
 void GestureEventListener::CancelGesture() {
   mTouches.Clear();
   mState = GESTURE_NONE;
 }
--- a/gfx/layers/ipc/GestureEventListener.h
+++ b/gfx/layers/ipc/GestureEventListener.h
@@ -100,16 +100,22 @@ protected:
    * Attempts to handle a single tap confirmation. This is what will actually
    * open links, etc. In general, this will not attempt to block the touch event
    * from being passed along to AsyncPanZoomController since APZC needs to know
    * about touches ending (and we only know if a touch was a tap once it ends).
    */
   nsEventStatus HandleSingleTapConfirmedEvent(const MultiTouchInput& aEvent);
 
   /**
+   * Attempts to handle a long tap confirmation. This is what will use
+   * for context menu.
+   */
+  nsEventStatus HandleLongTapEvent(const MultiTouchInput& aEvent);
+
+  /**
    * Attempts to handle a tap event cancellation. This happens when we think
    * something was a tap but it actually wasn't. In general, this will not
    * attempt to block the touch event from being passed along to
    * AsyncPanZoomController since APZC needs to know about touches ending (and
    * we only know if a touch was a tap once it ends).
    */
   nsEventStatus HandleTapCancel(const MultiTouchInput& aEvent);
 
@@ -125,16 +131,21 @@ protected:
   /**
    * Times out a single tap we think may be turned into a double tap. This will
    * also send a single tap if we're still in the "GESTURE_WAITING_DOUBLE_TAP"
    * state when this is called. This should be called a short time after a
    * single tap is detected, and the delay on it should be enough that the user
    * has time to tap again (to make a double tap).
    */
   void TimeoutDoubleTap();
+  /**
+   * Times out a long tap. This should be called a 'long' time after a single
+   * tap is detected.
+   */
+  void TimeoutLongTap();
 
   nsRefPtr<AsyncPanZoomController> mAsyncPanZoomController;
 
   /**
    * Array containing all active touches. When a touch happens it, gets added to
    * this array, even if we choose not to handle it. When it ends, we remove it.
    */
   nsTArray<SingleTouchData> mTouches;
@@ -179,16 +190,23 @@ protected:
   /**
    * Task used to timeout a double tap. This gets posted to the UI thread such
    * that it runs a short time after a single tap happens. We cache it so that
    * we can cancel it if a double tap actually comes in.
    */
   CancelableTask *mDoubleTapTimeoutTask;
 
   /**
+   * Task used to timeout a long tap. This gets posted to the UI thread such
+   * that it runs a time when a single tap happens. We cache it so that
+   * we can cancel it if any other touch event happens.
+   */
+  CancelableTask *mLongTapTimeoutTask;
+
+  /**
    * Position of the last touch starting. This is only valid during an attempt
    * to determine if a touch is a tap. This means that it is used in both the
    * "GESTURE_WAITING_SINGLE_TAP" and "GESTURE_WAITING_DOUBLE_TAP" states.
    */
   nsIntPoint mTouchStartPosition;
 };
 
 }
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -533,16 +533,33 @@ public:
       return;
     }
     if (mRenderFrame) {
       TabParent* browser = static_cast<TabParent*>(mRenderFrame->Manager());
       browser->HandleSingleTap(aPoint);
     }
   }
 
+  virtual void HandleLongTap(const nsIntPoint& aPoint) 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));
+      return;
+    }
+    if (mRenderFrame) {
+      TabParent* browser = static_cast<TabParent*>(mRenderFrame->Manager());
+      browser->HandleLongTap(aPoint);
+    }
+  }
+
   void ClearRenderFrame() { mRenderFrame = nullptr; }
 
 private:
   MessageLoop* mUILoop;
   RenderFrameParent* mRenderFrame;
 };
 
 RenderFrameParent::RenderFrameParent(nsFrameLoader* aFrameLoader,