tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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