Bug 1105109 - Notify content when APZ is handling an autoscroll. r=kats draft
authorBotond Ballo <botond@mozilla.com>
Wed, 26 Jul 2017 19:32:57 -0400
changeset 618565 0d96639d873d12e63d981b3bc50b08360fb10d59
parent 617935 c482816ecbaec0a889d817851ee15be186b2a49c
child 618566 a85b20059ae904cc18241e128974bbe8ee060468
push id71381
push userbballo@mozilla.com
push dateMon, 31 Jul 2017 18:42:38 +0000
reviewerskats
bugs1105109
milestone56.0a1
Bug 1105109 - Notify content when APZ is handling an autoscroll. r=kats MozReview-Commit-ID: BeuZt30fMpn
gfx/layers/apz/public/GeckoContentController.h
gfx/layers/apz/src/AsyncPanZoomController.cpp
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
toolkit/content/browser-content.js
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -152,16 +152,18 @@ public:
 
   /**
    * Notify content that the repaint requests have been flushed.
    */
   virtual void NotifyFlushComplete() = 0;
 
   virtual void NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId) = 0;
 
+  virtual void NotifyAutoscrollHandledByAPZ(const FrameMetrics::ViewID& aScrollId) = 0;
+
   virtual void UpdateOverscrollVelocity(float aX, float aY, bool aIsRootContent) {}
   virtual void UpdateOverscrollOffset(float aX, float aY, bool aIsRootContent) {}
 
   GeckoContentController() {}
 
   /**
    * Needs to be called on the main thread.
    */
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1073,16 +1073,21 @@ void AsyncPanZoomController::HandleTouch
 
 void AsyncPanZoomController::StartAutoscroll(const ScreenPoint& aPoint)
 {
   // Cancel any existing animation.
   CancelAnimation();
 
   SetState(AUTOSCROLL);
   StartAnimation(new AutoscrollAnimation(*this, aPoint));
+
+  // Notify content that we are handlng the autoscroll.
+  if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
+    controller->NotifyAutoscrollHandledByAPZ(mFrameMetrics.GetScrollId());
+  }
 }
 
 void AsyncPanZoomController::StopAutoscroll()
 {
   if (mState == AUTOSCROLL) {
     CancelAnimation();
   }
 }
--- a/gfx/layers/apz/test/gtest/APZTestCommon.h
+++ b/gfx/layers/apz/test/gtest/APZTestCommon.h
@@ -88,16 +88,17 @@ public:
     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&));
+  MOCK_METHOD1(NotifyAutoscrollHandledByAPZ, 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
@@ -954,16 +954,28 @@ APZCCallbackHelper::NotifyAsyncScrollbar
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (nsIScrollableFrame* scrollFrame = nsLayoutUtils::FindScrollableFrameFor(aScrollId)) {
     scrollFrame->AsyncScrollbarDragRejected();
   }
 }
 
 /* static */ void
