Bug 1298886 - Fire magnify gesture events in the parent process if APZ is enabled but APZ zooming is disabled. r?botond,dvander draft
authorKartikaya Gupta <kgupta@mozilla.com>
Fri, 23 Sep 2016 15:17:32 -0400
changeset 417187 16d8ae7f878814352b88435d821d591945986deb
parent 417186 ab6510b316a302a39345ad3a2f782edc4599b7ce
child 532049 15933ac11cbe6d0d930cd5fe6f861a5bd378e64d
push id30358
push userkgupta@mozilla.com
push dateFri, 23 Sep 2016 19:18:18 +0000
reviewersbotond, dvander
bugs1298886
milestone52.0a1
Bug 1298886 - Fire magnify gesture events in the parent process if APZ is enabled but APZ zooming is disabled. r?botond,dvander MozReview-Commit-ID: H5R9b2Of26O
gfx/ipc/RemoteCompositorSession.cpp
gfx/ipc/RemoteCompositorSession.h
gfx/layers/apz/public/GeckoContentController.h
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/test/gtest/APZTestCommon.h
gfx/layers/apz/test/gtest/TestPinching.cpp
gfx/layers/apz/util/APZCCallbackHelper.cpp
gfx/layers/apz/util/APZCCallbackHelper.h
gfx/layers/apz/util/ChromeProcessController.cpp
gfx/layers/apz/util/ChromeProcessController.h
gfx/layers/apz/util/ContentProcessController.cpp
gfx/layers/apz/util/ContentProcessController.h
gfx/layers/ipc/APZCTreeManagerChild.cpp
gfx/layers/ipc/APZCTreeManagerChild.h
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/CompositorBridgeParent.h
gfx/layers/ipc/PAPZCTreeManager.ipdl
gfx/layers/ipc/RemoteContentController.cpp
gfx/layers/ipc/RemoteContentController.h
layout/base/UnitTransforms.h
--- a/gfx/ipc/RemoteCompositorSession.cpp
+++ b/gfx/ipc/RemoteCompositorSession.cpp
@@ -58,16 +58,22 @@ RemoteCompositorSession::SetContentContr
 }
 
 GeckoContentController*
 RemoteCompositorSession::GetContentController()
 {
   return mContentController.get();
 }
 
+nsIWidget*
+RemoteCompositorSession::GetWidget()
+{
+  return mWidget;
+}
+
 RefPtr<IAPZCTreeManager>
 RemoteCompositorSession::GetAPZCTreeManager() const
 {
   return mAPZ;
 }
 
 void
 RemoteCompositorSession::Shutdown()
--- a/gfx/ipc/RemoteCompositorSession.h
+++ b/gfx/ipc/RemoteCompositorSession.h
@@ -21,16 +21,17 @@ public:
                           CompositorWidgetDelegate* aWidgetDelegate,
                           APZCTreeManagerChild* aAPZ,
                           const uint64_t& aRootLayerTreeId);
   ~RemoteCompositorSession() override;
 
   CompositorBridgeParent* GetInProcessBridge() const override;
   void SetContentController(GeckoContentController* aController) override;
   GeckoContentController* GetContentController();
+  nsIWidget* GetWidget();
   RefPtr<IAPZCTreeManager> GetAPZCTreeManager() const override;
   void Shutdown() override;
 
   void NotifySessionLost();
 
 private:
   nsBaseWidget* mWidget;
   RefPtr<APZCTreeManagerChild> mAPZ;
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
 
 #ifndef mozilla_layers_GeckoContentController_h
 #define mozilla_layers_GeckoContentController_h
 
 #include "FrameMetrics.h"               // for FrameMetrics, etc
