Bug 1328658 - Notify main thread of a failed attempt to start an APZ scrollbar drag. r=kats,tnikkel draft
authorBotond Ballo <botond@mozilla.com>
Fri, 27 Jan 2017 18:02:22 -0500
changeset 470534 0b57987a2a15533fad577dd2d363ec386f6bfeae
parent 467243 07d7ecbf77e3be59797f16234d357a02bb38ed8b
child 484183 5a3fbe33acbf813b035b65c27fa63dbf421bbbce
child 484184 52e6e85b2b2017adb296629ca7534a1918663d95
push id44066
push userbballo@mozilla.com
push dateFri, 03 Feb 2017 22:19:32 +0000
reviewerskats, tnikkel
bugs1328658
milestone54.0a1
Bug 1328658 - Notify main thread of a failed attempt to start an APZ scrollbar drag. r=kats,tnikkel MozReview-Commit-ID: ERm1sVNfoKL
gfx/layers/apz/public/GeckoContentController.h
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/APZCTreeManager.h
gfx/layers/apz/test/gtest/APZTestCommon.h
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/APZChild.cpp
gfx/layers/ipc/APZChild.h
gfx/layers/ipc/PAPZ.ipdl
gfx/layers/ipc/RemoteContentController.cpp
gfx/layers/ipc/RemoteContentController.h
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
layout/generic/nsIScrollableFrame.h
layout/xul/nsSliderFrame.cpp
layout/xul/nsSliderFrame.h
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -152,16 +152,18 @@ public:
   virtual void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
   {}
 
   /**
    * Notify content that the repaint requests have been flushed.
    */
   virtual void NotifyFlushComplete() = 0;
 
