/* -*- 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> APZUpdater::sWindowIdMap; APZUpdater::APZUpdater(const RefPtr& 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& 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(); NS_DispatchToMainThread(NS_NewRunnableFunction( "APZUpdater::ClearOnShutdown", [] { ClearOnShutdown(&sWindowIdMap); })); } (*sWindowIdMap)[wr::AsUint64(aWindowId)] = this; } /*static*/ void APZUpdater::SetUpdaterThread(const wr::WrWindowId& aWindowId) { if (RefPtr 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 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 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 updater = GetUpdater(aWindowId)) { updater->ProcessQueue(); } } void APZUpdater::ClearTree(LayersId aRootLayersId) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); RefPtr 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( "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 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(); auto previous = self->mScrollData.find(aOriginatingLayersId); // If there's the previous scroll data which hasn't yet been // processed, we need to merge the previous scroll position updates // into the latest one. if (previous != self->mScrollData.end()) { WebRenderScrollData& previousData = previous->second; if (previousData.GetWasUpdateSkipped()) { MOZ_ASSERT(previousData.IsFirstPaint()); aScrollData.PrependUpdates(previousData); } } self->mScrollData[aOriginatingLayersId] = std::move(aScrollData); auto root = self->mScrollData.find(aRootLayerTreeId); if (root == self->mScrollData.end()) { return; } if ((self->mApz->UpdateHitTestingTree( WebRenderScrollDataWrapper(*self, &(root->second)), isFirstPaint, aOriginatingLayersId, paintSequenceNumber) == APZCTreeManager::OriginatingLayersIdUpdated::No) && isFirstPaint) { // If the given |aOriginatingLayersId| data wasn't used for // updating, it's likly that the parent process hasn't yet // received the LayersId as "ReferentId", thus we need to process // it in a subsequent update where we got the "ReferentId". // // NOTE: We restrict the above previous scroll data prepending to // the first paint case, otherwise the cumulative scroll data may // be exploded if we have never received the "ReferenceId". self->mScrollData[aOriginatingLayersId].SetWasUpdateSkipped(); } })); } void APZUpdater::UpdateScrollOffsets(LayersId aRootLayerTreeId, LayersId aOriginatingLayersId, ScrollUpdatesMap&& aUpdates, uint32_t aPaintSequenceNumber) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); RefPtr 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& aOldUpdater) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); RunOnUpdaterThread(aLayersId, NewRunnableMethod>( "APZUpdater::NotifyLayerTreeAdopted", mApz, &APZCTreeManager::NotifyLayerTreeAdopted, aLayersId, aOldUpdater ? aOldUpdater->mApz : nullptr)); } void APZUpdater::NotifyLayerTreeRemoved(LayersId aLayersId) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); RefPtr 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 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 apz = mApz; RunOnUpdaterThread( aLayersId, NS_NewRunnableFunction("APZUpdater::SetTestAsyncScrollOffset", [=]() { RefPtr 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 apz = mApz; RunOnUpdaterThread( aLayersId, NS_NewRunnableFunction("APZUpdater::SetTestAsyncZoom", [=]() { RefPtr 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 aTask) { RefPtr 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 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 aTask) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); RefPtr 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::GetUpdater( const wr::WrWindowId& aWindowId) { RefPtr 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 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); }