+#include "InputData.h"                  // for PinchGestureInput
 #include "Units.h"                      // for CSSPoint, CSSRect, etc
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2
 #include "mozilla/EventForwards.h"      // for Modifiers
 #include "nsISupportsImpl.h"
 
 namespace mozilla {
 
 class Runnable;
@@ -58,16 +59,37 @@ public:
    */
   virtual void HandleTap(TapType aType,
                          const LayoutDevicePoint& aPoint,
                          Modifiers aModifiers,
                          const ScrollableLayerGuid& aGuid,
                          uint64_t aInputBlockId) = 0;
 
   /**
+   * When the apz.allow_zooming pref is set to false, the APZ will not
+   * translate pinch gestures to actual zooming. Instead, it will call this
+   * method to notify gecko of the pinch gesture, and allow it to deal with
+   * however it wishes. Note that this function is not called if the pinch is
+   * prevented by content calling preventDefault() on the touch events, or via
+   * use of the touch-action property.
+   * @param aType One of PINCHGESTURE_START, PINCHGESTURE_SCALE, or
+   *        PINCHGESTURE_END, indicating the phase of the pinch.
+   * @param aGuid The guid of the APZ that is detecting the pinch. This is
+   *        generally the root APZC for the layers id.
+   * @param aSpan For the START or END event, this is always 0.
+   *        For a SCALE event, this is the difference in span between the
+   *        previous state and the new state.
+   * @param aModifiers The keyboard modifiers depressed during the pinch.
+   */
+  virtual void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+                                  const ScrollableLayerGuid& aGuid,
+                                  LayoutDeviceCoord aSpan,
+                                  Modifiers aModifiers) = 0;
+
+  /**
    * Schedules a runnable to run on the controller/UI thread at some time
    * in the future.
    * This method must always be called on the controller thread.
    */
   virtual void PostDelayedTask(already_AddRefed<Runnable> aRunnable, int aDelayMs) = 0;
 
   /**
    * Returns true if we are currently on the thread that can send repaint requests.
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1302,16 +1302,24 @@ nsEventStatus AsyncPanZoomController::On
 
   mPinchPaintTimerSet = false;
   // Note that there may not be a touch block at this point, if we received the
   // PinchGestureEvent directly from widget code without any touch events.
   if (HasReadyTouchBlock() && !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
     return nsEventStatus_eIgnore;
   }
 
+  // For platforms that don't support APZ zooming, dispatch a message to the
+  // content controller, it may want to do something else with this gesture.
+  if (!gfxPrefs::APZAllowZooming()) {
+    if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
+      controller->NotifyPinchGesture(aEvent.mType, GetGuid(), 0, aEvent.modifiers);
+    }
+  }
+
   SetState(PINCHING);
   mX.SetVelocity(0);
   mY.SetVelocity(0);
   mLastZoomFocus = aEvent.mLocalFocusPoint - mFrameMetrics.GetCompositionBounds().TopLeft();
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
@@ -1321,16 +1329,25 @@ nsEventStatus AsyncPanZoomController::On
   if (HasReadyTouchBlock() && !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
     return nsEventStatus_eIgnore;
   }
 
   if (mState != PINCHING) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
+  if (!gfxPrefs::APZAllowZooming()) {
+    if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
+      controller->NotifyPinchGesture(aEvent.mType, GetGuid(),
+          ViewAs<LayoutDevicePixel>(aEvent.mCurrentSpan - aEvent.mPreviousSpan,
+            PixelCastJustification::LayoutDeviceIsParentLayerForRCDRSF),
+          aEvent.modifiers);
+    }
+  }
+
   // Only the root APZC is zoomable, and the root APZC is not allowed to have
   // different x and y scales. If it did, the calculations in this function
   // would have to be adjusted (as e.g. it would no longer be valid to take
   // the minimum or maximum of the ratios of the widths and heights of the
   // page rect and the composition bounds).
   MOZ_ASSERT(mFrameMetrics.IsRootContent());
   MOZ_ASSERT(mFrameMetrics.GetZoom().AreScalesSame());
 
@@ -1425,16 +1442,22 @@ nsEventStatus AsyncPanZoomController::On
   APZC_LOG("%p got a scale-end in state %d\n", this, mState);
 
   mPinchPaintTimerSet = false;
 
   if (HasReadyTouchBlock() && !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
     return nsEventStatus_eIgnore;
   }
 
+  if (!gfxPrefs::APZAllowZooming()) {
+    if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
+      controller->NotifyPinchGesture(aEvent.mType, GetGuid(), 0, aEvent.modifiers);
+    }
+  }
+
   SetState(NOTHING);
 
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
     ScheduleComposite();
     RequestContentRepaint();
     UpdateSharedCompositorFrameMetrics();
   }
--- a/gfx/layers/apz/test/gtest/APZTestCommon.h
+++ b/gfx/layers/apz/test/gtest/APZTestCommon.h
@@ -73,16 +73,17 @@ static TimeStamp GetStartupTime() {
 }
 
 class MockContentController : public GeckoContentController {
 public:
   MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
   MOCK_METHOD2(RequestFlingSnap, void(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination));
   MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration));
   MOCK_METHOD5(HandleTap, void(TapType, const LayoutDevicePoint&, Modifiers, const ScrollableLayerGuid&, uint64_t));
+  MOCK_METHOD4(NotifyPinchGesture, void(PinchGestureInput::PinchGestureType, const ScrollableLayerGuid&, LayoutDeviceCoord, Modifiers));
   // Can't use the macros with already_AddRefed :(
   void PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs) {
     RefPtr<Runnable> task = aTask;
   }
   bool IsRepaintThread() {
     return NS_IsMainThread();
   }
   void DispatchToRepaintThread(already_AddRefed<Runnable> aTask) {
--- a/gfx/layers/apz/test/gtest/TestPinching.cpp
+++ b/gfx/layers/apz/test/gtest/TestPinching.cpp
@@ -97,16 +97,40 @@ protected:
 };
 
 class APZCPinchGestureDetectorTester : public APZCPinchTester {
 public:
   APZCPinchGestureDetectorTester()
     : APZCPinchTester(AsyncPanZoomController::USE_GESTURE_DETECTOR)
   {
   }
+
+  void DoPinchWithPreventDefaultTest() {
+    FrameMetrics originalMetrics = GetPinchableFrameMetrics();
+    apzc->SetFrameMetrics(originalMetrics);
+
+    MakeApzcWaitForMainThread();
+    MakeApzcZoomable();
+
+    int touchInputId = 0;
+    uint64_t blockId = 0;
+    PinchWithTouchInput(apzc, ScreenIntPoint(250, 300), 1.25, touchInputId,
+        nullptr, nullptr, &blockId);
+
+    // Send the prevent-default notification for the touch block
+    apzc->ContentReceivedInputBlock(blockId, true);
+
+    // verify the metrics didn't change (i.e. the pinch was ignored)
+    FrameMetrics fm = apzc->GetFrameMetrics();
+    EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom());
+    EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x);
+    EXPECT_EQ(originalMetrics.GetScrollOffset().y, fm.GetScrollOffset().y);
+
+    apzc->AssertStateIsReset();
+  }
 };
 
 TEST_F(APZCPinchTester, Pinch_DefaultGestures_NoTouchAction) {
   SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
   DoPinchTest(true);
 }
 
 TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_NoTouchAction) {
@@ -132,38 +156,42 @@ TEST_F(APZCPinchGestureDetectorTester, P
 TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNotAllowZoom) {
   SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   nsTArray<uint32_t> behaviors;
   behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
   behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
   DoPinchTest(false, &behaviors);
 }
 
-TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) {
-  FrameMetrics originalMetrics = GetPinchableFrameMetrics();
-  apzc->SetFrameMetrics(originalMetrics);
+TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNone_NoAPZZoom) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
+  SCOPED_GFX_PREF(APZAllowZooming, bool, false);
 
-  MakeApzcWaitForMainThread();
-  MakeApzcZoomable();
+  // Since we are preventing the pinch action via touch-action we should not be
+  // sending the pinch gesture notifications that would normally be sent when
+  // APZAllowZooming is false.
+  EXPECT_CALL(*mcc, NotifyPinchGesture(_, _, _, _)).Times(0);
+  nsTArray<uint32_t> behaviors = { mozilla::layers::AllowedTouchBehavior::NONE,
+                                   mozilla::layers::AllowedTouchBehavior::NONE };
+  DoPinchTest(false, &behaviors);
+}
 
-  int touchInputId = 0;
-  uint64_t blockId = 0;
-  PinchWithTouchInput(apzc, ScreenIntPoint(250, 300), 1.25, touchInputId,
-      nullptr, nullptr, &blockId);
+TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) {
+  DoPinchWithPreventDefaultTest();
+}
 
-  // Send the prevent-default notification for the touch block
-  apzc->ContentReceivedInputBlock(blockId, true);
+TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault_NoAPZZoom) {
+  SCOPED_GFX_PREF(APZAllowZooming, bool, false);
 
-  // verify the metrics didn't change (i.e. the pinch was ignored)
-  FrameMetrics fm = apzc->GetFrameMetrics();
-  EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom());
-  EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x);
-  EXPECT_EQ(originalMetrics.GetScrollOffset().y, fm.GetScrollOffset().y);
+  // Since we are preventing the pinch action we should not be sending the pinch
+  // gesture notifications that would normally be sent when APZAllowZooming is
+  // false.
+  EXPECT_CALL(*mcc, NotifyPinchGesture(_, _, _, _)).Times(0);
 
-  apzc->AssertStateIsReset();
+  DoPinchWithPreventDefaultTest();
 }
 
 TEST_F(APZCPinchTester, Panning_TwoFinger_ZoomDisabled) {
   // set up APZ
   apzc->SetFrameMetrics(GetPinchableFrameMetrics());
   MakeApzcUnzoomable();
 
   nsEventStatus statuses[3];  // scalebegin, scale, scaleend
@@ -174,8 +202,38 @@ TEST_F(APZCPinchTester, Panning_TwoFinge
 
   // It starts from (300, 300), then moves the focus point from (250, 350) to
   // (200, 300) pans by (50, 50) screen pixels, but there is a 2x zoom, which
   // causes the scroll offset to change by half of that (25, 25) pixels.
   EXPECT_EQ(325, fm.GetScrollOffset().x);
   EXPECT_EQ(325, fm.GetScrollOffset().y);
   EXPECT_EQ(2.0, fm.GetZoom().ToScaleFactor().scale);
 }
+
+TEST_F(APZCPinchGestureDetectorTester, Pinch_APZZoom_Disabled) {
+  SCOPED_GFX_PREF(APZAllowZooming, bool, false);
+
+  FrameMetrics originalMetrics = GetPinchableFrameMetrics();
+  apzc->SetFrameMetrics(originalMetrics);
+
+  // When APZAllowZooming is false, the ZoomConstraintsClient produces
+  // ZoomConstraints with mAllowZoom set to false.
+  MakeApzcUnzoomable();
+
+  // With APZAllowZooming false, we expect the NotifyPinchGesture function to
+  // get called as the pinch progresses, but the metrics shouldn't change.
+  EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_START, apzc->GetGuid(), LayoutDeviceCoord(0), _)).Times(1);
+  EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_SCALE, apzc->GetGuid(), _, _)).Times(AtLeast(1));
+  EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_END, apzc->GetGuid(), LayoutDeviceCoord(0), _)).Times(1);
+
+  int touchInputId = 0;
+  uint64_t blockId = 0;
+  PinchWithTouchInput(apzc, ScreenIntPoint(250, 300), 1.25, touchInputId,
+      nullptr, nullptr, &blockId);
+
+  // verify the metrics didn't change (i.e. the pinch was ignored inside APZ)
+  FrameMetrics fm = apzc->GetFrameMetrics();
+  EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom());
+  EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x);
+  EXPECT_EQ(originalMetrics.GetScrollOffset().y, fm.GetScrollOffset().y);
+
+  apzc->AssertStateIsReset();
+}
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -898,11 +898,40 @@ APZCCallbackHelper::IsDisplayportSuppres
 /* static */ bool
 APZCCallbackHelper::IsScrollInProgress(nsIScrollableFrame* aFrame)
 {
   return aFrame->IsProcessingAsyncScroll()
          || nsLayoutUtils::CanScrollOriginClobberApz(aFrame->LastScrollOrigin())
          || aFrame->LastSmoothScrollOrigin();
 }
 
