Bug 1105109 - Notify content when APZ is handling an autoscroll. r=kats
MozReview-Commit-ID: BeuZt30fMpn
--- 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() {