+APZCCallbackHelper::NotifyAutoscrollHandledByAPZ(const FrameMetrics::ViewID& aScrollId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+  MOZ_ASSERT(observerService);
+
+  nsAutoString data;
+  data.AppendInt(aScrollId);
+  observerService->NotifyObservers(nullptr, "autoscroll-handled-by-apz", data.get());
+}
+
+/* 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
@@ -161,16 +161,18 @@ public:
     /* 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);
 
+    static void NotifyAutoscrollHandledByAPZ(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
@@ -303,8 +303,23 @@ ChromeProcessController::NotifyAsyncScro
       this,
       &ChromeProcessController::NotifyAsyncScrollbarDragRejected,
       aScrollId));
     return;
   }
 
   APZCCallbackHelper::NotifyAsyncScrollbarDragRejected(aScrollId);
 }
+
+void
+ChromeProcessController::NotifyAutoscrollHandledByAPZ(const FrameMetrics::ViewID& aScrollId)
+{
+  if (MessageLoop::current() != mUILoop) {
+    mUILoop->PostTask(NewRunnableMethod<FrameMetrics::ViewID>(
+      "layers::ChromeProcessController::NotifyAutoscrollHandledByAPZ",
+      this,
+      &ChromeProcessController::NotifyAutoscrollHandledByAPZ,
+      aScrollId));
+    return;
+  }
+
+  APZCCallbackHelper::NotifyAutoscrollHandledByAPZ(aScrollId);
+}
--- a/gfx/layers/apz/util/ChromeProcessController.h
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -59,16 +59,17 @@ public:
                                   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;
+  virtual void NotifyAutoscrollHandledByAPZ(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
@@ -88,16 +88,22 @@ ContentProcessController::NotifyFlushCom
 
 void
 ContentProcessController::NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId)
 {
   APZCCallbackHelper::NotifyAsyncScrollbarDragRejected(aScrollId);
 }
 
 void
+ContentProcessController::NotifyAutoscrollHandledByAPZ(const FrameMetrics::ViewID& aScrollId)
+{
+  APZCCallbackHelper::NotifyAutoscrollHandledByAPZ(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
@@ -59,16 +59,18 @@ public:
 
   void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
                                  const nsString& aEvent) override;
 
   void NotifyFlushComplete() override;
 
   void NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId) override;
 
+  void NotifyAutoscrollHandledByAPZ(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()
 mozilla::ipc::IPCResult
 APZChild::RecvNotifyAsyncScrollbarDragRejected(const ViewID& aScrollId)
 {
   mController->NotifyAsyncScrollbarDragRejected(aScrollId);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+APZChild::RecvNotifyAutoscrollHandledByAPZ(const ViewID& aScrollId)
+{
+  mController->NotifyAutoscrollHandledByAPZ(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:
   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 RecvNotifyAutoscrollHandledByAPZ(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
@@ -60,13 +60,15 @@ child:
   async NotifyMozMouseScrollEvent(ViewID aScrollId, nsString aEvent);
 
   async NotifyAPZStateChange(ScrollableLayerGuid aGuid, APZStateChange aChange, int aArg);
 
   async NotifyFlushComplete();
 
   async NotifyAsyncScrollbarDragRejected(ViewID aScrollId);
 
+  async NotifyAutoscrollHandledByAPZ(ViewID aScrollId);
+
   async Destroy();
 };
 
 } // layers
 } // mozilla
--- a/gfx/layers/ipc/RemoteContentController.cpp
+++ b/gfx/layers/ipc/RemoteContentController.cpp
@@ -267,16 +267,34 @@ RemoteContentController::NotifyAsyncScro
   }
 
   if (mCanSend) {
     Unused << SendNotifyAsyncScrollbarDragRejected(aScrollId);
   }
 }
 
 void
+RemoteContentController::NotifyAutoscrollHandledByAPZ(const FrameMetrics::ViewID& aScrollId)
+{
+  if (MessageLoop::current() != mCompositorThread) {
+    // We have to send messages from the compositor thread
+    mCompositorThread->PostTask(NewRunnableMethod<FrameMetrics::ViewID>(
+      "layers::RemoteContentController::NotifyAutoscrollHandledByAPZ",
+      this,
+      &RemoteContentController::NotifyAutoscrollHandledByAPZ,
+      aScrollId));
+    return;
+  }
+
+  if (mCanSend) {
+    Unused << SendNotifyAutoscrollHandledByAPZ(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 NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
                                          const nsString& aEvent) override;
 
   virtual void NotifyFlushComplete() override;
 
   virtual void NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId) override;
 
+  virtual void NotifyAutoscrollHandledByAPZ(const FrameMetrics::ViewID& aScrollId) override;
+
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual void Destroy() override;
 
 private:
   MessageLoop* mCompositorThread;
   bool mCanSend;
 
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -33,16 +33,18 @@ var ClickEventHandler = {
   init: function init() {
     this._scrollable = null;
     this._scrolldir = "";
     this._startX = null;
     this._startY = null;
     this._screenX = null;
     this._screenY = null;
     this._lastFrame = null;
+    this._autoscrollHandledByApz = false;
+    this._scrollId = null;
     this.autoscrollLoop = this.autoscrollLoop.bind(this);
 
     Services.els.addSystemEventListener(global, "mousedown", this, true);
 
     addMessageListener("Autoscroll:Stop", this);
   },
 
   isAutoscrollBlocker(node) {
@@ -136,56 +138,59 @@ var ClickEventHandler = {
 
     let domUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDOMWindowUtils);
     let scrollable = this._scrollable;
     if (scrollable instanceof Ci.nsIDOMWindow) {
       // getViewId() needs an element to operate on.
       scrollable = scrollable.document.documentElement;
     }
-    let scrollId = null;
+    this._scrollId = null;
     try {
-      scrollId = domUtils.getViewId(scrollable);
+      this._scrollId = domUtils.getViewId(scrollable);
     } catch (e) {
-      // No view ID - leave it as null. Receiving side will check.
+      // No view ID - leave this._scrollId as null. Receiving side will check.
     }
     let presShellId = domUtils.getPresShellId();
     let [enabled] = sendSyncMessage("Autoscroll:Start",
                                     {scrolldir: this._scrolldir,
                                      screenX: event.screenX,
                                      screenY: event.screenY,
-                                     scrollId,
+                                     scrollId: this._scrollId,
                                      presShellId});
     if (!enabled) {
       this._scrollable = null;
       return;
     }
 
     Services.els.addSystemEventListener(global, "mousemove", this, true);
     addEventListener("pagehide", this, true);
+    Services.obs.addObserver(this, "autoscroll-handled-by-apz");
 
     this._ignoreMouseEvents = true;
     this._startX = event.screenX;
     this._startY = event.screenY;
     this._screenX = event.screenX;
     this._screenY = event.screenY;
     this._scrollErrorX = 0;
     this._scrollErrorY = 0;
+    this._autoscrollHandledByApz = false;
     this._lastFrame = content.performance.now();
 
     content.requestAnimationFrame(this.autoscrollLoop);
   },
 
   stopScroll() {
     if (this._scrollable) {
       this._scrollable.mozScrollSnap();
       this._scrollable = null;
 
       Services.els.removeSystemEventListener(global, "mousemove", this, true);
       removeEventListener("pagehide", this, true);
+      Services.obs.removeObserver(this, "autoscroll-handled-by-apz");
     }
   },
 
   accelerate(curr, start) {
     const speed = 12;
     var val = (curr - start) / speed;
 
     if (val > 1)
@@ -202,16 +207,22 @@ var ClickEventHandler = {
   },
 
   autoscrollLoop(timestamp) {
     if (!this._scrollable) {
       // Scrolling has been canceled
       return;
     }
 
+    if (this._autoscrollHandledByApz) {
+      // APZ is handling the autoscroll, so we don't need to keep running
+      // this callback.
+      return;
+    }
+
     // avoid long jumps when the browser hangs for more than
     // |maxTimeDelta| ms
     const maxTimeDelta = 100;
     var timeDelta = Math.min(maxTimeDelta, timestamp - this._lastFrame);
     // we used to scroll |accelerate()| pixels every 20ms (50fps)
     var timeCompensation = timeDelta / 20;
     this._lastFrame = timestamp;
 
@@ -235,16 +246,17 @@ var ClickEventHandler = {
     const kAutoscroll = 15;  // defined in mozilla/layers/ScrollInputMethods.h
     Services.telemetry.getHistogramById("SCROLL_INPUT_METHODS").add(kAutoscroll);
 
     this._scrollable.scrollBy({
       left: actualScrollX,
       top: actualScrollY,
       behavior: "instant"
     });
+
     content.requestAnimationFrame(this.autoscrollLoop);
   },
 
   handleEvent(event) {
     if (event.type == "mousemove") {
       this._screenX = event.screenX;
       this._screenY = event.screenY;
     } else if (event.type == "mousedown") {
@@ -269,16 +281,25 @@ var ClickEventHandler = {
   receiveMessage(msg) {
     switch (msg.name) {
       case "Autoscroll:Stop": {
         this.stopScroll();
         break;
       }
     }
   },
+
+  observe(subject, topic, data) {
+    if (topic === "autoscroll-handled-by-apz") {
+      // The caller passes in the scroll id via 'data'.
+      if (data == this._scrollId) {
+        this._autoscrollHandledByApz = true;
+      }
+    }
+  },
 };
 ClickEventHandler.init();
 
 var PopupBlocking = {
   popupData: null,
   popupDataInternal: null,
 
   init() {