+/* static */ void
+APZCCallbackHelper::NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+                                       LayoutDeviceCoord aSpan,
+                                       Modifiers aModifiers,
+                                       nsIWidget* aWidget)
+{
+  EventMessage msg;
+  switch (aType) {
+    case PinchGestureInput::PINCHGESTURE_START:
+      msg = eMagnifyGestureStart;
+      break;
+    case PinchGestureInput::PINCHGESTURE_SCALE:
+      msg = eMagnifyGestureUpdate;
+      break;
+    case PinchGestureInput::PINCHGESTURE_END:
+      msg = eMagnifyGesture;
+      break;
+    case PinchGestureInput::PINCHGESTURE_SENTINEL:
+    default:
+      MOZ_ASSERT_UNREACHABLE("Invalid gesture type");
+      return;
+  }
+
+  WidgetSimpleGestureEvent event(true, msg, aWidget);
+  event.mDelta = aSpan;
+  event.mModifiers = aModifiers;
+  DispatchWidgetEvent(event);
+}
+
 } // namespace layers
 } // namespace mozilla
 
--- a/gfx/layers/apz/util/APZCCallbackHelper.h
+++ b/gfx/layers/apz/util/APZCCallbackHelper.h
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
 
 #ifndef mozilla_layers_APZCCallbackHelper_h
 #define mozilla_layers_APZCCallbackHelper_h
 
 #include "FrameMetrics.h"
