/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "APZTaskRunnable.h" #include "mozilla/PresShell.h" #include "nsRefreshDriver.h" namespace mozilla::layers { NS_IMETHODIMP APZTaskRunnable::Run() { if (!mController) { mRegisteredPresShellId = 0; return NS_OK; } // Move these variables first since below RequestContentPaint and // 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(mPendingRequestQueue); mPendingRepaintRequestMap.clear(); mNeedsFlushCompleteNotification = false; mRegisteredPresShellId = 0; RefPtr controller = mController; // We need to process pending RepaintRequests first. while (!requests.empty()) { 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(); } if (needsFlushCompleteNotification) { // Then notify "apz-repaints-flushed" so that we can ensure that all pending // scroll position updates have finished when the "apz-repaints-flushed" // arrives. controller->NotifyFlushComplete(); } return NS_OK; } void APZTaskRunnable::QueueRequest(const RepaintRequest& aRequest) { // 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 controller = mController; Run(); controller->RequestContentRepaint(aRequest); return; } EnsureRegisterAsEarlyRunner(); RepaintRequestKey key{aRequest.GetScrollId(), aRequest.GetScrollUpdateType()}; auto lastDiscardableRequest = mPendingRepaintRequestMap.find(key); // If there's an existing request with the same key, we can discard it and we // 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 = mPendingRequestQueue.begin(); it != mPendingRequestQueue.end(); it++) { if (it->is()) { const RepaintRequest& request = it->as(); if (RepaintRequestKey{request.GetScrollId(), request.GetScrollUpdateType()} == key) { mPendingRequestQueue.erase(it); break; } } } } mPendingRepaintRequestMap.insert(key); mPendingRequestQueue.push_back(AsVariant(aRequest)); } void APZTaskRunnable::QueueAPZStateChange(const ScrollableLayerGuid& aGuid, const APZStateChange& aChange, const int& aArg, Maybe 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 controller = mController; Run(); controller->NotifyAPZStateChange(aGuid, aChange, aArg, aInputBlockId); return; } EnsureRegisterAsEarlyRunner(); mPendingRequestQueue.push_back( AsVariant(APZStateChangeRequest{aGuid, aChange, aArg, aInputBlockId})); } void APZTaskRunnable::QueueFlushCompleteNotification() { // If we are in test-controlled refreshes mode, notify apz-repaints-flushed // synchronously. if (IsTestControllingRefreshesEnabled()) { // Flush all pending requests and notification just in case the refresh // driver mode was changed before flushing them. RefPtr controller = mController; Run(); controller->NotifyFlushComplete(); return; } EnsureRegisterAsEarlyRunner(); mNeedsFlushCompleteNotification = true; } bool APZTaskRunnable::IsRegisteredWithCurrentPresShell() const { MOZ_ASSERT(mController); uint32_t current = 0; if (PresShell* presShell = mController->GetTopLevelPresShell()) { current = presShell->GetPresShellId(); } return mRegisteredPresShellId == current; } void APZTaskRunnable::EnsureRegisterAsEarlyRunner() { if (IsRegisteredWithCurrentPresShell()) { return; } // If the registered presshell id has been changed, we need to discard pending // requests and notification since all of them are for documents which // have been torn down. if (mRegisteredPresShellId) { mPendingRepaintRequestMap.clear(); mPendingRequestQueue.clear(); mNeedsFlushCompleteNotification = false; } if (PresShell* presShell = mController->GetTopLevelPresShell()) { if (nsRefreshDriver* driver = presShell->GetRefreshDriver()) { driver->AddEarlyRunner(this); mRegisteredPresShellId = presShell->GetPresShellId(); } } } bool APZTaskRunnable::IsTestControllingRefreshesEnabled() const { if (!mController) { return false; } if (PresShell* presShell = mController->GetTopLevelPresShell()) { if (nsRefreshDriver* driver = presShell->GetRefreshDriver()) { return driver->IsTestControllingRefreshesEnabled(); } } return false; } } // namespace mozilla::layers