summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/src/APZUpdater.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /gfx/layers/apz/src/APZUpdater.cpp
parentInitial commit. (diff)
downloadfirefox-upstream/124.0.1.tar.xz
firefox-upstream/124.0.1.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--gfx/layers/apz/src/APZUpdater.cpp546
1 files changed, 546 insertions, 0 deletions
diff --git a/gfx/layers/apz/src/APZUpdater.cpp b/gfx/layers/apz/src/APZUpdater.cpp
new file mode 100644
index 0000000000..2bbad6e1a7
--- /dev/null
+++ b/gfx/layers/apz/src/APZUpdater.cpp
@@ -0,0 +1,546 @@
+/* -*- 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 "mozilla/layers/APZUpdater.h"
+
+#include "APZCTreeManager.h"
+#include "AsyncPanZoomController.h"
+#include "base/task.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/SynchronousTask.h"
+#include "mozilla/layers/WebRenderScrollDataWrapper.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+
+namespace mozilla {
+namespace layers {
+
+StaticMutex APZUpdater::sWindowIdLock;
+StaticAutoPtr<std::unordered_map<uint64_t, APZUpdater*>>
+ APZUpdater::sWindowIdMap;
+
+APZUpdater::APZUpdater(const RefPtr<APZCTreeManager>& aApz,
+ bool aConnectedToWebRender)
+ : mApz(aApz),
+ mDestroyed(false),
+ mConnectedToWebRender(aConnectedToWebRender),
+ mThreadIdLock("APZUpdater::ThreadIdLock"),
+ mQueueLock("APZUpdater::QueueLock") {
+ MOZ_ASSERT(aApz);
+ mApz->SetUpdater(this);
+}
+
+APZUpdater::~APZUpdater() {
+ mApz->SetUpdater(nullptr);
+
+ StaticMutexAutoLock lock(sWindowIdLock);
+ if (mWindowId) {
+ MOZ_ASSERT(sWindowIdMap);
+ // Ensure that ClearTree was called and the task got run
+ MOZ_ASSERT(sWindowIdMap->find(wr::AsUint64(*mWindowId)) ==
+ sWindowIdMap->end());
+ }
+}
+
+bool APZUpdater::HasTreeManager(const RefPtr<APZCTreeManager>& aApz) {
+ return aApz.get() == mApz.get();
+}
+
+void APZUpdater::SetWebRenderWindowId(const wr::WindowId& aWindowId) {
+ StaticMutexAutoLock lock(sWindowIdLock);
+ MOZ_ASSERT(!mWindowId);
+ mWindowId = Some(aWindowId);
+ if (!sWindowIdMap) {
+ sWindowIdMap = new std::unordered_map<uint64_t, APZUpdater*>();
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "APZUpdater::ClearOnShutdown", [] { ClearOnShutdown(&sWindowIdMap); }));
+ }
+ (*sWindowIdMap)[wr::AsUint64(aWindowId)] = this;
+}
+
+/*static*/
+void APZUpdater::SetUpdaterThread(const wr::WrWindowId& aWindowId) {
+ if (RefPtr<APZUpdater> updater = GetUpdater(aWindowId)) {
+ MutexAutoLock lock(updater->mThreadIdLock);
+ updater->mUpdaterThreadId = Some(PlatformThread::CurrentId());
+ }
+}
+
+// Takes a conditional lock!
+/*static*/
+void APZUpdater::PrepareForSceneSwap(const wr::WrWindowId& aWindowId)
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ if (RefPtr<APZUpdater> updater = GetUpdater(aWindowId)) {
+ updater->mApz->LockTree();
+ }
+}
+
+// Assumes we took a conditional lock!
+/*static*/
+void APZUpdater::CompleteSceneSwap(const wr::WrWindowId& aWindowId,
+ const wr::WrPipelineInfo& aInfo) {
+ RefPtr<APZUpdater> updater = GetUpdater(aWindowId);
+ if (!updater) {
+ // This should only happen in cases where PrepareForSceneSwap also got a
+ // null updater. No updater-thread tasks get run between PrepareForSceneSwap
+ // and this function, so there is no opportunity for the updater mapping
+ // to have gotten removed from sWindowIdMap in between the two calls.
+ return;
+ }
+ updater->mApz->mTreeLock.AssertCurrentThreadIn();
+
+ for (const auto& removedPipeline : aInfo.removed_pipelines) {
+ LayersId layersId = wr::AsLayersId(removedPipeline.pipeline_id);
+ updater->mEpochData.erase(layersId);
+ }
+ // Reset the built info for all pipelines, then put it back for the ones
+ // that got built in this scene swap.
+ for (auto& i : updater->mEpochData) {
+ i.second.mBuilt = Nothing();
+ }
+ for (const auto& epoch : aInfo.epochs) {
+ LayersId layersId = wr::AsLayersId(epoch.pipeline_id);
+ updater->mEpochData[layersId].mBuilt = Some(epoch.epoch);
+ }
+
+ // Run any tasks that got unblocked, then unlock the tree. The order is
+ // important because we want to run all the tasks up to and including the
+ // UpdateHitTestingTree calls corresponding to the built epochs, and we
+ // want to run those before we release the lock (i.e. atomically with the
+ // scene swap). This ensures that any hit-tests always encounter a consistent
+ // state between the APZ tree and the built scene in WR.
+ //
+ // While we could add additional information to the queued tasks to figure
+ // out the minimal set of tasks we want to run here, it's easier and harmless
+ // to just run all the queued and now-unblocked tasks inside the lock.
+ //
+ // Note that the ProcessQueue here might remove the window id -> APZUpdater
+ // mapping from sWindowIdMap, but we still unlock the tree successfully to
+ // leave things in a good state.
+ updater->ProcessQueue();
+
+ updater->mApz->UnlockTree();
+}
+
+/*static*/
+void APZUpdater::ProcessPendingTasks(const wr::WrWindowId& aWindowId) {
+ if (RefPtr<APZUpdater> updater = GetUpdater(aWindowId)) {
+ updater->ProcessQueue();
+ }
+}
+
+void APZUpdater::ClearTree(LayersId aRootLayersId) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ RefPtr<APZUpdater> self = this;
+ RunOnUpdaterThread(aRootLayersId,
+ NS_NewRunnableFunction("APZUpdater::ClearTree", [=]() {
+ self->mApz->ClearTree();
+ self->mDestroyed = true;
+
+ // Once ClearTree is called on the APZCTreeManager, we
+ // are in a shutdown phase. After this point it's ok if
+ // WebRender cannot get a hold of the updater via the
+ // window id, and it's a good point to remove the mapping
+ // and avoid leaving a dangling pointer to this object.
+ StaticMutexAutoLock lock(sWindowIdLock);
+ if (self->mWindowId) {
+ MOZ_ASSERT(sWindowIdMap);
+ sWindowIdMap->erase(wr::AsUint64(*(self->mWindowId)));
+ }
+ }));
+}
+
+void APZUpdater::UpdateFocusState(LayersId aRootLayerTreeId,
+ LayersId aOriginatingLayersId,
+ const FocusTarget& aFocusTarget) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ RunOnUpdaterThread(aOriginatingLayersId,
+ NewRunnableMethod<LayersId, LayersId, FocusTarget>(
+ "APZUpdater::UpdateFocusState", mApz,
+ &APZCTreeManager::UpdateFocusState, aRootLayerTreeId,
+ aOriginatingLayersId, aFocusTarget));
+}
+
+void APZUpdater::UpdateScrollDataAndTreeState(
+ LayersId aRootLayerTreeId, LayersId aOriginatingLayersId,
+ const wr::Epoch& aEpoch, WebRenderScrollData&& aScrollData) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ RefPtr<APZUpdater> self = this;
+ // Insert an epoch requirement update into the queue, so that
+ // tasks inserted into the queue after this point only get executed
+ // once the epoch requirement is satisfied. In particular, the
+ // UpdateHitTestingTree call below needs to wait until the epoch requirement
+ // is satisfied, which is why it is a separate task in the queue.
+ RunOnUpdaterThread(
+ aOriginatingLayersId,
+ NS_NewRunnableFunction("APZUpdater::UpdateEpochRequirement", [=]() {
+ if (aRootLayerTreeId == aOriginatingLayersId) {
+ self->mEpochData[aOriginatingLayersId].mIsRoot = true;
+ }
+ self->mEpochData[aOriginatingLayersId].mRequired = aEpoch;
+ }));
+ RunOnUpdaterThread(
+ aOriginatingLayersId,
+ NS_NewRunnableFunction(
+ "APZUpdater::UpdateHitTestingTree",
+ [=, aScrollData = std::move(aScrollData)]() mutable {
+ auto isFirstPaint = aScrollData.IsFirstPaint();
+ auto paintSequenceNumber = aScrollData.GetPaintSequenceNumber();
+
+ self->mScrollData[aOriginatingLayersId] = std::move(aScrollData);
+ auto root = self->mScrollData.find(aRootLayerTreeId);
+ if (root == self->mScrollData.end()) {
+ return;
+ }
+ self->mApz->UpdateHitTestingTree(
+ WebRenderScrollDataWrapper(*self, &(root->second)),
+ isFirstPaint, aOriginatingLayersId, paintSequenceNumber);
+ }));
+}
+
+void APZUpdater::UpdateScrollOffsets(LayersId aRootLayerTreeId,
+ LayersId aOriginatingLayersId,
+ ScrollUpdatesMap&& aUpdates,
+ uint32_t aPaintSequenceNumber) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ RefPtr<APZUpdater> self = this;
+ RunOnUpdaterThread(
+ aOriginatingLayersId,
+ NS_NewRunnableFunction(
+ "APZUpdater::UpdateScrollOffsets",
+ [=, updates = std::move(aUpdates)]() mutable {
+ self->mScrollData[aOriginatingLayersId].ApplyUpdates(
+ std::move(updates), aPaintSequenceNumber);
+ auto root = self->mScrollData.find(aRootLayerTreeId);
+ if (root == self->mScrollData.end()) {
+ return;
+ }
+ self->mApz->UpdateHitTestingTree(
+ WebRenderScrollDataWrapper(*self, &(root->second)),
+ /*isFirstPaint*/ false, aOriginatingLayersId,
+ aPaintSequenceNumber);
+ }));
+}
+
+void APZUpdater::NotifyLayerTreeAdopted(LayersId aLayersId,
+ const RefPtr<APZUpdater>& aOldUpdater) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ RunOnUpdaterThread(aLayersId,
+ NewRunnableMethod<LayersId, RefPtr<APZCTreeManager>>(
+ "APZUpdater::NotifyLayerTreeAdopted", mApz,
+ &APZCTreeManager::NotifyLayerTreeAdopted, aLayersId,
+ aOldUpdater ? aOldUpdater->mApz : nullptr));
+}
+
+void APZUpdater::NotifyLayerTreeRemoved(LayersId aLayersId) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ RefPtr<APZUpdater> self = this;
+ RunOnUpdaterThread(
+ aLayersId,
+ NS_NewRunnableFunction("APZUpdater::NotifyLayerTreeRemoved", [=]() {
+ self->mEpochData.erase(aLayersId);
+ self->mScrollData.erase(aLayersId);
+ self->mApz->NotifyLayerTreeRemoved(aLayersId);
+ }));
+}
+
+bool APZUpdater::GetAPZTestData(LayersId aLayersId, APZTestData* aOutData) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ RefPtr<APZCTreeManager> apz = mApz;
+ bool ret = false;
+ SynchronousTask waiter("APZUpdater::GetAPZTestData");
+ RunOnUpdaterThread(
+ aLayersId, NS_NewRunnableFunction("APZUpdater::GetAPZTestData", [&]() {
+ AutoCompleteTask notifier(&waiter);
+ ret = apz->GetAPZTestData(aLayersId, aOutData);
+ }));
+
+ // Wait until the task posted above has run and populated aOutData and ret
+ waiter.Wait();
+
+ return ret;
+}
+
+void APZUpdater::SetTestAsyncScrollOffset(
+ LayersId aLayersId, const ScrollableLayerGuid::ViewID& aScrollId,
+ const CSSPoint& aOffset) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ RefPtr<APZCTreeManager> apz = mApz;
+ RunOnUpdaterThread(
+ aLayersId,
+ NS_NewRunnableFunction("APZUpdater::SetTestAsyncScrollOffset", [=]() {
+ RefPtr<AsyncPanZoomController> apzc =
+ apz->GetTargetAPZC(aLayersId, aScrollId);
+ if (apzc) {
+ apzc->SetTestAsyncScrollOffset(aOffset);
+ } else {
+ NS_WARNING("Unable to find APZC in SetTestAsyncScrollOffset");
+ }
+ }));
+}
+
+void APZUpdater::SetTestAsyncZoom(LayersId aLayersId,
+ const ScrollableLayerGuid::ViewID& aScrollId,
+ const LayerToParentLayerScale& aZoom) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ RefPtr<APZCTreeManager> apz = mApz;
+ RunOnUpdaterThread(
+ aLayersId, NS_NewRunnableFunction("APZUpdater::SetTestAsyncZoom", [=]() {
+ RefPtr<AsyncPanZoomController> apzc =
+ apz->GetTargetAPZC(aLayersId, aScrollId);
+ if (apzc) {
+ apzc->SetTestAsyncZoom(aZoom);
+ } else {
+ NS_WARNING("Unable to find APZC in SetTestAsyncZoom");
+ }
+ }));
+}
+
+const WebRenderScrollData* APZUpdater::GetScrollData(LayersId aLayersId) const {
+ AssertOnUpdaterThread();
+ auto it = mScrollData.find(aLayersId);
+ return (it == mScrollData.end() ? nullptr : &(it->second));
+}
+
+void APZUpdater::AssertOnUpdaterThread() const {
+ if (APZThreadUtils::GetThreadAssertionsEnabled()) {
+ MOZ_ASSERT(IsUpdaterThread());
+ }
+}
+
+void APZUpdater::RunOnUpdaterThread(LayersId aLayersId,
+ already_AddRefed<Runnable> aTask) {
+ RefPtr<Runnable> task = aTask;
+
+ // In the scenario where IsConnectedToWebRender() is true, this function
+ // might get called early (before mUpdaterThreadId is set). In that case
+ // IsUpdaterThread() will return false and we'll queue the task onto
+ // mUpdaterQueue. This is fine; the task is still guaranteed to run (barring
+ // catastrophic failure) because the WakeSceneBuilder call will still trigger
+ // the callback to run tasks.
+
+ if (IsUpdaterThread()) {
+ // This function should only be called from the updater thread in test
+ // scenarios where we are not connected to WebRender. If it were called from
+ // the updater thread when we are connected to WebRender, running the task
+ // right away would be incorrect (we'd need to check that |aLayersId|
+ // isn't blocked, and if it is then enqueue the task instead).
+ MOZ_ASSERT(!IsConnectedToWebRender());
+ task->Run();
+ return;
+ }
+
+ if (IsConnectedToWebRender()) {
+ // If the updater thread is a WebRender thread, and we're not on it
+ // right now, save the task in the queue. We will run tasks from the queue
+ // during the callback from the updater thread, which we trigger by the
+ // call to WakeSceneBuilder.
+
+ bool sendWakeMessage = true;
+ { // scope lock
+ MutexAutoLock lock(mQueueLock);
+ for (const auto& queuedTask : mUpdaterQueue) {
+ if (queuedTask.mLayersId == aLayersId) {
+ // If there's already a task in the queue with this layers id, then
+ // we must have previously sent a WakeSceneBuilder message (when
+ // adding the first task with this layers id to the queue). Either
+ // that hasn't been fully processed yet, or the layers id is blocked
+ // waiting for an epoch - in either case there's no point in sending
+ // another WakeSceneBuilder message.
+ sendWakeMessage = false;
+ break;
+ }
+ }
+ mUpdaterQueue.push_back(QueuedTask{aLayersId, task});
+ }
+ if (sendWakeMessage) {
+ RefPtr<wr::WebRenderAPI> api = mApz->GetWebRenderAPI();
+ if (api) {
+ api->WakeSceneBuilder();
+ } else {
+ // Not sure if this can happen, but it might be possible. If it does,
+ // the task is in the queue, but if we didn't get a WebRenderAPI it
+ // might never run, or it might run later if we manage to get a
+ // WebRenderAPI later. For now let's just emit a warning, this can
+ // probably be upgraded to an assert later.
+ NS_WARNING("Possibly dropping task posted to updater thread");
+ }
+ }
+ return;
+ }
+
+ if (CompositorThread()) {
+ CompositorThread()->Dispatch(task.forget());
+ } else {
+ // Could happen during startup
+ NS_WARNING("Dropping task posted to updater thread");
+ }
+}
+
+bool APZUpdater::IsUpdaterThread() const {
+ if (IsConnectedToWebRender()) {
+ // If the updater thread id isn't set yet then we cannot be running on the
+ // updater thread (because we will have the thread id before we run any
+ // C++ code on it, and this function is only ever invoked from C++ code),
+ // so return false in that scenario.
+ MutexAutoLock lock(mThreadIdLock);
+ return mUpdaterThreadId && PlatformThread::CurrentId() == *mUpdaterThreadId;
+ }
+ return CompositorThreadHolder::IsInCompositorThread();
+}
+
+void APZUpdater::RunOnControllerThread(LayersId aLayersId,
+ already_AddRefed<Runnable> aTask) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ RefPtr<Runnable> task = aTask;
+
+ RunOnUpdaterThread(
+ aLayersId,
+ NewRunnableFunction("APZUpdater::RunOnControllerThread",
+ &APZThreadUtils::RunOnControllerThread,
+ std::move(task), nsIThread::DISPATCH_NORMAL));
+}
+
+bool APZUpdater::IsConnectedToWebRender() const {
+ return mConnectedToWebRender;
+}
+
+/*static*/
+already_AddRefed<APZUpdater> APZUpdater::GetUpdater(
+ const wr::WrWindowId& aWindowId) {
+ RefPtr<APZUpdater> updater;
+ StaticMutexAutoLock lock(sWindowIdLock);
+ if (sWindowIdMap) {
+ auto it = sWindowIdMap->find(wr::AsUint64(aWindowId));
+ if (it != sWindowIdMap->end()) {
+ updater = it->second;
+ }
+ }
+ return updater.forget();
+}
+
+void APZUpdater::ProcessQueue() {
+ MOZ_ASSERT(!mDestroyed);
+
+ { // scope lock to check for emptiness
+ MutexAutoLock lock(mQueueLock);
+ if (mUpdaterQueue.empty()) {
+ return;
+ }
+ }
+
+ std::deque<QueuedTask> blockedTasks;
+ while (true) {
+ QueuedTask task;
+
+ { // scope lock to extract a task
+ MutexAutoLock lock(mQueueLock);
+ if (mUpdaterQueue.empty()) {
+ // If we're done processing mUpdaterQueue, swap the tasks that are
+ // still blocked back in and finish
+ std::swap(mUpdaterQueue, blockedTasks);
+ break;
+ }
+ task = mUpdaterQueue.front();
+ mUpdaterQueue.pop_front();
+ }
+
+ // We check the task to see if it is blocked. Note that while this
+ // ProcessQueue function is executing, a particular layers id cannot go
+ // from blocked to unblocked, because only CompleteSceneSwap can unblock
+ // a layers id, and that also runs on the updater thread. If somehow
+ // a layers id gets unblocked while we're processing the queue, then it
+ // might result in tasks getting executed out of order.
+
+ auto it = mEpochData.find(task.mLayersId);
+ if (it != mEpochData.end() && it->second.IsBlocked()) {
+ // If this task is blocked, put it into the blockedTasks queue that
+ // we will replace mUpdaterQueue with
+ blockedTasks.push_back(task);
+ } else {
+ // Run and discard the task
+ task.mRunnable->Run();
+ }
+ }
+
+ if (mDestroyed) {
+ // If we get here, then we must have just run the ClearTree task for
+ // this updater. There might be tasks in the queue from content subtrees
+ // of this window that are blocked due to stale epochs. This can happen
+ // if the tasks were queued after the root pipeline was removed in
+ // WebRender, which prevents scene builds (and therefore prevents us
+ // from getting updated epochs via CompleteSceneSwap). See bug 1465658
+ // comment 43 for some more context.
+ // To avoid leaking these tasks, we discard the contents of the queue.
+ // This happens during window shutdown so if we don't run the tasks it's
+ // not going to matter much.
+ MutexAutoLock lock(mQueueLock);
+ if (!mUpdaterQueue.empty()) {
+ mUpdaterQueue.clear();
+ }
+ }
+}
+
+void APZUpdater::MarkAsDetached(LayersId aLayersId) {
+ mApz->MarkAsDetached(aLayersId);
+}
+
+APZUpdater::EpochState::EpochState() : mRequired{0}, mIsRoot(false) {}
+
+bool APZUpdater::EpochState::IsBlocked() const {
+ // The root is a special case because we basically assume it is "visible"
+ // even before it is built for the first time. This is because building the
+ // scene automatically makes it visible, and we need to make sure the APZ
+ // scroll data gets applied atomically with that happening.
+ //
+ // Layer subtrees on the other hand do not automatically become visible upon
+ // being built, because there must be a another layer tree update to change
+ // the visibility (i.e. an ancestor layer tree update that adds the necessary
+ // reflayer to complete the chain of reflayers).
+ //
+ // So in the case of non-visible subtrees, we know that no hit-test will
+ // actually end up hitting that subtree either before or after the scene swap,
+ // because the subtree will remain non-visible. That in turns means that we
+ // can apply the APZ scroll data for that subtree epoch before the scene is
+ // built, because it's not going to get used anyway. And that means we don't
+ // need to block the queue for non-visible subtrees. Which is a good thing,
+ // because in practice it seems like we often have non-visible subtrees sent
+ // to the compositor from content.
+ if (mIsRoot && !mBuilt) {
+ return true;
+ }
+ return mBuilt && (*mBuilt < mRequired);
+}
+
+} // namespace layers
+} // namespace mozilla
+
+// Rust callback implementations
+
+void apz_register_updater(mozilla::wr::WrWindowId aWindowId) {
+ mozilla::layers::APZUpdater::SetUpdaterThread(aWindowId);
+}
+
+void apz_pre_scene_swap(mozilla::wr::WrWindowId aWindowId) {
+ mozilla::layers::APZUpdater::PrepareForSceneSwap(aWindowId);
+}
+
+void apz_post_scene_swap(mozilla::wr::WrWindowId aWindowId,
+ const mozilla::wr::WrPipelineInfo* aInfo) {
+ mozilla::layers::APZUpdater::CompleteSceneSwap(aWindowId, *aInfo);
+}
+
+void apz_run_updater(mozilla::wr::WrWindowId aWindowId) {
+ mozilla::layers::APZUpdater::ProcessPendingTasks(aWindowId);
+}
+
+void apz_deregister_updater(mozilla::wr::WrWindowId aWindowId) {
+ // Run anything that's still left.
+ mozilla::layers::APZUpdater::ProcessPendingTasks(aWindowId);
+}