+#include "InputData.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/Function.h"
 #include "mozilla/layers/APZUtils.h"
 #include "nsIDOMWindowUtils.h"
 
 class nsIContent;
 class nsIDocument;
 class nsIPresShell;
@@ -182,16 +183,26 @@ public:
 
     /*
      * Check if the scrollable frame is currently in the middle of an async
      * or smooth scroll. We want to discard certain scroll input if this is
      * true to prevent clobbering higher priority origins.
      */
     static bool
     IsScrollInProgress(nsIScrollableFrame* aFrame);
+
+    /* Notify content of the progress of a pinch gesture that APZ won't do
+     * zooming for (because the apz.allow_zooming pref is false). This function
+     * will dispatch appropriate WidgetSimpleGestureEvent events to gecko.
+     * The widget provided must be non-null.
+     */
+    static void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+                                   LayoutDeviceCoord aSpan,
+                                   Modifiers aModifiers,
+                                   nsIWidget* aWidget);
 private:
   static uint64_t sLastTargetAPZCNotificationInputBlock;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif /* mozilla_layers_APZCCallbackHelper_h */
--- a/gfx/layers/apz/util/ChromeProcessController.cpp
+++ b/gfx/layers/apz/util/ChromeProcessController.cpp
@@ -203,16 +203,38 @@ ChromeProcessController::HandleTap(TapTy
     // Should never happen, but we need to handle this case branch for the
     // compiler to be happy.
     MOZ_ASSERT(false);
     break;
   }
 }
 
 void