+  virtual void NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId) = 0;
+
   virtual void UpdateOverscrollVelocity(float aX, float aY, bool aIsRootContent) {}
   virtual void UpdateOverscrollOffset(float aX, float aY, bool aIsRootContent) {}
   virtual void SetScrollingRootContent(bool isRootContent) {}
 
   GeckoContentController() {}
 
   /**
    * Needs to be called on the main thread.
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -48,31 +48,33 @@
 
 namespace mozilla {
 namespace layers {
 
 typedef mozilla::gfx::Point Point;
 typedef mozilla::gfx::Point4D Point4D;
 typedef mozilla::gfx::Matrix4x4 Matrix4x4;
 
+typedef CompositorBridgeParent::LayerTreeState LayerTreeState;
+
 float APZCTreeManager::sDPI = 160.0;
 
 struct APZCTreeManager::TreeBuildingState {
-  TreeBuildingState(const CompositorBridgeParent::LayerTreeState* const aLayerTreeState,
+  TreeBuildingState(const LayerTreeState* const aLayerTreeState,
                     bool aIsFirstPaint, uint64_t aOriginatingLayersId,
                     APZTestData* aTestData, uint32_t aPaintSequence)
     : mLayerTreeState(aLayerTreeState)
     , mIsFirstPaint(aIsFirstPaint)
     , mOriginatingLayersId(aOriginatingLayersId)
     , mPaintLogger(aTestData, aPaintSequence)
   {
   }
 
   // State that doesn't change as we recurse in the tree building
-  const CompositorBridgeParent::LayerTreeState* const mLayerTreeState;
+  const LayerTreeState* const mLayerTreeState;
   const bool mIsFirstPaint;
   const uint64_t mOriginatingLayersId;
   const APZPaintLogHelper mPaintLogger;
 
   // State that is updated as we perform the tree build
 
   // A list of nodes that need to be destroyed at the end of the tree building.
   // This is initialized with all nodes in the old tree, and nodes are removed
@@ -221,23 +223,23 @@ APZCTreeManager::UpdateHitTestingTree(ui
   APZThreadUtils::AssertOnCompositorThread();
 
   MutexAutoLock lock(mTreeLock);
 
   // For testing purposes, we log some data to the APZTestData associated with
   // the layers id that originated this update.
   APZTestData* testData = nullptr;
   if (gfxPrefs::APZTestLoggingEnabled()) {
-    if (CompositorBridgeParent::LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(aOriginatingLayersId)) {
+    if (LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(aOriginatingLayersId)) {
       testData = &state->mApzTestData;
       testData->StartNewPaint(aPaintSequenceNumber);
     }
   }
 
-  const CompositorBridgeParent::LayerTreeState* treeState =
+  const LayerTreeState* treeState =
     CompositorBridgeParent::GetIndirectShadowTree(aRootLayerTreeId);
   MOZ_ASSERT(treeState);
   TreeBuildingState state(treeState, aIsFirstPaint, aOriginatingLayersId,
                           testData, aPaintSequenceNumber);
 
   // We do this business with collecting the entire tree into an array because otherwise
   // it's very hard to determine which APZC instances need to be destroyed. In the worst
   // case, there are two scenarios: (a) a layer with an APZC is removed from the layer
@@ -434,40 +436,49 @@ GetEventRegionsOverride(HitTestingTreeNo
 
 void
 APZCTreeManager::StartScrollbarDrag(const ScrollableLayerGuid& aGuid,
                                     const AsyncDragMetrics& aDragMetrics)
 {
 
   RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
   if (!apzc) {
+    NotifyScrollbarDragRejected(aGuid);
     return;
   }
 
   uint64_t inputBlockId = aDragMetrics.mDragStartSequenceNumber;
   mInputQueue->ConfirmDragBlock(inputBlockId, apzc, aDragMetrics);
 }
 
+void
+APZCTreeManager::NotifyScrollbarDragRejected(const ScrollableLayerGuid& aGuid) const
+{
+  const LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(aGuid.mLayersId);
+  MOZ_ASSERT(state && state->mController);
+  state->mController->NotifyAsyncScrollbarDragRejected(aGuid.mScrollId);
+}
+
 HitTestingTreeNode*
 APZCTreeManager::PrepareNodeForLayer(const LayerMetricsWrapper& aLayer,
                                      const FrameMetrics& aMetrics,
                                      uint64_t aLayersId,
                                      const gfx::Matrix4x4& aAncestorTransform,
                                      HitTestingTreeNode* aParent,
                                      HitTestingTreeNode* aNextSibling,
                                      TreeBuildingState& aState)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
   bool needsApzc = true;
   if (!aMetrics.IsScrollable()) {
     needsApzc = false;
   }
 
-  const CompositorBridgeParent::LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(aLayersId);
+  const LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(aLayersId);
   if (!(state && state->mController.get())) {
     needsApzc = false;
   }
 
   RefPtr<HitTestingTreeNode> node = nullptr;
   if (!needsApzc) {
     node = RecycleOrCreateNode(aState, nullptr, aLayersId);
     AttachNodeToTree(node, aParent, aNextSibling);
@@ -685,17 +696,17 @@ WillHandleInput(const PanGestureOrScroll
 
 void
 APZCTreeManager::FlushApzRepaints(uint64_t aLayersId)
 {
   // Previously, paints were throttled and therefore this method was used to
   // ensure any pending paints were flushed. Now, paints are flushed
   // immediately, so it is safe to simply send a notification now.
   APZCTM_LOG("Flushing repaints for layers id %" PRIu64, aLayersId);
-  const CompositorBridgeParent::LayerTreeState* state =
+  const LayerTreeState* state =
     CompositorBridgeParent::GetIndirectShadowTree(aLayersId);
   MOZ_ASSERT(state && state->mController);
   state->mController->DispatchToRepaintThread(NewRunnableMethod(
      state->mController, &GeckoContentController::NotifyFlushComplete));
 }
 
 nsEventStatus
 APZCTreeManager::ReceiveInputEvent(InputData& aEvent,
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -472,16 +472,18 @@ private:
                                           const gfx::Matrix4x4& aAncestorTransform,
                                           HitTestingTreeNode* aParent,
                                           HitTestingTreeNode* aNextSibling,
                                           TreeBuildingState& aState);
 
   void PrintAPZCInfo(const LayerMetricsWrapper& aLayer,
                      const AsyncPanZoomController* apzc);
 
+  void NotifyScrollbarDragRejected(const ScrollableLayerGuid& aGuid) const;
+
 protected:
   /* The input queue where input events are held until we know enough to
    * figure out where they're going. Protected so gtests can access it.
    */
   RefPtr<InputQueue> mInputQueue;
 
 private:
   /* Whenever walking or mutating the tree rooted at mRootNode, mTreeLock must be held.
--- a/gfx/layers/apz/test/gtest/APZTestCommon.h
+++ b/gfx/layers/apz/test/gtest/APZTestCommon.h
@@ -86,16 +86,17 @@ public:
   bool IsRepaintThread() {
     return NS_IsMainThread();
   }
   void DispatchToRepaintThread(already_AddRefed<Runnable> aTask) {
     NS_DispatchToMainThread(Move(aTask));
   }
   MOCK_METHOD3(NotifyAPZStateChange, void(const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg));
   MOCK_METHOD0(NotifyFlushComplete, void());
+  MOCK_METHOD1(NotifyAsyncScrollbarDragRejected, void(const FrameMetrics::ViewID&));
 };
 
 class MockContentControllerDelayed : public MockContentController {
 public:
   MockContentControllerDelayed()
     : mTime(GetStartupTime())
   {
   }
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -908,16 +908,25 @@ APZCCallbackHelper::IsDisplayportSuppres
 APZCCallbackHelper::IsScrollInProgress(nsIScrollableFrame* aFrame)
 {
   return aFrame->IsProcessingAsyncScroll()
          || nsLayoutUtils::CanScrollOriginClobberApz(aFrame->LastScrollOrigin())
          || aFrame->LastSmoothScrollOrigin();
 }
 
 /* static */ void
