Bug 1105109 - Notify content when APZ is handling an autoscroll. r=kats
authorBotond Ballo <botond@mozilla.com>
Wed, 26 Jul 2017 19:32:57 -0400
changeset 423145 3ec1e0cf48cb32624327e902b57045a5f9f19435
parent 423144 ab07a3654f59dfc98ed7a884d84936123af3fca3
child 423146 598ea131759fa398db351e9c0d7e31012af1c188
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1105109
milestone56.0a1
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
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() {