commit 43f2dcd05a9611902c75caff20f0a3adfb1f346f
parent 4fb3fa392553cf13c6ce2e782c8549d4b2c45217
Author: Botond Ballo <botond@mozilla.com>
Date: Tue, 9 Dec 2025 03:21:27 +0000
Bug 1993068 - Put APZStateChange requests in the same queue as repaint requests in APZChild. r=hiro
This ensures that repaint requests and APZStateChange requests
are processed in the same order in which they arrive.
This can be important because repaint requests can trigger
`scroll` events and APZStateChange requests can trigger
`scrollend` events, and if they get out of order, the user
can experience weird outcomes like a `scrollend` arriving
before a `scroll` whose end it represents.
Differential Revision: https://phabricator.services.mozilla.com/D275529
Diffstat:
3 files changed, 63 insertions(+), 12 deletions(-)
diff --git a/gfx/layers/apz/util/APZTaskRunnable.cpp b/gfx/layers/apz/util/APZTaskRunnable.cpp
@@ -22,7 +22,7 @@ APZTaskRunnable::Run() {
// NotifyFlushComplete might spin event loop so that any new incoming requests
// will be properly queued and run in the next refresh driver's tick.
const bool needsFlushCompleteNotification = mNeedsFlushCompleteNotification;
- auto requests = std::move(mPendingRepaintRequestQueue);
+ auto requests = std::move(mPendingRequestQueue);
mPendingRepaintRequestMap.clear();
mNeedsFlushCompleteNotification = false;
mRegisteredPresShellId = 0;
@@ -30,7 +30,18 @@ APZTaskRunnable::Run() {
// We need to process pending RepaintRequests first.
while (!requests.empty()) {
- controller->RequestContentRepaint(requests.front());
+ struct RequestProcessor {
+ GeckoContentController* mController;
+ void operator()(const RepaintRequest& aRequest) {
+ mController->RequestContentRepaint(aRequest);
+ }
+ void operator()(const APZStateChangeRequest& aRequest) {
+ mController->NotifyAPZStateChange(aRequest.mGuid, aRequest.mChange,
+ aRequest.mArg,
+ aRequest.mInputBlockId);
+ }
+ };
+ requests.front().match(RequestProcessor{controller.get()});
requests.pop_front();
}
@@ -64,17 +75,40 @@ void APZTaskRunnable::QueueRequest(const RepaintRequest& aRequest) {
// push the incoming one into the queue's tail so that we can ensure the order
// of processing requests.
if (lastDiscardableRequest != mPendingRepaintRequestMap.end()) {
- for (auto it = mPendingRepaintRequestQueue.begin();
- it != mPendingRepaintRequestQueue.end(); it++) {
- if (RepaintRequestKey{it->GetScrollId(), it->GetScrollUpdateType()} ==
- key) {
- mPendingRepaintRequestQueue.erase(it);
- break;
+ for (auto it = mPendingRequestQueue.begin();
+ it != mPendingRequestQueue.end(); it++) {
+ if (it->is<RepaintRequest>()) {
+ const RepaintRequest& request = it->as<RepaintRequest>();
+ if (RepaintRequestKey{request.GetScrollId(),
+ request.GetScrollUpdateType()} == key) {
+ mPendingRequestQueue.erase(it);
+ break;
+ }
}
}
}
mPendingRepaintRequestMap.insert(key);
- mPendingRepaintRequestQueue.push_back(aRequest);
+ mPendingRequestQueue.push_back(AsVariant(aRequest));
+}
+
+void APZTaskRunnable::QueueAPZStateChange(const ScrollableLayerGuid& aGuid,
+ const APZStateChange& aChange,
+ const int& aArg,
+ Maybe<uint64_t> aInputBlockId) {
+ // If we are in test-controlled refreshes mode, process this |aRequest|
+ // synchronously.
+ if (IsTestControllingRefreshesEnabled()) {
+ // Flush all pending requests and notification just in case the refresh
+ // driver mode was changed before flushing them.
+ RefPtr<GeckoContentController> controller = mController;
+ Run();
+ controller->NotifyAPZStateChange(aGuid, aChange, aArg, aInputBlockId);
+ return;
+ }
+ EnsureRegisterAsEarlyRunner();
+
+ mPendingRequestQueue.push_back(
+ AsVariant(APZStateChangeRequest{aGuid, aChange, aArg, aInputBlockId}));
}
void APZTaskRunnable::QueueFlushCompleteNotification() {
@@ -114,7 +148,7 @@ void APZTaskRunnable::EnsureRegisterAsEarlyRunner() {
// have been torn down.
if (mRegisteredPresShellId) {
mPendingRepaintRequestMap.clear();
- mPendingRepaintRequestQueue.clear();
+ mPendingRequestQueue.clear();
mNeedsFlushCompleteNotification = false;
}
diff --git a/gfx/layers/apz/util/APZTaskRunnable.h b/gfx/layers/apz/util/APZTaskRunnable.h
@@ -11,6 +11,7 @@
#include <unordered_set>
#include "mozilla/layers/GeckoContentController.h"
+#include "mozilla/layers/GeckoContentControllerTypes.h"
#include "mozilla/layers/RepaintRequest.h"
#include "mozilla/layers/ScrollableLayerGuid.h"
#include "nsThreadUtils.h"
@@ -22,6 +23,8 @@ class GeckoContentController;
// A runnable invoked in nsRefreshDriver::Tick as an early runnable.
class APZTaskRunnable final : public Runnable {
+ using APZStateChange = GeckoContentController_APZStateChange;
+
public:
explicit APZTaskRunnable(GeckoContentController* aController)
: Runnable("RepaintRequestRunnable"),
@@ -36,6 +39,9 @@ class APZTaskRunnable final : public Runnable {
// one will be discarded.
void
QueueRequest(const RepaintRequest& aRequest);
+ void QueueAPZStateChange(const ScrollableLayerGuid& aGuid,
+ const APZStateChange& aChange, const int& aArg,
+ Maybe<uint64_t> aInputBlockId);
void QueueFlushCompleteNotification();
void Revoke() {
mController = nullptr;
@@ -67,13 +73,20 @@ class APZTaskRunnable final : public Runnable {
}
};
};
+ struct APZStateChangeRequest {
+ ScrollableLayerGuid mGuid;
+ APZStateChange mChange;
+ int mArg;
+ Maybe<uint64_t> mInputBlockId;
+ };
+ using Request = mozilla::Variant<RepaintRequest, APZStateChangeRequest>;
using RepaintRequests =
std::unordered_set<RepaintRequestKey, RepaintRequestKey::HashFn>;
// We have an unordered_map and a deque for pending RepaintRequests. The
// unordered_map is for quick lookup and the deque is for processing the
// pending RepaintRequests in the order we queued.
RepaintRequests mPendingRepaintRequestMap;
- std::deque<RepaintRequest> mPendingRepaintRequestQueue;
+ std::deque<Request> mPendingRequestQueue;
// This APZTaskRunnable instance is per APZChild instance, which means its
// lifetime is tied to the APZChild instance, thus this APZTaskRunnable
// instance will be (re-)used for different pres shells so we'd need to
diff --git a/gfx/layers/ipc/APZChild.cpp b/gfx/layers/ipc/APZChild.cpp
@@ -79,7 +79,11 @@ mozilla::ipc::IPCResult APZChild::RecvNotifyMozMouseScrollEvent(
mozilla::ipc::IPCResult APZChild::RecvNotifyAPZStateChange(
const ScrollableLayerGuid& aGuid, const APZStateChange& aChange,
const int& aArg, Maybe<uint64_t> aInputBlockId) {
- mController->NotifyAPZStateChange(aGuid, aChange, aArg, aInputBlockId);
+ MOZ_ASSERT(mController->IsRepaintThread());
+ EnsureAPZTaskRunnable();
+
+ mAPZTaskRunnable->QueueAPZStateChange(aGuid, aChange, aArg, aInputBlockId);
+
return IPC_OK();
}