+APZCCallbackHelper::NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (nsIScrollableFrame* scrollFrame = nsLayoutUtils::FindScrollableFrameFor(aScrollId)) {
+    scrollFrame->AsyncScrollbarDragRejected();
+  }
+}
+
+/* static */ void
 APZCCallbackHelper::NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
                                        LayoutDeviceCoord aSpanChange,
                                        Modifiers aModifiers,
                                        nsIWidget* aWidget)
 {
   EventMessage msg;
   switch (aType) {
     case PinchGestureInput::PINCHGESTURE_START:
--- a/gfx/layers/apz/util/APZCCallbackHelper.h
+++ b/gfx/layers/apz/util/APZCCallbackHelper.h
@@ -159,16 +159,18 @@ public:
                                                         const SetAllowedTouchBehaviorCallback& aCallback);
 
     /* Notify content of a mouse scroll testing event. */
     static void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent);
 
     /* Notify content that the repaint flush is complete. */
     static void NotifyFlushComplete(nsIPresShell* aShell);
 
+    static void NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId);
+
     /* Temporarily ignore the Displayport for better paint performance. If at
      * all possible, pass in a presShell if you have one at the call site, we
      * use it to trigger a repaint once suppression is disabled. Without that
      * the displayport may get left at the suppressed size for an extended
      * period of time and result in unnecessary checkerboarding (see bug
      * 1255054). */
     static void SuppressDisplayport(const bool& aEnabled,
                                     const nsCOMPtr<nsIPresShell>& aShell);
--- a/gfx/layers/apz/util/ChromeProcessController.cpp
+++ b/gfx/layers/apz/util/ChromeProcessController.cpp
@@ -269,8 +269,21 @@ ChromeProcessController::NotifyMozMouseS
 
 void
 ChromeProcessController::NotifyFlushComplete()
 {
   MOZ_ASSERT(IsRepaintThread());
 
   APZCCallbackHelper::NotifyFlushComplete(GetPresShell());
 }
+
+void
+ChromeProcessController::NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId)
+{
+  if (MessageLoop::current() != mUILoop) {
+    mUILoop->PostTask(NewRunnableMethod<FrameMetrics::ViewID>(this,
+                        &ChromeProcessController::NotifyAsyncScrollbarDragRejected,
+                        aScrollId));
+    return;
+  }
+
+  APZCCallbackHelper::NotifyAsyncScrollbarDragRejected(aScrollId);
+}
--- a/gfx/layers/apz/util/ChromeProcessController.h
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -58,16 +58,17 @@ public:
                                   LayoutDeviceCoord aSpanChange,
                                   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;