+ChromeProcessController::NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+                                            const ScrollableLayerGuid& aGuid,
+                                            LayoutDeviceCoord aSpan,
+                                            Modifiers aModifiers)
+{
+  if (MessageLoop::current() != mUILoop) {
+    mUILoop->PostTask(NewRunnableMethod
+                      <PinchGestureInput::PinchGestureType,
+                       ScrollableLayerGuid,
+                       LayoutDeviceCoord,
+                       Modifiers>(this,
+                          &ChromeProcessController::NotifyPinchGesture,
+                          aType, aGuid, aSpan, aModifiers));
+    return;
+  }
+
+  if (mWidget) {
+    APZCCallbackHelper::NotifyPinchGesture(aType, aSpan, aModifiers, mWidget.get());
+  }
+}
+
+void
 ChromeProcessController::NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
                                               APZStateChange aChange,
                                               int aArg)
 {
   if (MessageLoop::current() != mUILoop) {
     mUILoop->PostTask(NewRunnableMethod
                       <ScrollableLayerGuid,
                        APZStateChange,
--- a/gfx/layers/apz/util/ChromeProcessController.h
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -48,16 +48,20 @@ public:
   virtual void PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs) override;
   virtual bool IsRepaintThread() override;
   virtual void DispatchToRepaintThread(already_AddRefed<Runnable> aTask) override;
   virtual void HandleTap(TapType aType,
                          const mozilla::LayoutDevicePoint& aPoint,
                          Modifiers aModifiers,
                          const ScrollableLayerGuid& aGuid,
                          uint64_t aInputBlockId) override;
+  virtual void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+                                  const ScrollableLayerGuid& aGuid,
+                                  LayoutDeviceCoord aSpan,
+                                  Modifiers aModifiers) override;
   virtual void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
                                     APZStateChange aChange,
                                     int aArg) override;
   virtual void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
                                          const nsString& aEvent) override;
   virtual void NotifyFlushComplete() override;
 private:
   nsCOMPtr<nsIWidget> mWidget;
--- a/gfx/layers/apz/util/ContentProcessController.cpp
+++ b/gfx/layers/apz/util/ContentProcessController.cpp
@@ -139,16 +139,27 @@ ContentProcessController::HandleTap(
                         const ScrollableLayerGuid& aGuid,
                         uint64_t aInputBlockId)
 {
   // This should never get called
   MOZ_ASSERT(false);
 }
 
 void
+ContentProcessController::NotifyPinchGesture(
+                        PinchGestureInput::PinchGestureType aType,
+                        const ScrollableLayerGuid& aGuid,
+                        LayoutDeviceCoord aSpan,
+                        Modifiers aModifiers)
+{
+  // This should never get called
+  MOZ_ASSERT(false);
+}
+
+void
 ContentProcessController::NotifyAPZStateChange(
                                   const ScrollableLayerGuid& aGuid,
                                   APZStateChange aChange,
                                   int aArg)
 {
   if (mBrowser) {
     mBrowser->NotifyAPZStateChange(aGuid.mScrollId, aChange, aArg);
   }
--- a/gfx/layers/apz/util/ContentProcessController.h
+++ b/gfx/layers/apz/util/ContentProcessController.h
@@ -49,16 +49,21 @@ public:
   void RequestContentRepaint(const FrameMetrics& frame) override;
 
   void HandleTap(TapType aType,
                  const LayoutDevicePoint& aPoint,
                  Modifiers aModifiers,
                  const ScrollableLayerGuid& aGuid,
                  uint64_t aInputBlockId) override;
 
+  void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+                          const ScrollableLayerGuid& aGuid,
+                          LayoutDeviceCoord aSpan,
+                          Modifiers aModifiers) override;
+
   void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
                             APZStateChange aChange,
                             int aArg) override;
 
   void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
                                  const nsString& aEvent) override;
 
   void NotifyFlushComplete() override;
