Backed out changeset 669d3fd3ea2b (bug 906877) for Mnw failures.
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 03 Sep 2013 17:22:14 -0400
changeset 159139 6a2fa1dcddf0ca1ed2d2d3ea12c6aafe94546b2f
parent 159138 47baeadc005b7868e4e387b9656d3f9d19d1e6c2
child 159199 e3785e299ab6ea4c4f6f318ff601d73c6be9198c
push id407
push userlsblakk@mozilla.com
push dateTue, 03 Dec 2013 03:32:50 +0000
treeherdermozilla-release@babf8c9ebc52 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs906877
milestone26.0a1
backs out669d3fd3ea2bce52a31c0a5a2bf63f4513c0dc0a
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
Backed out changeset 669d3fd3ea2b (bug 906877) for Mnw failures.
dom/browser-element/BrowserElementPanning.js
dom/ipc/TabChild.cpp
gfx/layers/composite/APZCTreeManager.cpp
gfx/layers/composite/APZCTreeManager.h
gfx/layers/ipc/AsyncPanZoomController.cpp
gfx/layers/ipc/AsyncPanZoomController.h
layout/ipc/PRenderFrame.ipdl
layout/ipc/RenderFrameChild.cpp
layout/ipc/RenderFrameChild.h
layout/ipc/RenderFrameParent.cpp
layout/ipc/RenderFrameParent.h
--- a/dom/browser-element/BrowserElementPanning.js
+++ b/dom/browser-element/BrowserElementPanning.js
@@ -43,17 +43,17 @@ const ContentPanning = {
       // Touch events aren't supported, so fall back on mouse.
       events = ['mousedown', 'mouseup', 'mousemove'];
       this.watchedEventsType = 'mouse';
     }
 
     // If we are using an AsyncPanZoomController for the parent frame,
     // it will handle subframe scrolling too. We don't need to listen for
     // these events.