+  virtual void NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId) override;
 private:
   nsCOMPtr<nsIWidget> mWidget;
   RefPtr<APZEventState> mAPZEventState;
   RefPtr<IAPZCTreeManager> mAPZCTreeManager;
   MessageLoop* mUILoop;
 
   void InitializeRoot();
   nsIPresShell* GetPresShell() const;
--- a/gfx/layers/apz/util/ContentProcessController.cpp
+++ b/gfx/layers/apz/util/ContentProcessController.cpp
@@ -81,16 +81,22 @@ ContentProcessController::NotifyFlushCom
     if (nsCOMPtr<nsIDocument> doc = mBrowser->GetDocument()) {
       shell = doc->GetShell();
     }
     APZCCallbackHelper::NotifyFlushComplete(shell.get());
   }
 }
 
 void
+ContentProcessController::NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId)
+{
+  APZCCallbackHelper::NotifyAsyncScrollbarDragRejected(aScrollId);
+}
+
+void
 ContentProcessController::PostDelayedTask(already_AddRefed<Runnable> aRunnable, int aDelayMs)
 {
   MOZ_ASSERT_UNREACHABLE("ContentProcessController should only be used remotely.");
 }
 
 bool
 ContentProcessController::IsRepaintThread()
 {
--- a/gfx/layers/apz/util/ContentProcessController.h
+++ b/gfx/layers/apz/util/ContentProcessController.h
@@ -57,16 +57,18 @@ public:
                             APZStateChange aChange,
                             int aArg) override;
 
   void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
                                  const nsString& aEvent) override;
 
   void NotifyFlushComplete() override;
 
+  void NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId) override;
+
   void PostDelayedTask(already_AddRefed<Runnable> aRunnable, int aDelayMs) override;
 
   bool IsRepaintThread() override;
 
   void DispatchToRepaintThread(already_AddRefed<Runnable> aTask) override;
 
 private:
   RefPtr<dom::TabChild> mBrowser;
--- a/gfx/layers/ipc/APZChild.cpp
+++ b/gfx/layers/ipc/APZChild.cpp
@@ -81,16 +81,23 @@ APZChild::RecvNotifyFlushComplete()
 {
   MOZ_ASSERT(mController->IsRepaintThread());
 
   mController->NotifyFlushComplete();
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+APZChild::RecvNotifyAsyncScrollbarDragRejected(const ViewID& aScrollId)
+{
+  mController->NotifyAsyncScrollbarDragRejected(aScrollId);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 APZChild::RecvDestroy()
 {
   // mController->Destroy will be called in the destructor
   PAPZChild::Send__delete__(this);
   return IPC_OK();
 }
 
 
--- a/gfx/layers/ipc/APZChild.h
+++ b/gfx/layers/ipc/APZChild.h
@@ -37,16 +37,18 @@ public:
                                                         const nsString& aEvent) override;
 
   mozilla::ipc::IPCResult RecvNotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
                                                    const APZStateChange& aChange,
                                                    const int& aArg) override;
 
   mozilla::ipc::IPCResult RecvNotifyFlushComplete() override;
 
+  mozilla::ipc::IPCResult RecvNotifyAsyncScrollbarDragRejected(const ViewID& aScrollId) override;
+
   mozilla::ipc::IPCResult RecvDestroy() override;
 
 private:
   RefPtr<GeckoContentController> mController;
 };
 
 } // namespace layers
 
--- a/gfx/layers/ipc/PAPZ.ipdl
+++ b/gfx/layers/ipc/PAPZ.ipdl
@@ -59,13 +59,15 @@ child:
   async SetScrollingRootContent(bool aIsRootContent);
 
   async NotifyMozMouseScrollEvent(ViewID aScrollId, nsString aEvent);
 
   async NotifyAPZStateChange(ScrollableLayerGuid aGuid, APZStateChange aChange, int aArg);
 
   async NotifyFlushComplete();
 