--- a/gfx/layers/ipc/APZCTreeManagerChild.cpp
+++ b/gfx/layers/ipc/APZCTreeManagerChild.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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 "mozilla/layers/APZCTreeManagerChild.h"
 
 #include "InputData.h"                  // for InputData
 #include "mozilla/dom/TabParent.h"      // for TabParent
+#include "mozilla/layers/APZCCallbackHelper.h" // for APZCCallbackHelper
 #include "mozilla/layers/RemoteCompositorSession.h" // for RemoteCompositorSession
 
 namespace mozilla {
 namespace layers {
 
 APZCTreeManagerChild::APZCTreeManagerChild()
   : mCompositorSession(nullptr)
 {
@@ -236,16 +237,36 @@ APZCTreeManagerChild::RecvHandleTap(cons
   }
   dom::TabParent* tab = dom::TabParent::GetTabParentFromLayersId(aGuid.mLayersId);
   if (tab) {
     tab->SendHandleTap(aType, aPoint, aModifiers, aGuid, aInputBlockId);
   }
   return true;
 }
 
+bool
+APZCTreeManagerChild::RecvNotifyPinchGesture(const PinchGestureType& aType,
+                                             const ScrollableLayerGuid& aGuid,
+                                             const LayoutDeviceCoord& aSpan,
+                                             const Modifiers& aModifiers)
+{
+  // This will only get sent from the GPU process to the parent process, so
+  // this function should never get called in the content process.
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // We want to handle it in this process regardless of what the target guid
+  // of the pinch is. This may change in the future.
+  if (mCompositorSession &&
+      mCompositorSession->GetWidget()) {
+    APZCCallbackHelper::NotifyPinchGesture(aType, aSpan, aModifiers, mCompositorSession->GetWidget());
+  }
+  return true;
+}
+
 void
 APZCTreeManagerChild::OnProcessingError(
         Result aCode,
         const char* aReason)
 {
   MOZ_RELEASE_ASSERT(aCode != MsgDropped);
 }
 
--- a/gfx/layers/ipc/APZCTreeManagerChild.h
+++ b/gfx/layers/ipc/APZCTreeManagerChild.h
@@ -93,16 +93,21 @@ public:
 
 protected:
   bool RecvHandleTap(const TapType& aType,
                      const LayoutDevicePoint& aPoint,
                      const Modifiers& aModifiers,
                      const ScrollableLayerGuid& aGuid,
                      const uint64_t& aInputBlockId) override;
 
+  bool RecvNotifyPinchGesture(const PinchGestureType& aType,
+                              const ScrollableLayerGuid& aGuid,
+                              const LayoutDeviceCoord& aSpan,
+                              const Modifiers& aModifiers) override;
+
   virtual
   ~APZCTreeManagerChild() { }
 
 private:
   MOZ_NON_OWNING_REF RemoteCompositorSession* mCompositorSession;
 };
 
 } // namespace layers
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -2449,21 +2449,19 @@ CompositorBridgeParent::GetIndirectShado
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   LayerTreeMap::iterator cit = sIndirectLayerTrees.find(aId);
   if (sIndirectLayerTrees.end() == cit) {
     return nullptr;
   }
   return &cit->second;
 }
 
-/* static */ APZCTreeManagerParent*
-CompositorBridgeParent::GetApzcTreeManagerParentForRoot(uint64_t aContentLayersId)
+static CompositorBridgeParent::LayerTreeState*
+GetStateForRoot(uint64_t aContentLayersId, const MonitorAutoLock& aProofOfLock)
 {
-  MonitorAutoLock lock(*sIndirectLayerTreesLock);
-
   CompositorBridgeParent::LayerTreeState* state = nullptr;
   LayerTreeMap::iterator itr = sIndirectLayerTrees.find(aContentLayersId);
   if (sIndirectLayerTrees.end() != itr) {
     state = &itr->second;
   }
 
   // |state| is the state for the content process, but we want the APZCTMParent
   // for the parent process owning that content process. So we have to jump to
@@ -2472,19 +2470,37 @@ CompositorBridgeParent::GetApzcTreeManag
   // content processes, because RootLayerTreeId() will bypass any intermediate
   // processes' ids and go straight to the root.
   if (state) {
     uint64_t rootLayersId = state->mParent->RootLayerTreeId();
     itr = sIndirectLayerTrees.find(rootLayersId);
     state = (sIndirectLayerTrees.end() != itr) ? &itr->second : nullptr;
   }
 
+  return state;
+}
+
+/* static */ APZCTreeManagerParent*
+CompositorBridgeParent::GetApzcTreeManagerParentForRoot(uint64_t aContentLayersId)
+{
+  MonitorAutoLock lock(*sIndirectLayerTreesLock);
+  CompositorBridgeParent::LayerTreeState* state =
+      GetStateForRoot(aContentLayersId, lock);
   return state ? state->mApzcTreeManagerParent : nullptr;
 }
 