-    if (!docShell.asyncPanZoomEnabled) {
+    if (!this._asyncPanZoomForViewportFrame) {
       let els = Cc["@mozilla.org/eventlistenerservice;1"]
                   .getService(Ci.nsIEventListenerService);
 
       events.forEach(function(type) {
         // Using the system group for mouse/touch events to avoid
         // missing events if .stopPropagation() has been called.
         els.addSystemEventListener(global, type,
                                    this.handleEvent.bind(this),
@@ -135,21 +135,33 @@ const ContentPanning = {
       screenY = evt.screenY;
     }
     this.dragging = true;
     this.panning = false;
 
     let oldTarget = this.target;
     [this.target, this.scrollCallback] = this.getPannable(this.pointerDownTarget);
 
-    // If we have a pointer down target, we may need to fill in for EventStateManager
-    // in setting the active state on the target element.  Set a timer to
+    // If we found a target, that means we have found a scrollable subframe. In
+    // this case, and if we are using async panning and zooming on the parent
+    // frame, inform the pan/zoom controller that it should not attempt to
+    // handle any touch events it gets until the next batch (meaning the next
+    // time we get a touch end).
+    if (this.target != null && this._asyncPanZoomForViewportFrame) {
+      this.detectingScrolling = true;
+      var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+      os.notifyObservers(docShell, 'detect-scrollable-subframe', null);
+    }
+
+    // If we have a pointer down target and we're not async
+    // pan/zooming, we may need to fill in for EventStateManager in
+    // setting the active state on the target element.  Set a timer to
     // ensure the pointer-down target is active.  (If it's already
     // active, the timer is a no-op.)
-    if (this.pointerDownTarget !== null) {
+    if (this.pointerDownTarget !== null && !this.detectingScrolling) {
       // If there's no possibility this is a drag/pan, activate now.
       // Otherwise wait a little bit to see if the gesture isn't a
       // tap.
       if (this.target === null) {
         this.notify(this._activationTimer);
       } else {
         this._activationTimer.initWithCallback(this,
                                                this._activationDelayMs,
@@ -209,16 +221,22 @@ const ContentPanning = {
     }
 
     this._finishPanning();
 
     // Now that we're done, avoid entraining the thing we just panned.
     this.pointerDownTarget = null;
   },
 
+  // True when there's an async pan-zoom controll watching the
+  // outermost scrollable frame, and we're waiting to see whether
+  // we're going to take over from it and synchronously scroll an
+  // inner scrollable frame.
+  detectingScrolling: false,
+
   onTouchMove: function cp_onTouchMove(evt) {
     if (!this.dragging)
       return;
 
     let screenX, screenY;
     if (this.watchedEventsType == 'touch') {
       let primaryTouch = this.findPrimaryPointer(evt.changedTouches);
       if (evt.touches.length > 1 || !primaryTouch)
@@ -237,16 +255,37 @@ const ContentPanning = {
     KineticPanning.record(delta, evt.timeStamp);
 
     // There's no possibility of us panning anything.
     if (!this.scrollCallback) {
       return;
     }
 
     let isPan = KineticPanning.isPan();
+    if (!isPan && this.detectingScrolling) {
+      // If panning distance is not large enough and we're waiting to
+      // see whether we should use the sync scroll fallback or not,
+      // don't attempt scrolling.
+      return;
+    }
+
+    let isScroll = this.scrollCallback(delta.scale(-1));
+
+    if (this.detectingScrolling) {
+      this.detectingScrolling = false;
+      // Stop async-pan-zooming if the user is panning the subframe.
+      if (isScroll) {
+        // We're going to drive synchronously scrolling an inner frame.
+        Services.obs.notifyObservers(docShell, 'cancel-default-pan-zoom', null);
+      } else {
+        // Let AsyncPanZoomController handle the scrolling gesture.
+        this.scrollCallback = null;
+        return;
+      }
+    }
 
     // If we've detected a pan gesture, cancel the active state of the
     // current target.
     if (!this.panning && isPan) {
       this.panning = true;
       this._resetActive();
       this._activationTimer.cancel();
     }
@@ -312,16 +351,23 @@ const ContentPanning = {
            ('scrollTopMax' in node && node.scrollTopMax > 0)));
       if (isScroll || isAuto || isScrollableTextarea) {
         return node;
       }
 
       node = node.parentNode;
     }
 
+    if (ContentPanning._asyncPanZoomForViewportFrame &&
+        nodeContent === content) {
+        // The parent context is asynchronously panning and zooming our
+        // root scrollable frame, so don't use our synchronous fallback.
+        return null;
+    }
+
     if (nodeContent.scrollMaxX || nodeContent.scrollMaxY) {
       return nodeContent;
     }
 
     if (nodeContent.frameElement) {
       return this._findPannable(nodeContent.frameElement);
     }
 
@@ -433,16 +479,20 @@ const ContentPanning = {
     this._setActive(root.documentElement);
   },
 
   _setActive: function cp_setActive(elt) {
     const kStateActive = 0x00000001;
     this._domUtils.setContentState(elt, kStateActive);
   },
 
+  get _asyncPanZoomForViewportFrame() {
+    return docShell.asyncPanZoomEnabled;
+  },
+
   _recvViewportChange: function(data) {
     let metrics = data.json;
     this._viewport = new Rect(metrics.x, metrics.y,
                               metrics.viewport.width,
                               metrics.viewport.height);
     this._cssCompositedRect = new Rect(metrics.x, metrics.y,
                                        metrics.cssCompositedRect.width,
                                        metrics.cssCompositedRect.height);
@@ -544,16 +594,17 @@ const ContentPanning = {
     let ratioH = (aRect.height / vRect.height);
 
     return (showing > 0.9 && (ratioW > 0.9 || ratioH > 0.9)); 
   },
 
   _finishPanning: function() {
     this._resetActive();
     this.dragging = false;
+    this.detectingScrolling = false;
     delete this.primaryPointerId;
     this._activationTimer.cancel();
 
     if (this.panning) {
       KineticPanning.start(this);
     }
   }
 };
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -100,18 +100,20 @@ using namespace mozilla::docshell;
 using namespace mozilla::dom::indexedDB;
 using namespace mozilla::widget;
 using namespace mozilla::jsipc;
 
 NS_IMPL_ISUPPORTS1(ContentListener, nsIDOMEventListener)
 
 static const CSSSize kDefaultViewportSize(980, 480);
 
+static const char CANCEL_DEFAULT_PAN_ZOOM[] = "cancel-default-pan-zoom";
 static const char BROWSER_ZOOM_TO_RECT[] = "browser-zoom-to-rect";
 static const char BEFORE_FIRST_PAINT[] = "before-first-paint";
+static const char DETECT_SCROLLABLE_SUBFRAME[] = "detect-scrollable-subframe";
 
 static bool sCpowsEnabled = false;
 
 NS_IMETHODIMP
 ContentListener::HandleEvent(nsIDOMEvent* aEvent)
 {
   RemoteDOMEvent remoteEvent;
   remoteEvent.mEvent = do_QueryInterface(aEvent);
@@ -378,17 +380,23 @@ TabChild::HandleEvent(nsIDOMEvent* aEven
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabChild::Observe(nsISupports *aSubject,
                   const char *aTopic,
                   const PRUnichar *aData)
 {
-  if (!strcmp(aTopic, BROWSER_ZOOM_TO_RECT)) {
+  if (!strcmp(aTopic, CANCEL_DEFAULT_PAN_ZOOM)) {
+    nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aSubject));
+    nsCOMPtr<nsITabChild> tabChild(GetTabChildFrom(docShell));
+    if (tabChild == this) {
+      mRemoteFrame->CancelDefaultPanZoom();
+    }
+  } else if (!strcmp(aTopic, BROWSER_ZOOM_TO_RECT)) {
     nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aSubject));
     nsCOMPtr<nsITabChild> tabChild(GetTabChildFrom(docShell));
     if (tabChild == this) {
       CSSRect rect;
       sscanf(NS_ConvertUTF16toUTF8(aData).get(),
              "{\"x\":%f,\"y\":%f,\"w\":%f,\"h\":%f}",
              &rect.x, &rect.y, &rect.width, &rect.height);
       SendZoomToRect(rect);
@@ -423,16 +431,22 @@ TabChild::Observe(nsISupports *aSubject,
         mLastMetrics.mScrollOffset = CSSPoint(0, 0);
 
         utils->SetResolution(mLastMetrics.mResolution.scale,
                              mLastMetrics.mResolution.scale);
 
         HandlePossibleViewportChange();
       }
     }
+  } else if (!strcmp(aTopic, DETECT_SCROLLABLE_SUBFRAME)) {
+    nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aSubject));
+    nsCOMPtr<nsITabChild> tabChild(GetTabChildFrom(docShell));
+    if (tabChild == this) {
+      mRemoteFrame->DetectScrollableSubframe();
+    }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabChild::OnStateChange(nsIWebProgress* aWebProgress,
                         nsIRequest* aRequest,
@@ -2164,18 +2178,20 @@ TabChild::RecvDestroy()
     nsContentUtils::AddScriptRunner(
       new UnloadScriptEvent(this, mTabChildGlobal)
     );
   }
 
   nsCOMPtr<nsIObserverService> observerService =
     do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
 
+  observerService->RemoveObserver(this, CANCEL_DEFAULT_PAN_ZOOM);
   observerService->RemoveObserver(this, BROWSER_ZOOM_TO_RECT);
   observerService->RemoveObserver(this, BEFORE_FIRST_PAINT);
+  observerService->RemoveObserver(this, DETECT_SCROLLABLE_SUBFRAME);
 
   const InfallibleTArray<PIndexedDBChild*>& idbActors =
     ManagedPIndexedDBChild();
   for (uint32_t i = 0; i < idbActors.Length(); ++i) {
     static_cast<IndexedDBChild*>(idbActors[i])->Disconnect();
   }
 
   // XXX what other code in ~TabChild() should we be running here?
@@ -2301,21 +2317,27 @@ TabChild::InitRenderingState()
 
     mRemoteFrame = remoteFrame;
 
     nsCOMPtr<nsIObserverService> observerService =
         do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
 
     if (observerService) {
         observerService->AddObserver(this,
+                                     CANCEL_DEFAULT_PAN_ZOOM,
+                                     false);
+        observerService->AddObserver(this,
                                      BROWSER_ZOOM_TO_RECT,
                                      false);
         observerService->AddObserver(this,
                                      BEFORE_FIRST_PAINT,
                                      false);
+        observerService->AddObserver(this,
+                                     DETECT_SCROLLABLE_SUBFRAME,
+                                     false);
     }
 
     // This state can't really change during the lifetime of the child.
     sCpowsEnabled = Preferences::GetBool("browser.tabs.remote", false);
     if (Preferences::GetBool("dom.ipc.cpows.force-enabled", false))
       sCpowsEnabled = true;
 
     return true;
--- a/gfx/layers/composite/APZCTreeManager.cpp
+++ b/gfx/layers/composite/APZCTreeManager.cpp
@@ -356,16 +356,34 @@ APZCTreeManager::UpdateCompositionBounds
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
   if (apzc) {
     apzc->UpdateCompositionBounds(aCompositionBounds);
   }
 }
 
 void
+APZCTreeManager::CancelDefaultPanZoom(const ScrollableLayerGuid& aGuid)
+{
+  nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
+  if (apzc) {
+    apzc->CancelDefaultPanZoom();
+  }
+}
+
+void
+APZCTreeManager::DetectScrollableSubframe(const ScrollableLayerGuid& aGuid)
+{
+  nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
+  if (apzc) {
+    apzc->DetectScrollableSubframe();
+  }
+}
+
+void
 APZCTreeManager::ZoomToRect(const ScrollableLayerGuid& aGuid,
                             const CSSRect& aRect)
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
   if (apzc) {
     apzc->ZoomToRect(aRect);
   }
 }
--- a/gfx/layers/composite/APZCTreeManager.h
+++ b/gfx/layers/composite/APZCTreeManager.h
@@ -164,16 +164,32 @@ public:
    * general, this will just be:
    * { x = 0, y = 0, width = surface.width, height = surface.height }, however
    * there is no hard requirement for this.
    */
   void UpdateCompositionBounds(const ScrollableLayerGuid& aGuid,
                                const ScreenIntRect& aCompositionBounds);
 
   /**
+   * We are scrolling a subframe, so disable our machinery until we hit
+   * a touch end or a new touch start. This prevents us from accidentally
+   * panning both the subframe and the parent frame.
+   *
+   * XXX/bug 775452: We should eventually be supporting async scrollable
+   * subframes.
+   */
+  void CancelDefaultPanZoom(const ScrollableLayerGuid& aGuid);
+
+  /**
+   * We have found a scrollable subframe, so we need to delay the scrolling
+   * gesture executed and let subframe do the scrolling first.
+   */
+  void DetectScrollableSubframe(const ScrollableLayerGuid& aGuid);
+
+  /**
    * Kicks an animation to zoom to a rect. This may be either a zoom out or zoom
    * in. The actual animation is done on the compositor thread after being set
    * up. |aRect| must be given in CSS pixels, relative to the document.
    */
   void ZoomToRect(const ScrollableLayerGuid& aGuid,
                   const CSSRect& aRect);
 
   /**
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -207,17 +207,19 @@ AsyncPanZoomController::AsyncPanZoomCont
      mMinZoom(MIN_ZOOM),
      mMaxZoom(MAX_ZOOM),
      mLastSampleTime(GetFrameTime()),
      mState(NOTHING),
      mLastAsyncScrollTime(GetFrameTime()),
      mLastAsyncScrollOffset(0, 0),
      mCurrentAsyncScrollOffset(0, 0),
      mAsyncScrollTimeoutTask(nullptr),
-     mHandlingTouchQueue(false)
+     mDisableNextTouchBatch(false),
+     mHandlingTouchQueue(false),
+     mDelayPanning(false)
 {
   MOZ_COUNT_CTOR(AsyncPanZoomController);
 
   if (aGestures == USE_GESTURE_DETECTOR) {
     mGestureEventListener = new GestureEventListener(this);
   }
 }
 
@@ -305,22 +307,39 @@ nsEventStatus AsyncPanZoomController::Re
 
   return HandleInputEvent(aEvent);
 }
 
 nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent) {
   nsEventStatus rv = nsEventStatus_eIgnore;
 
   nsRefPtr<GestureEventListener> listener = GetGestureEventListener();
-  if (listener) {
+  if (listener && !mDisableNextTouchBatch) {
     rv = listener->HandleInputEvent(aEvent);
     if (rv == nsEventStatus_eConsumeNoDefault)
       return rv;
   }
 
+  if (mDelayPanning && aEvent.mInputType == MULTITOUCH_INPUT) {
+    const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
+    if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_MOVE) {
+      // Let BrowserElementScrolling perform panning gesture first.
+      SetState(WAITING_LISTENERS);
+      mTouchQueue.AppendElement(multiTouchInput);
+
+      if (!mTouchListenerTimeoutTask) {
+        mTouchListenerTimeoutTask =
+          NewRunnableMethod(this, &AsyncPanZoomController::TimeoutTouchListeners);
+
+        PostDelayedTask(mTouchListenerTimeoutTask, gTouchListenerTimeout);
+      }
+      return nsEventStatus_eConsumeNoDefault;
+    }
+  }
+
   switch (aEvent.mInputType) {
   case MULTITOUCH_INPUT: {
     const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
     switch (multiTouchInput.mType) {
       case MultiTouchInput::MULTITOUCH_START: rv = OnTouchStart(multiTouchInput); break;
       case MultiTouchInput::MULTITOUCH_MOVE: rv = OnTouchMove(multiTouchInput); break;
       case MultiTouchInput::MULTITOUCH_END: rv = OnTouchEnd(multiTouchInput); break;
       case MultiTouchInput::MULTITOUCH_CANCEL: rv = OnTouchCancel(multiTouchInput); break;
@@ -392,16 +411,20 @@ nsEventStatus AsyncPanZoomController::On
       NS_WARNING("Unhandled case in OnTouchStart");
       break;
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent) {
+  if (mDisableNextTouchBatch) {
+    return nsEventStatus_eIgnore;
+  }
+
   switch (mState) {
     case FLING:
     case NOTHING:
     case ANIMATING_ZOOM:
       // May happen if the user double-taps and drags without lifting after the
       // second tap. Ignore the move if this happens.
       return nsEventStatus_eIgnore;
 
@@ -431,16 +454,21 @@ nsEventStatus AsyncPanZoomController::On
       NS_WARNING("Received impossible touch in OnTouchMove");
       break;
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) {
+  if (mDisableNextTouchBatch) {
+    mDisableNextTouchBatch = false;
+    return nsEventStatus_eIgnore;
+  }
+
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
     SendAsyncScrollEvent();
   }
 
   switch (mState) {
   case FLING:
     // Should never happen.
@@ -1152,16 +1180,28 @@ void AsyncPanZoomController::UpdateCompo
     ScreenToScreenScale adjustmentFactor(float(aCompositionBounds.width) / float(oldCompositionBounds.width));
     SetZoomAndResolution(mFrameMetrics.mZoom * adjustmentFactor);
 
     // Repaint on a rotation so that our new resolution gets properly updated.
     RequestContentRepaint();
   }
 }
 
+void AsyncPanZoomController::CancelDefaultPanZoom() {
+  mDisableNextTouchBatch = true;
+  nsRefPtr<GestureEventListener> listener = GetGestureEventListener();
+  if (listener) {
+    listener->CancelGesture();
+  }
+}
+
+void AsyncPanZoomController::DetectScrollableSubframe() {
+  mDelayPanning = true;
+}
+
 void AsyncPanZoomController::ZoomToRect(CSSRect aRect) {
   SetState(ANIMATING_ZOOM);
 
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     ScreenIntRect compositionBounds = mFrameMetrics.mCompositionBounds;
     CSSRect cssPageRect = mFrameMetrics.mScrollableRect;
@@ -1231,34 +1271,43 @@ void AsyncPanZoomController::ZoomToRect(
 
     mAnimationStartTime = GetFrameTime();
 
     ScheduleComposite();
   }
 }
 
 void AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
-  if (!mFrameMetrics.mMayHaveTouchListeners) {
+  if (!mFrameMetrics.mMayHaveTouchListeners && !mDelayPanning) {
     mTouchQueue.Clear();
     return;
   }
 
   if (mTouchListenerTimeoutTask) {
     mTouchListenerTimeoutTask->Cancel();
     mTouchListenerTimeoutTask = nullptr;
   }
 
   if (mState == WAITING_LISTENERS) {
     if (!aPreventDefault) {
-      SetState(NOTHING);
+      // Delayed scrolling gesture is pending at TOUCHING state.
+      if (mDelayPanning) {
+        SetState(TOUCHING);
+      } else {
+        SetState(NOTHING);
+      }
     }
 
     mHandlingTouchQueue = true;
 
     while (!mTouchQueue.IsEmpty()) {
+      // we need to reset mDelayPanning before handling scrolling gesture.
+      if (!aPreventDefault && mTouchQueue[0].mType == MultiTouchInput::MULTITOUCH_MOVE) {
+        mDelayPanning = false;
+      }
       if (!aPreventDefault) {
         HandleInputEvent(mTouchQueue[0]);
       }
 
       if (mTouchQueue[0].mType == MultiTouchInput::MULTITOUCH_END ||
           mTouchQueue[0].mType == MultiTouchInput::MULTITOUCH_CANCEL) {
         mTouchQueue.RemoveElementAt(0);
         break;
--- a/gfx/layers/ipc/AsyncPanZoomController.h
+++ b/gfx/layers/ipc/AsyncPanZoomController.h
@@ -105,16 +105,32 @@ public:
    * the frame this is tied to during composition onto, in device pixels. In
    * general, this will just be:
    * { x = 0, y = 0, width = surface.width, height = surface.height }, however
    * there is no hard requirement for this.
    */
   void UpdateCompositionBounds(const ScreenIntRect& aCompositionBounds);
 
   /**
+   * We are scrolling a subframe, so disable our machinery until we hit
+   * a touch end or a new touch start. This prevents us from accidentally
+   * panning both the subframe and the parent frame.
+   *
+   * XXX/bug 775452: We should eventually be supporting async scrollable
+   * subframes.
+   */
+  void CancelDefaultPanZoom();
+
+  /**
+   * We have found a scrollable subframe, so we need to delay the scrolling
+   * gesture executed and let subframe do the scrolling first.
+   */
+  void DetectScrollableSubframe();
+
+  /**
    * Kicks an animation to zoom to a rect. This may be either a zoom out or zoom
    * in. The actual animation is done on the compositor thread after being set
    * up.
    */
   void ZoomToRect(CSSRect aRect);
 
   /**
    * If we have touch listeners, this should always be called when we know
@@ -559,22 +575,32 @@ private:
   // The current offset drawn on the screen, it may not be sent since we have
   // throttling policy for mozbrowserasyncscroll event.
   CSSPoint mCurrentAsyncScrollOffset;
 
   // The delay task triggered by the throttling mozbrowserasyncscroll event
   // ensures the last mozbrowserasyncscroll event is always been fired.
   CancelableTask* mAsyncScrollTimeoutTask;
 
+  // Flag used to determine whether or not we should disable handling of the
+  // next batch of touch events. This is used for sync scrolling of subframes.
+  bool mDisableNextTouchBatch;
+
   // Flag used to determine whether or not we should try to enter the
   // WAITING_LISTENERS state. This is used in the case that we are processing a
   // queued up event block. If set, this means that we are handling this queue
   // and we don't want to queue the events back up again.
   bool mHandlingTouchQueue;
 
+  // Flag used to determine whether or not we should try scrolling by
+  // BrowserElementScrolling first.  If set, we delay delivering
+  // touchmove events to GestureListener until BrowserElementScrolling
+  // decides whether it wants to handle panning for this touch series.
+  bool mDelayPanning;
+
   friend class Axis;
 
   /* The functions and members in this section are used to build a tree
    * structure out of APZC instances. This tree can only be walked or
    * manipulated while holding the lock in the associated APZCTreeManager
    * instance.
    */
 public:
--- a/layout/ipc/PRenderFrame.ipdl
+++ b/layout/ipc/PRenderFrame.ipdl
@@ -40,16 +40,19 @@ parent:
      *
      * |id| is set to 0 in the "direct" case, and to a whole number
      * in the "indirect" case.
      */
     async PLayerTransaction();
 
     async NotifyCompositorTransaction();
 
+    async CancelDefaultPanZoom();
+    async DetectScrollableSubframe();
+
     async UpdateHitRegion(nsRegion aRegion);
 
     async __delete__();
 
 state EMPTY_OR_DIRECT_COMPOSITOR:
     recv PLayerTransaction goto HAVE_CONTENT;
     recv NotifyCompositorTransaction goto EMPTY_OR_DIRECT_COMPOSITOR;
     recv UpdateHitRegion goto EMPTY_OR_DIRECT_COMPOSITOR;
--- a/layout/ipc/RenderFrameChild.cpp
+++ b/layout/ipc/RenderFrameChild.cpp
@@ -27,16 +27,28 @@ RenderFrameChild::Destroy()
     layers->Destroy();
     // |layers| was just deleted, take care
   }
 
   Send__delete__(this);
   // WARNING: |this| is dead, hands off
 }
 
+void
+RenderFrameChild::CancelDefaultPanZoom()
+{
+  SendCancelDefaultPanZoom();
+}
+
+void
+RenderFrameChild::DetectScrollableSubframe()
+{
+  SendDetectScrollableSubframe();
+}
+
 PLayerTransactionChild*
 RenderFrameChild::AllocPLayerTransactionChild()
 {
   return new LayerTransactionChild();
 }
 
 bool
 RenderFrameChild::DeallocPLayerTransactionChild(PLayerTransactionChild* aLayers)
--- a/layout/ipc/RenderFrameChild.h
+++ b/layout/ipc/RenderFrameChild.h
@@ -14,16 +14,19 @@ namespace mozilla {
 namespace layout {
 
 class RenderFrameChild : public PRenderFrameChild
 {
 public:
   RenderFrameChild() {}
   virtual ~RenderFrameChild() {}
 
+  void CancelDefaultPanZoom();
+  void DetectScrollableSubframe();
+
   void Destroy();
 
 protected:
   virtual PLayerTransactionChild* AllocPLayerTransactionChild() MOZ_OVERRIDE;
   virtual bool DeallocPLayerTransactionChild(PLayerTransactionChild* aLayers) MOZ_OVERRIDE;
 };
 
 } // namespace layout
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -842,16 +842,34 @@ RenderFrameParent::ActorDestroy(ActorDes
 bool
 RenderFrameParent::RecvNotifyCompositorTransaction()
 {
   TriggerRepaint();
   return true;
 }
 
 bool
+RenderFrameParent::RecvCancelDefaultPanZoom()
+{
+  if (GetApzcTreeManager()) {
+    GetApzcTreeManager()->CancelDefaultPanZoom(ScrollableLayerGuid(mLayersId));
+  }
+  return true;
+}
+
+bool
+RenderFrameParent::RecvDetectScrollableSubframe()
+{
+  if (GetApzcTreeManager()) {
+    GetApzcTreeManager()->DetectScrollableSubframe(ScrollableLayerGuid(mLayersId));
+  }
+  return true;
+}
+
+bool
 RenderFrameParent::RecvUpdateHitRegion(const nsRegion& aRegion)
 {
   mTouchRegion = aRegion;
   return true;
 }
 
 PLayerTransactionParent*
 RenderFrameParent::AllocPLayerTransactionParent()
--- a/layout/ipc/RenderFrameParent.h
+++ b/layout/ipc/RenderFrameParent.h
@@ -112,16 +112,19 @@ public:
 
   bool HitTest(const nsRect& aRect);
 
 protected:
   void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
   virtual bool RecvNotifyCompositorTransaction() MOZ_OVERRIDE;
 
+  virtual bool RecvCancelDefaultPanZoom() MOZ_OVERRIDE;
+  virtual bool RecvDetectScrollableSubframe() MOZ_OVERRIDE;
+
   virtual bool RecvUpdateHitRegion(const nsRegion& aRegion) MOZ_OVERRIDE;
 
   virtual PLayerTransactionParent* AllocPLayerTransactionParent() MOZ_OVERRIDE;
   virtual bool DeallocPLayerTransactionParent(PLayerTransactionParent* aLayers) MOZ_OVERRIDE;
 
 private:
   void BuildViewMap();
   void TriggerRepaint();