+  async NotifyAsyncScrollbarDragRejected(ViewID aScrollId);
+
   async Destroy();
 };
 
 } // layers
 } // mozilla
--- a/gfx/layers/ipc/RemoteContentController.cpp
+++ b/gfx/layers/ipc/RemoteContentController.cpp
@@ -240,16 +240,31 @@ RemoteContentController::NotifyFlushComp
   MOZ_ASSERT(IsRepaintThread());
 
   if (mCanSend) {
     Unused << SendNotifyFlushComplete();
   }
 }
 
 void
+RemoteContentController::NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId)
+{
+  if (MessageLoop::current() != mCompositorThread) {
+    // We have to send messages from the compositor thread
+    mCompositorThread->PostTask(NewRunnableMethod<FrameMetrics::ViewID>(this,
+        &RemoteContentController::NotifyAsyncScrollbarDragRejected, aScrollId));
+    return;
+  }
+
+  if (mCanSend) {
+    Unused << SendNotifyAsyncScrollbarDragRejected(aScrollId);
+  }
+}
+
+void
 RemoteContentController::ActorDestroy(ActorDestroyReason aWhy)
 {
   // This controller could possibly be kept alive longer after this
   // by a RefPtr, but it is no longer valid to send messages.
   mCanSend = false;
 }
 
 void
--- a/gfx/layers/ipc/RemoteContentController.h
+++ b/gfx/layers/ipc/RemoteContentController.h
@@ -69,16 +69,18 @@ public:
 
   virtual void SetScrollingRootContent(bool aIsRootContent) override;
 
   virtual void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
                                          const nsString& aEvent) override;
 
   virtual void NotifyFlushComplete() override;
 
+  virtual void NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId) override;
+
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual void Destroy() override;
 
 private:
   MessageLoop* mCompositorThread;
   bool mCanSend;
 
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -58,16 +58,17 @@
 #include "StickyScrollContainer.h"
 #include "nsIFrameInlines.h"
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "AsyncScrollBase.h"
 #include "ScrollSnap.h"
 #include "UnitTransforms.h"
 #include "nsPluginFrame.h"
+#include "nsSliderFrame.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include <mozilla/layers/AxisPhysicsModel.h>
 #include <mozilla/layers/AxisPhysicsMSDModel.h>
 #include "mozilla/layers/LayerTransactionChild.h"
 #include "mozilla/layers/ScrollLinkedEffectDetector.h"
 #include "mozilla/Unused.h"
 #include "LayersLogging.h"  // for Stringify
 #include <algorithm>
@@ -6239,8 +6240,35 @@ ScrollFrameHelper::DragScroll(WidgetEven
   }
 
   if (offset.x || offset.y) {
     ScrollTo(GetScrollPosition() + offset, nsIScrollableFrame::NORMAL);
   }
 
   return willScroll;
 }
+
+static void
+AsyncScrollbarDragRejected(nsIFrame* aScrollbar)
+{
+  if (!aScrollbar) {
+    return;
+  }
+
+  for (nsIFrame::ChildListIterator childLists(aScrollbar);
+       !childLists.IsDone();
+       childLists.Next()) {
+    for (nsIFrame* frame : childLists.CurrentList()) {
+      if (nsSliderFrame* sliderFrame = do_QueryFrame(frame)) {
+        sliderFrame->AsyncScrollbarDragRejected();
+      }
+    }
+  }
+}
+
+void
+ScrollFrameHelper::AsyncScrollbarDragRejected()
+{
+  // We don't get told which scrollbar requested the async drag,
+  // so we notify both.
+  ::AsyncScrollbarDragRejected(mHScrollbarBox);
+  ::AsyncScrollbarDragRejected(mVScrollbarBox);
+}
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -457,16 +457,18 @@ public:
                     nsIScrollbarMediator::ScrollSnapMode aSnap
                       = nsIScrollbarMediator::DISABLE_SNAP);
   bool ShouldSuppressScrollbarRepaints() const {
     return mSuppressScrollbarRepaints;
   }
 
   bool DragScroll(WidgetEvent* aEvent);
 
