Bug 1730998 - Use control priority for RequestContentRepaint and NotifyFlushComplete, and process them in an early refresh driver runner. r=botond,smaug

Depends on D127030

Differential Revision: https://phabricator.services.mozilla.com/D127032
This commit is contained in:
Hiroyuki Ikezoe 2021-10-14 09:43:02 +00:00
parent 346c0300a6
commit 7dd944492a
10 changed files with 273 additions and 2 deletions

View File

@ -166,6 +166,8 @@ class GeckoContentController {
*/
virtual bool IsRemote() { return false; }
virtual PresShell* GetTopLevelPresShell() const { return nullptr; };
protected:
// Protected destructor, to discourage deletion outside of Release():
virtual ~GeckoContentController() = default;

View File

@ -0,0 +1,148 @@
/* -*- 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(mPendingRepaintRequestQueue);
mPendingRepaintRequestMap.clear();
mNeedsFlushCompleteNotification = false;
mRegisteredPresShellId = 0;
// We need to process pending RepaintRequests first.
while (!requests.empty()) {
mController->RequestContentRepaint(requests.front());
if (!mController) {
return NS_OK;
}
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.
RefPtr<GeckoContentController> controller = mController;
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.
Run();
if (mController) {
mController->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 = mPendingRepaintRequestQueue.begin();
it != mPendingRepaintRequestQueue.end(); it++) {
if (RepaintRequestKey{it->GetScrollId(), it->GetScrollUpdateType()} ==
key) {
mPendingRepaintRequestQueue.erase(it);
break;
}
}
}
mPendingRepaintRequestMap.insert(key);
mPendingRepaintRequestQueue.push_back(aRequest);
}
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.
Run();
if (mController) {
RefPtr<GeckoContentController> controller = mController;
controller->NotifyFlushComplete();
}
return;
}
EnsureRegisterAsEarlyRunner();
mNeedsFlushCompleteNotification = true;
}
bool APZTaskRunnable::IsRegistereddWithCurrentPresShell() const {
MOZ_ASSERT(mController);
uint32_t current = 0;
if (PresShell* presShell = mController->GetTopLevelPresShell()) {
current = presShell->GetPresShellId();
}
return mRegisteredPresShellId == current;
}
void APZTaskRunnable::EnsureRegisterAsEarlyRunner() {
if (IsRegistereddWithCurrentPresShell()) {
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();
mPendingRepaintRequestQueue.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

View File

@ -0,0 +1,88 @@
/* -*- 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/. */
#ifndef mozilla_layers_RepaintRequestRunnable_h
#define mozilla_layers_RepaintRequestRunnable_h
#include <deque>
#include <unordered_set>
#include "mozilla/layers/GeckoContentController.h"
#include "mozilla/layers/RepaintRequest.h"
#include "mozilla/layers/ScrollableLayerGuid.h"
#include "nsThreadUtils.h"
namespace mozilla {
namespace layers {
class GeckoContentController;
// A runnable invoked in nsRefreshDriver::Tick as an early runnable.
class APZTaskRunnable final : public Runnable {
public:
explicit APZTaskRunnable(GeckoContentController* aController)
: Runnable("RepaintRequestRunnable"),
mController(aController),
mRegisteredPresShellId(0),
mNeedsFlushCompleteNotification(false) {}
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_DECL_NSIRUNNABLE
// Queue a RepaintRequest.
// If there's already a RepaintRequest having the same scroll id, the old
// one will be discarded.
void QueueRequest(const RepaintRequest& aRequest);
void QueueFlushCompleteNotification();
void Revoke() {
mController = nullptr;
mRegisteredPresShellId = 0;
}
private:
void EnsureRegisterAsEarlyRunner();
bool IsRegistereddWithCurrentPresShell() const;
bool IsTestControllingRefreshesEnabled() const;
// Use a GeckoContentController raw pointer here since the owner of the
// GeckoContentController instance (an APZChild instance) holds a strong
// reference of this APZTaskRunnable instance and will call Revoke() before
// the GeckoContentController gets destroyed in the dtor of the APZChild
// instance.
GeckoContentController* mController;
struct RepaintRequestKey {
ScrollableLayerGuid::ViewID mScrollId;
RepaintRequest::ScrollOffsetUpdateType mScrollUpdateType;
bool operator==(const RepaintRequestKey& aOther) const {
return mScrollId == aOther.mScrollId &&
mScrollUpdateType == aOther.mScrollUpdateType;
}
struct HashFn {
std::size_t operator()(const RepaintRequestKey& aKey) const {
return HashGeneric(aKey.mScrollId, aKey.mScrollUpdateType);
}
};
};
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;
// 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
// have to remember the pres shell which is currently tied to the APZChild
// to deliver queued requests and notifications to the proper pres shell.
uint32_t mRegisteredPresShellId;
bool mNeedsFlushCompleteNotification;
};
} // namespace layers
} // namespace mozilla
#endif // mozilla_layers_RepaintRequestRunnable_h

View File

@ -76,6 +76,8 @@ class ChromeProcessController : public mozilla::layers::GeckoContentController {
const ScrollableLayerGuid::ViewID& aScrollId) override;
void CancelAutoscroll(const ScrollableLayerGuid& aGuid) override;
PresShell* GetTopLevelPresShell() const override { return GetPresShell(); }
private:
nsCOMPtr<nsIWidget> mWidget;
RefPtr<APZEventState> mAPZEventState;

View File

@ -104,5 +104,12 @@ void ContentProcessController::DispatchToRepaintThread(
NS_DispatchToMainThread(std::move(aTask));
}
PresShell* ContentProcessController::GetTopLevelPresShell() const {
if (!mBrowser) {
return nullptr;
}
return mBrowser->GetTopLevelPresShell();
}
} // namespace layers
} // namespace mozilla

