From 6acdee2bb05138215b5b838e1f5939dc0d80fb7a Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 30 May 2018 15:54:50 -0400 Subject: [PATCH] Bug 1457590 - Add the HitTestingTreeNodeAutoLock class. r=botond This adds the HitTestingTreeNodeAutoLock RAII class that allows using a HitTestingTreeNode safely outside a tree lock, and ensures that the node won't get destroyed or recycled concurrently. MozReview-Commit-ID: 8Tb3vdIeUgr --HG-- extra : rebase_source : 6eed3e733edcaa4088da52882a9b3dc8c2355c2e --- gfx/layers/apz/src/HitTestingTreeNode.cpp | 56 ++++++++++++++++++++++- gfx/layers/apz/src/HitTestingTreeNode.h | 41 +++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/gfx/layers/apz/src/HitTestingTreeNode.cpp b/gfx/layers/apz/src/HitTestingTreeNode.cpp index d69d19067f4a..d63ac84ae27d 100644 --- a/gfx/layers/apz/src/HitTestingTreeNode.cpp +++ b/gfx/layers/apz/src/HitTestingTreeNode.cpp @@ -24,6 +24,7 @@ HitTestingTreeNode::HitTestingTreeNode(AsyncPanZoomController* aApzc, LayersId aLayersId) : mApzc(aApzc) , mIsPrimaryApzcHolder(aIsPrimaryHolder) + , mLocked(false) , mLayersId(aLayersId) , mScrollbarAnimationId(0) , mFixedPosTarget(FrameMetrics::NULL_SCROLL_ID) @@ -73,7 +74,7 @@ HitTestingTreeNode::Destroy() bool HitTestingTreeNode::IsRecyclable(const RecursiveMutexAutoLock& aProofOfTreeLock) { - return !IsPrimaryHolder(); + return !(IsPrimaryHolder() || mLocked); } void @@ -406,5 +407,58 @@ HitTestingTreeNode::SetApzcParent(AsyncPanZoomController* aParent) } } +void +HitTestingTreeNode::Lock(const RecursiveMutexAutoLock& aProofOfTreeLock) +{ + MOZ_ASSERT(!mLocked); + mLocked = true; +} + +void +HitTestingTreeNode::Unlock(const RecursiveMutexAutoLock& aProofOfTreeLock) +{ + MOZ_ASSERT(mLocked); + mLocked = false; +} + +HitTestingTreeNodeAutoLock::HitTestingTreeNodeAutoLock() + : mTreeMutex(nullptr) +{ +} + +HitTestingTreeNodeAutoLock::~HitTestingTreeNodeAutoLock() +{ + Clear(); +} + +void +HitTestingTreeNodeAutoLock::Initialize(const RecursiveMutexAutoLock& aProofOfTreeLock, + already_AddRefed aNode, + RecursiveMutex& aTreeMutex) +{ + MOZ_ASSERT(!mNode); + + mNode = aNode; + mTreeMutex = &aTreeMutex; + + mNode->Lock(aProofOfTreeLock); +} + +void +HitTestingTreeNodeAutoLock::Clear() +{ + if (!mNode) { + return; + } + MOZ_ASSERT(mTreeMutex); + + { // scope lock + RecursiveMutexAutoLock lock(*mTreeMutex); + mNode->Unlock(lock); + } + mNode = nullptr; + mTreeMutex = nullptr; +} + } // namespace layers } // namespace mozilla diff --git a/gfx/layers/apz/src/HitTestingTreeNode.h b/gfx/layers/apz/src/HitTestingTreeNode.h index 7cf2d314cc77..ea11edff39d3 100644 --- a/gfx/layers/apz/src/HitTestingTreeNode.h +++ b/gfx/layers/apz/src/HitTestingTreeNode.h @@ -48,6 +48,13 @@ class AsyncPanZoomController; * Accessing the compositor layer tree can only be done on the compositor * thread, and so it is simpler to make a copy of the hit-testing related * properties into a separate tree. + * + * The tree pointers on the node (mLastChild, etc.) can only be manipulated + * while holding the APZ tree lock. Any code that wishes to use a + * HitTestingTreeNode outside of holding the tree lock should do so by using + * the HitTestingTreeNodeAutoLock wrapper, which prevents the node from + * being recycled (and also holds a RefPtr to the node to prevent it from + * getting freed). */ class HitTestingTreeNode { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HitTestingTreeNode); @@ -137,6 +144,12 @@ public: void Dump(const char* aPrefix = "") const; private: + friend class HitTestingTreeNodeAutoLock; + // Functions that are private but called from HitTestingTreeNodeAutoLock + void Lock(const RecursiveMutexAutoLock& aProofOfTreeLock); + void Unlock(const RecursiveMutexAutoLock& aProofOfTreeLock); + + void SetApzcParent(AsyncPanZoomController* aApzc); RefPtr mLastChild; @@ -145,6 +158,7 @@ private: RefPtr mApzc; bool mIsPrimaryApzcHolder; + bool mLocked; LayersId mLayersId; @@ -193,6 +207,33 @@ private: EventRegionsOverride mOverride; }; +/** + * A class that allows safe usage of a HitTestingTreeNode outside of the APZ + * tree lock. In general, this class should be Initialize()'d inside the tree + * lock (enforced by the proof-of-lock to Initialize), and then can be returned + * to a scope outside the tree lock and used safely. Upon destruction or + * Clear() being called, it unlocks the underlying node at which point it can + * be recycled or freed. + */ +class MOZ_RAII HitTestingTreeNodeAutoLock +{ +public: + HitTestingTreeNodeAutoLock(); + HitTestingTreeNodeAutoLock(const HitTestingTreeNodeAutoLock&) = delete; + HitTestingTreeNodeAutoLock& operator=(const HitTestingTreeNodeAutoLock&) = delete; + HitTestingTreeNodeAutoLock(HitTestingTreeNodeAutoLock&&) = delete; + ~HitTestingTreeNodeAutoLock(); + + void Initialize(const RecursiveMutexAutoLock& aProofOfTreeLock, + already_AddRefed aNode, + RecursiveMutex& aTreeMutex); + void Clear(); + +private: + RefPtr mNode; + RecursiveMutex* mTreeMutex; +}; + } // namespace layers } // namespace mozilla