+  void AsyncScrollbarDragRejected();
+
   // owning references to the nsIAnonymousContentCreator-built content
   nsCOMPtr<nsIContent> mHScrollbarContent;
   nsCOMPtr<nsIContent> mVScrollbarContent;
   nsCOMPtr<nsIContent> mScrollCornerContent;
   nsCOMPtr<nsIContent> mResizerContent;
 
   RefPtr<ScrollEvent> mScrollEvent;
   nsRevocableEventPtr<AsyncScrollPortEvent> mAsyncScrollPortEvent;
@@ -1040,16 +1042,20 @@ public:
   ScrollSnapInfo GetScrollSnapInfo() const override {
     return mHelper.GetScrollSnapInfo();
   }
 
   virtual bool DragScroll(mozilla::WidgetEvent* aEvent) override {
     return mHelper.DragScroll(aEvent);
   }
 
+  virtual void AsyncScrollbarDragRejected() override {
+    return mHelper.AsyncScrollbarDragRejected();
+  }
+
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
 #ifdef ACCESSIBILITY
   virtual mozilla::a11y::AccType AccessibleType() override;
 #endif
 
@@ -1473,16 +1479,20 @@ public:
   ScrollSnapInfo GetScrollSnapInfo() const override {
     return mHelper.GetScrollSnapInfo();
   }
 
   virtual bool DragScroll(mozilla::WidgetEvent* aEvent) override {
     return mHelper.DragScroll(aEvent);
   }
 
+  virtual void AsyncScrollbarDragRejected() override {
+    return mHelper.AsyncScrollbarDragRejected();
+  }
+
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
 protected:
   nsXULScrollFrame(nsStyleContext* aContext, bool aIsRoot,
                    bool aClipAllDescendants);
 
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -478,11 +478,13 @@ public:
 
   /**
    * Given the drag event aEvent, determine whether the mouse is near the edge
    * of the scrollable area, and scroll the view in the direction of that edge
    * if so. If scrolling occurred, true is returned. When false is returned, the
    * caller should look for an ancestor to scroll.
    */
   virtual bool DragScroll(mozilla::WidgetEvent* aEvent) = 0;
+
+  virtual void AsyncScrollbarDragRejected() = 0;
 };
 
 #endif
--- a/layout/xul/nsSliderFrame.cpp
+++ b/layout/xul/nsSliderFrame.cpp
@@ -984,54 +984,54 @@ ScrollFrameWillBuildScrollInfoLayer(nsIF
     if (UsesSVGEffects(current)) {
       return true;
     }
     current = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(current);
   }
   return false;
 }
 
