tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mgfx/layers/apz/util/APZTaskRunnable.cpp | 54++++++++++++++++++++++++++++++++++++++++++++----------
Mgfx/layers/apz/util/APZTaskRunnable.h | 15++++++++++++++-
Mgfx/layers/ipc/APZChild.cpp | 6+++++-
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(); }