+/* static */ GeckoContentController*
+CompositorBridgeParent::GetGeckoContentControllerForRoot(uint64_t aContentLayersId)
+{
+  MonitorAutoLock lock(*sIndirectLayerTreesLock);
+  CompositorBridgeParent::LayerTreeState* state =
+      GetStateForRoot(aContentLayersId, lock);
+  return state ? state->mController.get() : nullptr;
+}
+
 PTextureParent*
 CompositorBridgeParent::AllocPTextureParent(const SurfaceDescriptor& aSharedData,
                                             const LayersBackend& aLayersBackend,
                                             const TextureFlags& aFlags,
                                             const uint64_t& aId,
                                             const uint64_t& aSerial)
 {
   return TextureHost::CreateIPDLActor(this, aSharedData, aLayersBackend, aFlags, aSerial);
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -475,16 +475,22 @@ public:
    * if one is found, will always be connected to the parent process rather
    * than a content process. Note that unless the compositor process is
    * separated this is expected to return null, because if the compositor is
    * living in the gecko parent process then there is no APZCTreeManagerParent
    * for the parent process.
    */
   static APZCTreeManagerParent* GetApzcTreeManagerParentForRoot(
         uint64_t aContentLayersId);
+  /**
+   * Same as the GetApzcTreeManagerParentForRoot function, but returns
+   * the GeckoContentController for the parent process.
+   */
+  static GeckoContentController* GetGeckoContentControllerForRoot(
+        uint64_t aContentLayersId);
 
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
   /**
    * Calculates and requests the main thread update plugin positioning, clip,
    * and visibility via ipc.
    */
   bool UpdatePluginWindowState(uint64_t aId);
 
--- a/gfx/layers/ipc/PAPZCTreeManager.ipdl
+++ b/gfx/layers/ipc/PAPZCTreeManager.ipdl
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include "mozilla/GfxMessageUtils.h";
 include "ipc/nsGUIEventIPC.h";
 
 include protocol PCompositorBridge;
 
 using CSSRect from "Units.h";
+using LayoutDeviceCoord from "Units.h";
 using LayoutDeviceIntPoint from "Units.h";
 using mozilla::LayoutDevicePoint from "Units.h";
 using ScreenPoint from "Units.h";
 using struct mozilla::layers::ScrollableLayerGuid from "FrameMetrics.h";
 using mozilla::layers::MaybeZoomConstraints from "FrameMetrics.h";
 using mozilla::layers::TouchBehaviorFlags from "mozilla/layers/APZUtils.h";
 using mozilla::layers::AsyncDragMetrics from "mozilla/layers/AsyncDragMetrics.h";
 using mozilla::layers::GeckoContentController::TapType from "mozilla/layers/GeckoContentController.h";
@@ -26,16 +27,17 @@ using class mozilla::WidgetMouseEventBas
 using mozilla::WidgetMouseEvent::Reason from "mozilla/MouseEvents.h";
 using class mozilla::WidgetTouchEvent from "mozilla/TouchEvents.h";
 using class mozilla::WidgetWheelEvent from "mozilla/MouseEvents.h";
 using class mozilla::InputData from "InputData.h";
 using class mozilla::MultiTouchInput from "InputData.h";
 using class mozilla::MouseInput from "InputData.h";
 using class mozilla::PanGestureInput from "InputData.h";
 using class mozilla::PinchGestureInput from "InputData.h";
+using mozilla::PinchGestureInput::PinchGestureType from "InputData.h";
 using class mozilla::TapGestureInput from "InputData.h";
 using class mozilla::ScrollWheelInput from "InputData.h";
 
 namespace mozilla {
 namespace layers {
 
 /**
  * PAPZCTreeManager is a protocol for remoting an IAPZCTreeManager. PAPZCTreeManager
@@ -123,12 +125,15 @@ parent:
              ScrollableLayerGuid    aOutTargetGuid);
 
   async __delete__();
 
 child:
 
   async HandleTap(TapType aType, LayoutDevicePoint point, Modifiers aModifiers,
                   ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
+
+  async NotifyPinchGesture(PinchGestureType aType, ScrollableLayerGuid aGuid,
+                           LayoutDeviceCoord aSpan, Modifiers aModifiers);
 };
 
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/layers/ipc/RemoteContentController.cpp
+++ b/gfx/layers/ipc/RemoteContentController.cpp
@@ -81,16 +81,55 @@ RemoteContentController::HandleTap(TapTy
     // environment, so we must be on the main thread.
     MOZ_ASSERT(XRE_IsParentProcess());
     MOZ_ASSERT(NS_IsMainThread());
     tab->SendHandleTap(aTapType, aPoint, aModifiers, aGuid, aInputBlockId);
   }
 }
 
 void
+RemoteContentController::NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+                                            const ScrollableLayerGuid& aGuid,
+                                            LayoutDeviceCoord aSpan,
+                                            Modifiers aModifiers)
+{
+  APZThreadUtils::AssertOnControllerThread();
+
+  // For now we only ever want to handle this NotifyPinchGesture message in
+  // the parent process, even if the APZ is sending it to a content process.
+
+  // If we're in the GPU process, try to find a handle to the parent process
+  // and send it there.
+  if (XRE_GetProcessType() == GeckoProcessType_GPU) {
+    MOZ_ASSERT(MessageLoop::current() == mCompositorThread);
+
+    // The raw pointer to APZCTreeManagerParent is ok here because we are on the
+    // compositor thread.
+    APZCTreeManagerParent* apzctmp =
+        CompositorBridgeParent::GetApzcTreeManagerParentForRoot(aGuid.mLayersId);
+    if (apzctmp) {
+      Unused << apzctmp->SendNotifyPinchGesture(aType, aGuid, aSpan, aModifiers);
+      return;
+    }
+  }
+
+  // If we're in the parent process, handle it directly. We don't have a handle
+  // to the widget though, so we fish out the ChromeProcessController and
+  // delegate to that instead.
+  if (XRE_IsParentProcess()) {
+    MOZ_ASSERT(NS_IsMainThread());
+    RefPtr<GeckoContentController> rootController =
+        CompositorBridgeParent::GetGeckoContentControllerForRoot(aGuid.mLayersId);
+    if (rootController) {
+      rootController->NotifyPinchGesture(aType, aGuid, aSpan, aModifiers);
+    }
+  }
+}
+
+void
 RemoteContentController::PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs)
 {
 #ifdef MOZ_WIDGET_ANDROID
   AndroidBridge::Bridge()->PostTaskToUiThread(Move(aTask), aDelayMs);
 #else
   (MessageLoop::current() ? MessageLoop::current() : mCompositorThread)->
     PostDelayedTask(Move(aTask), aDelayMs);
 #endif
--- a/gfx/layers/ipc/RemoteContentController.h
+++ b/gfx/layers/ipc/RemoteContentController.h
@@ -43,16 +43,21 @@ public:
   virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) override;
 
   virtual void HandleTap(TapType aTapType,
                          const LayoutDevicePoint& aPoint,
                          Modifiers aModifiers,
                          const ScrollableLayerGuid& aGuid,
                          uint64_t aInputBlockId) override;
 
+  virtual void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
+                                  const ScrollableLayerGuid& aGuid,
+                                  LayoutDeviceCoord aSpan,
+                                  Modifiers aModifiers) override;
+
   virtual void PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs) override;
 
   virtual bool IsRepaintThread() override;
 
   virtual void DispatchToRepaintThread(already_AddRefed<Runnable> aTask) override;
 
   virtual bool GetTouchSensitiveRegion(CSSRect* aOutRegion) override;
 
--- a/layout/base/UnitTransforms.h
+++ b/layout/base/UnitTransforms.h
@@ -51,16 +51,20 @@ enum class PixelCastJustification : uint
   LayoutDeviceIsParentLayerForRCDRSF,
   // Used to treat the product of AsyncTransformComponentMatrix objects
   // as an AsyncTransformMatrix. See the definitions of these matrices in
   // LayersTypes.h for details.
   MultipleAsyncTransforms
 };
 
 template <class TargetUnits, class SourceUnits>
+gfx::CoordTyped<TargetUnits> ViewAs(const gfx::CoordTyped<SourceUnits>& aCoord, PixelCastJustification) {
+  return gfx::CoordTyped<TargetUnits>(aCoord.value);
+}
+template <class TargetUnits, class SourceUnits>
 gfx::SizeTyped<TargetUnits> ViewAs(const gfx::SizeTyped<SourceUnits>& aSize, PixelCastJustification) {
   return gfx::SizeTyped<TargetUnits>(aSize.width, aSize.height);
 }
 template <class TargetUnits, class SourceUnits>
 gfx::IntSizeTyped<TargetUnits> ViewAs(const gfx::IntSizeTyped<SourceUnits>& aSize, PixelCastJustification) {
   return gfx::IntSizeTyped<TargetUnits>(aSize.width, aSize.height);
 }
 template <class TargetUnits, class SourceUnits>