-bool
+void
 nsSliderFrame::StartAPZDrag(WidgetGUIEvent* aEvent)
 {
   if (!aEvent->mFlags.mHandledByAPZ) {
-    return false;
+    return;
   }
 
   if (!gfxPlatform::GetPlatform()->SupportsApzDragInput()) {
-    return false;
+    return;
   }
 
   nsContainerFrame* scrollFrame = GetScrollbar()->GetParent();
   if (!scrollFrame) {
-    return false;
+    return;
   }
 
   nsIContent* scrollableContent = scrollFrame->GetContent();
   if (!scrollableContent) {
-    return false;
+    return;
   }
 
   nsIScrollableFrame* scrollFrameAsScrollable = do_QueryFrame(scrollFrame);
   if (!scrollFrameAsScrollable) {
-    return false;
+    return;
   }
 
   // APZ dragging requires the scrollbar to be layerized, which doesn't
   // happen for scroll info layers.
   if (ScrollFrameWillBuildScrollInfoLayer(scrollFrame)) {
-    return false;
+    return;
   }
 
   mozilla::layers::FrameMetrics::ViewID scrollTargetId;
   bool hasID = nsLayoutUtils::FindIDFor(scrollableContent, &scrollTargetId);
   bool hasAPZView = hasID && (scrollTargetId != layers::FrameMetrics::NULL_SCROLL_ID);
 
   if (!hasAPZView) {
-    return false;
+    return;
   }
 
   nsIFrame* scrollbarBox = GetScrollbar();
   nsCOMPtr<nsIContent> scrollbar = GetContentOfBox(scrollbarBox);
 
   nsRect sliderTrack;
   GetXULClientRect(sliderTrack);
 
@@ -1046,31 +1046,36 @@ nsSliderFrame::StartAPZDrag(WidgetGUIEve
   AsyncDragMetrics dragMetrics(scrollTargetId, presShellId, inputblockId,
                                NSAppUnitsToFloatPixels(mDragStart,
                                  float(AppUnitsPerCSSPixel())),
                                sliderTrackCSS,
                                IsXULHorizontal() ? AsyncDragMetrics::HORIZONTAL :
                                                    AsyncDragMetrics::VERTICAL);
 
   if (!nsLayoutUtils::HasDisplayPort(scrollableContent)) {
-    return false;
+    return;
   }
 
+  // It's important to set this before calling nsIWidget::StartAsyncScrollbarDrag(),
+  // because in some configurations, that can call AsyncScrollbarDragRejected()
+  // synchronously, which clears the flag (and we want it to stay cleared in
+  // that case).
+  mScrollingWithAPZ = true;
+
   // When we start an APZ drag, we wont get mouse events for the drag.
   // APZ will consume them all and only notify us of the new scroll position.
   bool waitForRefresh = InputAPZContext::HavePendingLayerization();
   nsIWidget* widget = this->GetNearestWidget();
   if (waitForRefresh) {
     waitForRefresh = shell->AddPostRefreshObserver(
         new AsyncScrollbarDragStarter(shell, widget, dragMetrics));
   }
   if (!waitForRefresh) {
     widget->StartAsyncScrollbarDrag(dragMetrics);
   }
-  return true;
 }
 
 nsresult
 nsSliderFrame::StartDrag(nsIDOMEvent* aEvent)
 {
 #ifdef DEBUG_SLIDER
   printf("Begin dragging\n");
 #endif
@@ -1130,44 +1135,39 @@ nsSliderFrame::StartDrag(nsIDOMEvent* aE
 
   if (isHorizontal)
     mThumbStart = thumbFrame->GetPosition().x;
   else
     mThumbStart = thumbFrame->GetPosition().y;
 
   mDragStart = pos - mThumbStart;
 
-  mScrollingWithAPZ = StartAPZDrag(event);
+  mScrollingWithAPZ = false;
+  StartAPZDrag(event);  // sets mScrollingWithAPZ=true if appropriate
 
 #ifdef DEBUG_SLIDER
   printf("Pressed mDragStart=%d\n",mDragStart);
 #endif
 
-  if (!mScrollingWithAPZ && !mSuppressionActive) {
-    MOZ_ASSERT(PresContext()->PresShell());
-    APZCCallbackHelper::SuppressDisplayport(true, PresContext()->PresShell());
-    mSuppressionActive = true;
+  if (!mScrollingWithAPZ) {
+    SuppressDisplayport();
   }
 
   return NS_OK;
 }
 
 nsresult
 nsSliderFrame::StopDrag()
 {
   AddListener();
   DragThumb(false);
 
   mScrollingWithAPZ = false;
 
-  if (mSuppressionActive) {
-    MOZ_ASSERT(PresContext()->PresShell());
-    APZCCallbackHelper::SuppressDisplayport(false, PresContext()->PresShell());
-    mSuppressionActive = false;
-  }
+  UnsuppressDisplayport();
 
 #ifdef MOZ_WIDGET_GTK
   nsIFrame* thumbFrame = mFrames.FirstChild();
   if (thumbFrame) {
     nsCOMPtr<nsIContent> thumb = thumbFrame->GetContent();
     thumb->UnsetAttr(kNameSpaceID_None, nsGkAtoms::active, true);
   }
 #endif
@@ -1529,10 +1529,41 @@ nsSliderFrame::GetThumbRatio() const
 {
   // mRatio is in thumb app units per scrolled css pixels. Convert it to a
   // ratio of the thumb's CSS pixels per scrolled CSS pixels. (Note the thumb
   // is in the scrollframe's parent's space whereas the scrolled CSS pixels
   // are in the scrollframe's space).
   return mRatio / mozilla::AppUnitsPerCSSPixel();
 }
 
+void
+nsSliderFrame::AsyncScrollbarDragRejected()
+{
+  mScrollingWithAPZ = false;
+  // Only suppress the displayport if we're still dragging the thumb.
+  // Otherwise, no one will unsuppress it.
+  if (isDraggingThumb()) {
+    SuppressDisplayport();
+  }
+}
+
+void
+nsSliderFrame::SuppressDisplayport()
+{
+  if (!mSuppressionActive) {
+    MOZ_ASSERT(PresContext()->PresShell());
+    APZCCallbackHelper::SuppressDisplayport(true, PresContext()->PresShell());
+    mSuppressionActive = true;
+  }
+}
+
+void
+nsSliderFrame::UnsuppressDisplayport()
+{
+  if (mSuppressionActive) {
+    MOZ_ASSERT(PresContext()->PresShell());
+    APZCCallbackHelper::SuppressDisplayport(false, PresContext()->PresShell());
+    mSuppressionActive = false;
+  }
+}
+
 NS_IMPL_ISUPPORTS(nsSliderMediator,
                   nsIDOMEventListener)
--- a/layout/xul/nsSliderFrame.h
+++ b/layout/xul/nsSliderFrame.h
@@ -95,17 +95,17 @@ public:
                             nsIFrame*       aPrevFrame,
                             nsFrameList&    aFrameList) override;
   virtual void RemoveFrame(ChildListID     aListID,
                            nsIFrame*       aOldFrame) override;
 
   nsresult StartDrag(nsIDOMEvent* aEvent);
   nsresult StopDrag();
 
-  bool StartAPZDrag(WidgetGUIEvent* aEvent);
+  void StartAPZDrag(mozilla::WidgetGUIEvent* aEvent);
 
   static int32_t GetCurrentPosition(nsIContent* content);
   static int32_t GetMinPosition(nsIContent* content);
   static int32_t GetMaxPosition(nsIContent* content);
   static int32_t GetIncrement(nsIContent* content);
   static int32_t GetPageIncrement(nsIContent* content);
   static int32_t GetIntegerAttribute(nsIContent* content, nsIAtom* atom, int32_t defaultValue);
   void EnsureOrient();
@@ -132,16 +132,21 @@ public:
   NS_IMETHOD HandleRelease(nsPresContext* aPresContext,
                            mozilla::WidgetGUIEvent* aEvent,
                            nsEventStatus* aEventStatus) override;
 
   // Return the ratio the scrollbar thumb should move in proportion to the
   // scrolled frame.
   float GetThumbRatio() const;
 
+  // Notify the slider frame than an async scrollbar drag requested in
+  // StartAPZDrag() was rejected by APZ, and the slider frame should
+  // fall back to main-thread dragging.
+  void AsyncScrollbarDragRejected();
+
 private:
 
   bool GetScrollToClick();
   nsIFrame* GetScrollbar();
   bool ShouldScrollForEvent(mozilla::WidgetGUIEvent* aEvent);
   bool ShouldScrollToClickForEvent(mozilla::WidgetGUIEvent* aEvent);
   bool IsEventOverThumb(mozilla::WidgetGUIEvent* aEvent);
 
@@ -153,16 +158,19 @@ private:
                                   bool aIsSmooth);
   void CurrentPositionChanged();
 
   void DragThumb(bool aGrabMouseEvents);
   void AddListener();
   void RemoveListener();
   bool isDraggingThumb();
 
+  void SuppressDisplayport();
+  void UnsuppressDisplayport();
+
   void StartRepeat() {
     nsRepeatService::GetInstance()->Start(Notify, this);
   }
   void StopRepeat() {
     nsRepeatService::GetInstance()->Stop(Notify, this);
   }
   void Notify();
   static void Notify(void* aData) {