View File

@ -76,6 +76,8 @@ class ContentProcessController final : public GeckoContentController {
void DispatchToRepaintThread(already_AddRefed<Runnable> aTask) override;
PresShell* GetTopLevelPresShell() const override;
private:
RefPtr<dom::BrowserChild> mBrowser;
};

View File

@ -21,6 +21,10 @@ APZChild::APZChild(RefPtr<GeckoContentController> aController)
}
APZChild::~APZChild() {
if (mAPZTaskRunnable) {
mAPZTaskRunnable->Revoke();
mAPZTaskRunnable = nullptr;
}
if (mController) {
mController->Destroy();
mController = nullptr;
@ -37,7 +41,9 @@ mozilla::ipc::IPCResult APZChild::RecvRequestContentRepaint(
const RepaintRequest& aRequest) {
MOZ_ASSERT(mController->IsRepaintThread());
mController->RequestContentRepaint(aRequest);
EnsureAPZTaskRunnable();
mAPZTaskRunnable->QueueRequest(aRequest);
return IPC_OK();
}
@ -70,8 +76,10 @@ mozilla::ipc::IPCResult APZChild::RecvNotifyAPZStateChange(
mozilla::ipc::IPCResult APZChild::RecvNotifyFlushComplete() {
MOZ_ASSERT(mController->IsRepaintThread());
EnsureAPZTaskRunnable();
mAPZTaskRunnable->QueueFlushCompleteNotification();
mController->NotifyFlushComplete();
return IPC_OK();
}

View File

@ -8,6 +8,7 @@
#define mozilla_layers_APZChild_h
#include "mozilla/layers/PAPZChild.h"
#include "mozilla/layers/APZTaskRunnable.h"
namespace mozilla {
namespace layers {
@ -60,7 +61,16 @@ class APZChild final : public PAPZChild {
mozilla::ipc::IPCResult RecvDestroy();
private:
void EnsureAPZTaskRunnable() {
if (!mAPZTaskRunnable) {
mAPZTaskRunnable = new APZTaskRunnable(mController);
}
}
RefPtr<GeckoContentController> mController;
// A runnable invoked in a nsRefreshDriver's tick to update multiple
// RepaintRequests and notify a "apz-repaints-flushed" at the same time.
RefPtr<APZTaskRunnable> mAPZTaskRunnable;
};
} // namespace layers

View File

@ -51,6 +51,7 @@ parent:
child:
async LayerTransforms(MatrixMessage[] aTransforms);
[Priority=control]
async RequestContentRepaint(RepaintRequest request);
async UpdateOverscrollVelocity(ScrollableLayerGuid aGuid, float aX, float aY, bool aIsRootContent);
@ -61,6 +62,7 @@ child:
async NotifyAPZStateChange(ScrollableLayerGuid aGuid, GeckoContentController_APZStateChange aChange, int aArg);
[Priority=control]
async NotifyFlushComplete();
async NotifyAsyncScrollbarDragInitiated(uint64_t aDragBlockId, ViewID aScrollId, ScrollDirection aDirection);

View File

@ -94,6 +94,7 @@ EXPORTS.mozilla.layers += [
"apz/util/ActiveElementManager.h",
"apz/util/APZCCallbackHelper.h",
"apz/util/APZEventState.h",
"apz/util/APZTaskRunnable.h",
"apz/util/APZThreadUtils.h",
"apz/util/ChromeProcessController.h",
"apz/util/ContentProcessController.h",
@ -320,6 +321,7 @@ UNIFIED_SOURCES += [
"apz/util/ActiveElementManager.cpp",
"apz/util/APZCCallbackHelper.cpp",
"apz/util/APZEventState.cpp",
"apz/util/APZTaskRunnable.cpp",
"apz/util/APZThreadUtils.cpp",
"apz/util/CheckerboardReportService.cpp",
"apz/util/ChromeProcessController.cpp",