WRHitTester.cpp (11031B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "WRHitTester.h" 8 #include "AsyncPanZoomController.h" 9 #include "APZCTreeManager.h" 10 #include "TreeTraversal.h" // for BreadthFirstSearch 11 #include "mozilla/gfx/CompositorHitTestInfo.h" 12 #include "mozilla/layers/CompositorBridgeParent.h" 13 #include "mozilla/webrender/WebRenderAPI.h" 14 #include "nsDebug.h" // for NS_ASSERTION 15 #include "nsIXULRuntime.h" // for FissionAutostart 16 #include "mozilla/gfx/Matrix.h" 17 18 #define APZCTM_LOG(...) \ 19 MOZ_LOG(APZCTreeManager::sLog, LogLevel::Debug, (__VA_ARGS__)) 20 21 namespace mozilla { 22 namespace layers { 23 24 using mozilla::gfx::CompositorHitTestFlags; 25 using mozilla::gfx::CompositorHitTestInvisibleToHit; 26 27 static bool CheckCloseToIdentity(const gfx::Matrix4x4& aMatrix) { 28 // We allow a factor of 1/2048 in the multiply part of the matrix, so that if 29 // we multiply by a point on a screen of size 2048 we would be off by at most 30 // 1 pixel approximately. 31 const float multiplyEps = 1 / 2048.f; 32 // We allow 1 pixel in the translate part of the matrix. 33 const float translateEps = 1.f; 34 35 if (!FuzzyEqualsAdditive(aMatrix._11, 1.f, multiplyEps) || 36 !FuzzyEqualsAdditive(aMatrix._12, 0.f, multiplyEps) || 37 !FuzzyEqualsAdditive(aMatrix._13, 0.f, multiplyEps) || 38 !FuzzyEqualsAdditive(aMatrix._14, 0.f, multiplyEps) || 39 !FuzzyEqualsAdditive(aMatrix._21, 0.f, multiplyEps) || 40 !FuzzyEqualsAdditive(aMatrix._22, 1.f, multiplyEps) || 41 !FuzzyEqualsAdditive(aMatrix._23, 0.f, multiplyEps) || 42 !FuzzyEqualsAdditive(aMatrix._24, 0.f, multiplyEps) || 43 !FuzzyEqualsAdditive(aMatrix._31, 0.f, multiplyEps) || 44 !FuzzyEqualsAdditive(aMatrix._32, 0.f, multiplyEps) || 45 !FuzzyEqualsAdditive(aMatrix._33, 1.f, multiplyEps) || 46 !FuzzyEqualsAdditive(aMatrix._34, 0.f, multiplyEps) || 47 !FuzzyEqualsAdditive(aMatrix._41, 0.f, translateEps) || 48 !FuzzyEqualsAdditive(aMatrix._42, 0.f, translateEps) || 49 !FuzzyEqualsAdditive(aMatrix._43, 0.f, translateEps) || 50 !FuzzyEqualsAdditive(aMatrix._44, 1.f, multiplyEps)) { 51 return false; 52 } 53 return true; 54 } 55 56 // Checks that within the constraints of floating point math we can invert it 57 // reasonably enough that multiplying by the computed inverse is close to the 58 // identity. 59 static bool CheckInvertibleWithFinitePrecision(const gfx::Matrix4x4& aMatrix) { 60 auto inverse = aMatrix.MaybeInverse(); 61 if (inverse.isNothing()) { 62 // Should we return false? 63 return true; 64 } 65 if (!CheckCloseToIdentity(aMatrix * *inverse)) { 66 return false; 67 } 68 if (!CheckCloseToIdentity(*inverse * aMatrix)) { 69 return false; 70 } 71 return true; 72 } 73 74 IAPZHitTester::HitTestResult WRHitTester::GetAPZCAtPoint( 75 const ScreenPoint& aHitTestPoint, 76 const RecursiveMutexAutoLock& aProofOfTreeLock) { 77 HitTestResult hit; 78 RefPtr<wr::WebRenderAPI> wr = mTreeManager->GetWebRenderAPI(); 79 if (!wr) { 80 // If WebRender isn't running, fall back to the root APZC. 81 // This is mostly for the benefit of GTests which do not 82 // run a WebRender instance, but gracefully falling back 83 // here allows those tests which are not specifically 84 // testing the hit-test algorithm to still work. 85 hit.mTargetApzc = FindRootApzcForLayersId(GetRootLayersId()); 86 hit.mHitResult = CompositorHitTestFlags::eVisibleToHitTest; 87 return hit; 88 } 89 90 APZCTM_LOG("Hit-testing point %s with WR\n", ToString(aHitTestPoint).c_str()); 91 std::vector<wr::WrHitResult> results = 92 wr->HitTest(wr::ToWorldPoint(aHitTestPoint)); 93 94 Maybe<wr::WrHitResult> chosenResult; 95 // It's possible for the WebRender hit test to produce a result with 96 // a LayersId whose corresponding layer tree has already been 97 // torn down (e.g. during window/tab shutdown, or navigation to 98 // a different domain). This is expected, so avoid debug assertions 99 // firing in this scenario. 100 DebugOnly<bool> layersIdExists = true; 101 for (const wr::WrHitResult& result : results) { 102 ScrollableLayerGuid guid{result.mLayersId, 0, result.mScrollId}; 103 APZCTM_LOG("Examining result with guid %s hit info 0x%x... ", 104 ToString(guid).c_str(), result.mHitInfo.serialize()); 105 if (result.mHitInfo == CompositorHitTestInvisibleToHit) { 106 APZCTM_LOG("skipping due to invisibility.\n"); 107 continue; 108 } 109 RefPtr<HitTestingTreeNode> node = 110 GetTargetNode(guid, &ScrollableLayerGuid::EqualsIgnoringPresShell); 111 if (!node) { 112 APZCTM_LOG("no corresponding node found, falling back to root.\n"); 113 114 #ifdef DEBUG 115 // We can enter here during normal codepaths for cases where the 116 // nsDisplayCompositorHitTestInfo item emitted a scrollId of 117 // NULL_SCROLL_ID to the webrender display list. The semantics of that 118 // is to fall back to the root APZC for the layers id, so that's what 119 // we do here. 120 // If we enter this codepath and scrollId is not NULL_SCROLL_ID, then 121 // that's more likely to be due to a race condition between rebuilding 122 // the APZ tree and updating the WR scene/hit-test information, resulting 123 // in WR giving us a hit result for a scene that is not active in APZ. 124 // Such a scenario would need debugging and fixing. 125 // In non-Fission mode, make this assertion non-fatal because there is 126 // a known issue related to inactive scroll frames that can cause this 127 // to fire (see bug 1634763), which is fixed in Fission mode and not 128 // worth fixing in non-Fission mode. 129 layersIdExists = 130 CompositorBridgeParent::HasIndirectShadowTree(result.mLayersId); 131 if (FissionAutostart()) { 132 MOZ_ASSERT(result.mScrollId == ScrollableLayerGuid::NULL_SCROLL_ID || 133 !layersIdExists); 134 } else { 135 NS_ASSERTION( 136 result.mScrollId == ScrollableLayerGuid::NULL_SCROLL_ID, 137 "Inconsistency between WebRender display list and APZ scroll data"); 138 } 139 #endif 140 node = FindRootNodeForLayersId(result.mLayersId); 141 if (!node) { 142 // Should never happen, but handle gracefully in release builds just 143 // in case. 144 MOZ_ASSERT(!layersIdExists, 145 "No root node found for hit-test result layers id"); 146 chosenResult = Some(result); 147 break; 148 } 149 } 150 MOZ_ASSERT(node->GetApzc()); // any node returned must have an APZC 151 EventRegionsOverride flags = node->GetEventRegionsOverride(); 152 if (flags & EventRegionsOverride::ForceEmptyHitRegion) { 153 // This result is inside a subtree that is invisible to hit-testing. 154 APZCTM_LOG("skipping due to FEHR subtree.\n"); 155 continue; 156 } 157 158 if (!CheckInvertibleWithFinitePrecision( 159 mTreeManager->GetScreenToApzcTransform(node->GetApzc()) 160 .ToUnknownMatrix())) { 161 APZCTM_LOG("skipping due to check inverse accuracy\n"); 162 continue; 163 } 164 165 APZCTM_LOG("selecting as chosen result.\n"); 166 chosenResult = Some(result); 167 hit.mTargetApzc = node->GetApzc(); 168 if (flags & EventRegionsOverride::ForceDispatchToContent) { 169 chosenResult->mHitInfo += CompositorHitTestFlags::eApzAwareListeners; 170 } 171 break; 172 } 173 if (!chosenResult) { 174 return hit; 175 } 176 177 MOZ_ASSERT( 178 hit.mTargetApzc || !layersIdExists, 179 "A hit-test result with a valid layers id should have a target APZC"); 180 hit.mLayersId = chosenResult->mLayersId; 181 ScrollableLayerGuid::ViewID scrollId = chosenResult->mScrollId; 182 gfx::CompositorHitTestInfo hitInfo = chosenResult->mHitInfo; 183 Maybe<uint64_t> animationId = chosenResult->mAnimationId; 184 SideBits sideBits = chosenResult->mSideBits; 185 186 APZCTM_LOG("Successfully matched APZC %p (hit result 0x%x)\n", 187 hit.mTargetApzc.get(), hitInfo.serialize()); 188 189 const bool isScrollbar = 190 hitInfo.contains(gfx::CompositorHitTestFlags::eScrollbar); 191 const bool isScrollbarThumb = 192 hitInfo.contains(gfx::CompositorHitTestFlags::eScrollbarThumb); 193 const ScrollDirection direction = 194 hitInfo.contains(gfx::CompositorHitTestFlags::eScrollbarVertical) 195 ? ScrollDirection::eVertical 196 : ScrollDirection::eHorizontal; 197 HitTestingTreeNode* scrollbarNode = nullptr; 198 if (isScrollbar || isScrollbarThumb) { 199 scrollbarNode = BreadthFirstSearch<ReverseIterator>( 200 GetRootNode(), [&](HitTestingTreeNode* aNode) { 201 return (aNode->GetLayersId() == hit.mLayersId) && 202 (aNode->IsScrollbarNode() == isScrollbar) && 203 (aNode->IsScrollThumbNode() == isScrollbarThumb) && 204 (aNode->GetScrollbarDirection() == direction) && 205 (aNode->GetScrollTargetId() == scrollId); 206 }); 207 } 208 209 hit.mHitResult = hitInfo; 210 211 if (scrollbarNode) { 212 RefPtr<HitTestingTreeNode> scrollbarRef = scrollbarNode; 213 InitializeHitTestingTreeNodeAutoLock(hit.mScrollbarNode, aProofOfTreeLock, 214 scrollbarRef); 215 } 216 217 hit.mFixedPosSides = sideBits; 218 if (animationId.isSome()) { 219 RefPtr<HitTestingTreeNode> positionedNode = nullptr; 220 221 positionedNode = BreadthFirstSearch<ReverseIterator>( 222 GetRootNode(), [&](HitTestingTreeNode* aNode) { 223 return (aNode->GetFixedPositionAnimationId() == animationId || 224 aNode->GetStickyPositionAnimationId() == animationId); 225 }); 226 227 if (positionedNode) { 228 MOZ_ASSERT(positionedNode->GetLayersId() == chosenResult->mLayersId, 229 "Found node layers id does not match the hit result"); 230 MOZ_ASSERT((positionedNode->GetFixedPositionAnimationId().isSome() || 231 positionedNode->GetStickyPositionAnimationId().isSome()), 232 "A a matching fixed/sticky position node should be found"); 233 InitializeHitTestingTreeNodeAutoLock(hit.mNode, aProofOfTreeLock, 234 positionedNode); 235 } 236 237 #if defined(MOZ_WIDGET_ANDROID) 238 if (hit.mNode && hit.mNode->GetFixedPositionAnimationId().isSome()) { 239 // If the hit element is a fixed position element, the side bits from 240 // the hit-result item tag are used. For now just ensure that these 241 // match what is found in the hit-testing tree node. 242 MOZ_ASSERT(sideBits == hit.mNode->GetFixedPosSides(), 243 "Fixed position side bits do not match"); 244 } else if (hit.mTargetApzc && hit.mTargetApzc->IsRootContent()) { 245 // If the hit element is not a fixed position element, then the hit test 246 // result item's side bits should not be populated. 247 MOZ_ASSERT(sideBits == SideBits::eNone, 248 "Hit test results have side bits only for pos:fixed"); 249 } 250 #endif 251 } 252 253 hit.mHitOverscrollGutter = 254 hit.mTargetApzc && hit.mTargetApzc->IsInOverscrollGutter(aHitTestPoint); 255 256 return hit; 257 } 258 259 } // namespace layers 260 } // namespace mozilla