/* 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 "HitTestingTreeNode.h" #include #include "AsyncPanZoomController.h" // for AsyncPanZoomController #include "mozilla/StaticPrefs_layout.h" #include "mozilla/gfx/Point.h" // for Point4D #include "mozilla/layers/APZUtils.h" // for AsyncTransform, CompleteAsyncTransform #include "mozilla/layers/AsyncDragMetrics.h" // for AsyncDragMetrics #include "mozilla/ToString.h" // for ToString #include "nsPrintfCString.h" // for nsPrintfCString #include "UnitTransforms.h" // for ViewAs static mozilla::LazyLogModule sApzMgrLog("apz.manager"); namespace mozilla { namespace layers { using gfx::CompositorHitTestInfo; HitTestingTreeNode::HitTestingTreeNode(AsyncPanZoomController* aApzc, bool aIsPrimaryHolder, LayersId aLayersId) : mApzc(aApzc), mIsPrimaryApzcHolder(aIsPrimaryHolder), mLockCount(0), mLayersId(aLayersId), mFixedPosTarget(ScrollableLayerGuid::NULL_SCROLL_ID), mStickyPosTarget(ScrollableLayerGuid::NULL_SCROLL_ID), mOverride(EventRegionsOverride::NoOverride) { if (mIsPrimaryApzcHolder) { MOZ_ASSERT(mApzc); } MOZ_ASSERT(!mApzc || mApzc->GetLayersId() == mLayersId); } void HitTestingTreeNode::RecycleWith( const RecursiveMutexAutoLock& aProofOfTreeLock, AsyncPanZoomController* aApzc, LayersId aLayersId) { MOZ_ASSERT(IsRecyclable(aProofOfTreeLock)); Destroy(); // clear out tree pointers mApzc = aApzc; mLayersId = aLayersId; MOZ_ASSERT(!mApzc || mApzc->GetLayersId() == mLayersId); // The caller is expected to call appropriate setters (SetHitTestData, // SetScrollbarData, SetFixedPosData, SetStickyPosData, etc.) to repopulate // all the data fields before using this node for "real work". Otherwise // those data fields may contain stale information from the previous use // of this node object. } HitTestingTreeNode::~HitTestingTreeNode() = default; void HitTestingTreeNode::Destroy() { // This runs on the updater thread, it's not worth passing around extra raw // pointers just to assert it. mPrevSibling = nullptr; mLastChild = nullptr; mParent = nullptr; if (mApzc) { if (mIsPrimaryApzcHolder) { mApzc->Destroy(); } mApzc = nullptr; } } bool HitTestingTreeNode::IsRecyclable( const RecursiveMutexAutoLock& aProofOfTreeLock) { return !(IsPrimaryHolder() || (mLockCount > 0)); } void HitTestingTreeNode::SetLastChild(HitTestingTreeNode* aChild) { mLastChild = aChild; if (aChild) { aChild->mParent = this; if (aChild->GetApzc()) { AsyncPanZoomController* parent = GetNearestContainingApzc(); // We assume that HitTestingTreeNodes with an ancestor/descendant // relationship cannot both point to the same APZC instance. This // assertion only covers a subset of cases in which that might occur, // but it's better than nothing. MOZ_ASSERT(aChild->GetApzc() != parent); aChild->SetApzcParent(parent); } } } void HitTestingTreeNode::SetScrollbarData( const Maybe& aScrollbarAnimationId, const ScrollbarData& aScrollbarData) { mScrollbarAnimationId = aScrollbarAnimationId; mScrollbarData = aScrollbarData; } bool HitTestingTreeNode::MatchesScrollDragMetrics( const AsyncDragMetrics& aDragMetrics, LayersId aLayersId) const { return IsScrollThumbNode() && mLayersId == aLayersId && mScrollbarData.mDirection == aDragMetrics.mDirection && mScrollbarData.mTargetViewId == aDragMetrics.mViewId; } bool HitTestingTreeNode::IsScrollThumbNode() const { return mScrollbarData.mScrollbarLayerType == layers::ScrollbarLayerType::Thumb; } bool HitTestingTreeNode::IsScrollbarNode() const { return mScrollbarData.mScrollbarLayerType != layers::ScrollbarLayerType::None; } bool HitTestingTreeNode::IsScrollbarContainerNode() const { return mScrollbarData.mScrollbarLayerType == layers::ScrollbarLayerType::Container; } ScrollDirection HitTestingTreeNode::GetScrollbarDirection() const { MOZ_ASSERT(IsScrollbarNode()); MOZ_ASSERT(mScrollbarData.mDirection.isSome()); return *mScrollbarData.mDirection; } ScrollableLayerGuid::ViewID HitTestingTreeNode::GetScrollTargetId() const { return mScrollbarData.mTargetViewId; } Maybe HitTestingTreeNode::GetScrollbarAnimationId() const { return mScrollbarAnimationId; } const ScrollbarData& HitTestingTreeNode::GetScrollbarData() const { return mScrollbarData; } void HitTestingTreeNode::SetFixedPosData( ScrollableLayerGuid::ViewID aFixedPosTarget, SideBits aFixedPosSides, const Maybe& aFixedPositionAnimationId) { mFixedPosTarget = aFixedPosTarget; mFixedPosSides = aFixedPosSides; mFixedPositionAnimationId = aFixedPositionAnimationId; } ScrollableLayerGuid::ViewID HitTestingTreeNode::GetFixedPosTarget() const { return mFixedPosTarget; } SideBits HitTestingTreeNode::GetFixedPosSides() const { return mFixedPosSides; } Maybe HitTestingTreeNode::GetFixedPositionAnimationId() const { return mFixedPositionAnimationId; } void HitTestingTreeNode::SetPrevSibling(HitTestingTreeNode* aSibling) { mPrevSibling = aSibling; if (aSibling) { aSibling->mParent = mParent; if (aSibling->GetApzc()) { AsyncPanZoomController* parent = mParent ? mParent->GetNearestContainingApzc() : nullptr; aSibling->SetApzcParent(parent); } } } void HitTestingTreeNode::SetStickyPosData( ScrollableLayerGuid::ViewID aStickyPosTarget, const LayerRectAbsolute& aScrollRangeOuter, const LayerRectAbsolute& aScrollRangeInner, const Maybe& aStickyPositionAnimationId) { mStickyPosTarget = aStickyPosTarget; mStickyScrollRangeOuter = aScrollRangeOuter; mStickyScrollRangeInner = aScrollRangeInner; mStickyPositionAnimationId = aStickyPositionAnimationId; } ScrollableLayerGuid::ViewID HitTestingTreeNode::GetStickyPosTarget() const { return mStickyPosTarget; } const LayerRectAbsolute& HitTestingTreeNode::GetStickyScrollRangeOuter() const { return mStickyScrollRangeOuter; } const LayerRectAbsolute& HitTestingTreeNode::GetStickyScrollRangeInner() const { return mStickyScrollRangeInner; } Maybe HitTestingTreeNode::GetStickyPositionAnimationId() const { return mStickyPositionAnimationId; } void HitTestingTreeNode::MakeRoot() { mParent = nullptr; if (GetApzc()) { SetApzcParent(nullptr); } } HitTestingTreeNode* HitTestingTreeNode::GetFirstChild() const { HitTestingTreeNode* child = GetLastChild(); while (child && child->GetPrevSibling()) { child = child->GetPrevSibling(); } return child; } HitTestingTreeNode* HitTestingTreeNode::GetLastChild() const { return mLastChild; } HitTestingTreeNode* HitTestingTreeNode::GetPrevSibling() const { return mPrevSibling; } HitTestingTreeNode* HitTestingTreeNode::GetParent() const { return mParent; } bool HitTestingTreeNode::IsAncestorOf(const HitTestingTreeNode* aOther) const { for (const HitTestingTreeNode* cur = aOther; cur; cur = cur->GetParent()) { if (cur == this) { return true; } } return false; } AsyncPanZoomController* HitTestingTreeNode::GetApzc() const { return mApzc; } AsyncPanZoomController* HitTestingTreeNode::GetNearestContainingApzc() const { for (const HitTestingTreeNode* n = this; n; n = n->GetParent()) { if (n->GetApzc()) { return n->GetApzc(); } } return nullptr; } bool HitTestingTreeNode::IsPrimaryHolder() const { return mIsPrimaryApzcHolder; } LayersId HitTestingTreeNode::GetLayersId() const { return mLayersId; } void HitTestingTreeNode::SetHitTestData( const LayerIntRect& aVisibleRect, const LayerIntSize& aRemoteDocumentSize, const CSSTransformMatrix& aTransform, const EventRegionsOverride& aOverride, const Maybe& aAsyncZoomContainerId) { mVisibleRect = aVisibleRect; mRemoteDocumentSize = aRemoteDocumentSize; mTransform = aTransform; mOverride = aOverride; mAsyncZoomContainerId = aAsyncZoomContainerId; } EventRegionsOverride HitTestingTreeNode::GetEventRegionsOverride() const { return mOverride; } const CSSTransformMatrix& HitTestingTreeNode::GetTransform() const { return mTransform; } LayerToScreenMatrix4x4 HitTestingTreeNode::GetTransformToGecko( LayersId aRemoteLayersId) const { if (mParent) { LayerToParentLayerMatrix4x4 thisToParent = mTransform * AsyncTransformMatrix(); if (mApzc) { thisToParent = thisToParent * ViewAs( mApzc->GetTransformToLastDispatchedPaint( LayoutAndVisual, aRemoteLayersId)); } ParentLayerToScreenMatrix4x4 parentToRoot = ViewAs( mParent->GetTransformToGecko(aRemoteLayersId), PixelCastJustification::MovingDownToChildren); return thisToParent * parentToRoot; } return ViewAs( mTransform * AsyncTransformMatrix(), PixelCastJustification::ScreenIsParentLayerForRoot); } const LayerIntRect& HitTestingTreeNode::GetVisibleRect() const { return mVisibleRect; } ScreenRect HitTestingTreeNode::GetRemoteDocumentScreenRect( LayersId aRemoteDocumentLayersId) const { ScreenRect result = TransformBy( GetTransformToGecko(aRemoteDocumentLayersId), IntRectToRect(LayerIntRect(LayerIntPoint(), mRemoteDocumentSize))); for (const HitTestingTreeNode* node = this; node; node = node->GetParent()) { if (!node->GetApzc()) { continue; } ParentLayerRect compositionBounds = node->GetApzc()->GetCompositionBounds(); if (compositionBounds.IsEmpty()) { return ScreenRect(); } ScreenRect scrollPortOnScreenCoordinate = TransformBy( node->GetParent() ? node->GetParent()->GetTransformToGecko(node->GetLayersId()) : LayerToScreenMatrix4x4(), ViewAs(compositionBounds, PixelCastJustification::MovingDownToChildren)); if (scrollPortOnScreenCoordinate.IsEmpty()) { return ScreenRect(); } result = result.Intersect(scrollPortOnScreenCoordinate); if (result.IsEmpty()) { return ScreenRect(); } } return result; } Maybe HitTestingTreeNode::GetAsyncZoomContainerId() const { return mAsyncZoomContainerId; } void HitTestingTreeNode::Dump(const char* aPrefix) const { MOZ_LOG( sApzMgrLog, LogLevel::Debug, ("%sHitTestingTreeNode (%p) APZC (%p) g=(%s) %s%s%s t=(%s) " "%s%s\n", aPrefix, this, mApzc.get(), mApzc ? ToString(mApzc->GetGuid()).c_str() : nsPrintfCString("l=0x%" PRIx64, uint64_t(mLayersId)).get(), (mOverride & EventRegionsOverride::ForceDispatchToContent) ? "fdtc " : "", (mOverride & EventRegionsOverride::ForceEmptyHitRegion) ? "fehr " : "", (mFixedPosTarget != ScrollableLayerGuid::NULL_SCROLL_ID) ? nsPrintfCString("fixed=%" PRIu64 " ", mFixedPosTarget).get() : "", ToString(mTransform).c_str(), mScrollbarData.mDirection.isSome() ? " scrollbar" : "", IsScrollThumbNode() ? " scrollthumb" : "")); if (!mLastChild) { return; } // Dump the children in order from first child to last child std::stack children; for (HitTestingTreeNode* child = mLastChild.get(); child; child = child->mPrevSibling) { children.push(child); } nsPrintfCString childPrefix("%s ", aPrefix); while (!children.empty()) { children.top()->Dump(childPrefix.get()); children.pop(); } } void HitTestingTreeNode::SetApzcParent(AsyncPanZoomController* aParent) { // precondition: GetApzc() is non-null MOZ_ASSERT(GetApzc() != nullptr); if (IsPrimaryHolder()) { GetApzc()->SetParent(aParent); } else { MOZ_ASSERT(GetApzc()->GetParent() == aParent); } } void HitTestingTreeNode::Lock(const RecursiveMutexAutoLock& aProofOfTreeLock) { mLockCount++; } void HitTestingTreeNode::Unlock( const RecursiveMutexAutoLock& aProofOfTreeLock) { MOZ_ASSERT(mLockCount > 0); mLockCount--; } 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