summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/src/WRHitTester.cpp
blob: 873400976f7959989cb3bb180814df95cf6e2952 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/* -*- 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 "WRHitTester.h"
#include "AsyncPanZoomController.h"
#include "APZCTreeManager.h"
#include "TreeTraversal.h"  // for BreadthFirstSearch
#include "mozilla/gfx/CompositorHitTestInfo.h"
#include "mozilla/webrender/WebRenderAPI.h"
#include "nsDebug.h"        // for NS_ASSERTION
#include "nsIXULRuntime.h"  // for FissionAutostart
#include "mozilla/gfx/Matrix.h"

#define APZCTM_LOG(...) \
  MOZ_LOG(APZCTreeManager::sLog, LogLevel::Debug, (__VA_ARGS__))

namespace mozilla {
namespace layers {

using mozilla::gfx::CompositorHitTestFlags;
using mozilla::gfx::CompositorHitTestInvisibleToHit;

static bool CheckCloseToIdentity(const gfx::Matrix4x4& aMatrix) {
  // We allow a factor of 1/2048 in the multiply part of the matrix, so that if
  // we multiply by a point on a screen of size 2048 we would be off by at most
  // 1 pixel approximately.
  const float multiplyEps = 1 / 2048.f;
  // We allow 1 pixel in the translate part of the matrix.
  const float translateEps = 1.f;

  if (!FuzzyEqualsAdditive(aMatrix._11, 1.f, multiplyEps) ||
      !FuzzyEqualsAdditive(aMatrix._12, 0.f, multiplyEps) ||
      !FuzzyEqualsAdditive(aMatrix._13, 0.f, multiplyEps) ||
      !FuzzyEqualsAdditive(aMatrix._14, 0.f, multiplyEps) ||
      !FuzzyEqualsAdditive(aMatrix._21, 0.f, multiplyEps) ||
      !FuzzyEqualsAdditive(aMatrix._22, 1.f, multiplyEps) ||
      !FuzzyEqualsAdditive(aMatrix._23, 0.f, multiplyEps) ||
      !FuzzyEqualsAdditive(aMatrix._24, 0.f, multiplyEps) ||
      !FuzzyEqualsAdditive(aMatrix._31, 0.f, multiplyEps) ||
      !FuzzyEqualsAdditive(aMatrix._32, 0.f, multiplyEps) ||
      !FuzzyEqualsAdditive(aMatrix._33, 1.f, multiplyEps) ||
      !FuzzyEqualsAdditive(aMatrix._34, 0.f, multiplyEps) ||
      !FuzzyEqualsAdditive(aMatrix._41, 0.f, translateEps) ||
      !FuzzyEqualsAdditive(aMatrix._42, 0.f, translateEps) ||
      !FuzzyEqualsAdditive(aMatrix._43, 0.f, translateEps) ||
      !FuzzyEqualsAdditive(aMatrix._44, 1.f, multiplyEps)) {
    return false;
  }
  return true;
}

// Checks that within the constraints of floating point math we can invert it
// reasonably enough that multiplying by the computed inverse is close to the
// identity.
static bool CheckInvertibleWithFinitePrecision(const gfx::Matrix4x4& aMatrix) {
  auto inverse = aMatrix.MaybeInverse();
  if (inverse.isNothing()) {
    // Should we return false?
    return true;
  }
  if (!CheckCloseToIdentity(aMatrix * *inverse)) {
    return false;
  }
  if (!CheckCloseToIdentity(*inverse * aMatrix)) {
    return false;
  }
  return true;
}

IAPZHitTester::HitTestResult WRHitTester::GetAPZCAtPoint(
    const ScreenPoint& aHitTestPoint,
    const RecursiveMutexAutoLock& aProofOfTreeLock) {
  HitTestResult hit;
  RefPtr<wr::WebRenderAPI> wr = mTreeManager->GetWebRenderAPI();
  if (!wr) {
    // If WebRender isn't running, fall back to the root APZC.
    // This is mostly for the benefit of GTests which do not
    // run a WebRender instance, but gracefully falling back
    // here allows those tests which are not specifically
    // testing the hit-test algorithm to still work.
    hit.mTargetApzc = FindRootApzcForLayersId(GetRootLayersId());
    hit.mHitResult = CompositorHitTestFlags::eVisibleToHitTest;
    return hit;
  }

  APZCTM_LOG("Hit-testing point %s with WR\n", ToString(aHitTestPoint).c_str());
  std::vector<wr::WrHitResult> results =
      wr->HitTest(wr::ToWorldPoint(aHitTestPoint));

  Maybe<wr::WrHitResult> chosenResult;
  for (const wr::WrHitResult& result : results) {
    ScrollableLayerGuid guid{result.mLayersId, 0, result.mScrollId};
    APZCTM_LOG("Examining result with guid %s hit info 0x%x... ",
               ToString(guid).c_str(), result.mHitInfo.serialize());
    if (result.mHitInfo == CompositorHitTestInvisibleToHit) {
      APZCTM_LOG("skipping due to invisibility.\n");
      continue;
    }
    RefPtr<HitTestingTreeNode> node =
        GetTargetNode(guid, &ScrollableLayerGuid::EqualsIgnoringPresShell);
    if (!node) {
      APZCTM_LOG("no corresponding node found, falling back to root.\n");

#ifdef DEBUG
      // We can enter here during normal codepaths for cases where the
      // nsDisplayCompositorHitTestInfo item emitted a scrollId of
      // NULL_SCROLL_ID to the webrender display list. The semantics of that
      // is to fall back to the root APZC for the layers id, so that's what
      // we do here.
      // If we enter this codepath and scrollId is not NULL_SCROLL_ID, then
      // that's more likely to be due to a race condition between rebuilding
      // the APZ tree and updating the WR scene/hit-test information, resulting
      // in WR giving us a hit result for a scene that is not active in APZ.
      // Such a scenario would need debugging and fixing.
      // In non-Fission mode, make this assertion non-fatal because there is
      // a known issue related to inactive scroll frames that can cause this
      // to fire (see bug 1634763), which is fixed in Fission mode and not
      // worth fixing in non-Fission mode.
      if (FissionAutostart()) {
        MOZ_ASSERT(result.mScrollId == ScrollableLayerGuid::NULL_SCROLL_ID);
      } else {
        NS_ASSERTION(
            result.mScrollId == ScrollableLayerGuid::NULL_SCROLL_ID,
            "Inconsistency between WebRender display list and APZ scroll data");
      }
#endif
      node = FindRootNodeForLayersId(result.mLayersId);
      if (!node) {
        // Should never happen, but handle gracefully in release builds just
        // in case.
        MOZ_ASSERT(false);
        chosenResult = Some(result);
        break;
      }
    }
    MOZ_ASSERT(node->GetApzc());  // any node returned must have an APZC
    EventRegionsOverride flags = node->GetEventRegionsOverride();
    if (flags & EventRegionsOverride::ForceEmptyHitRegion) {
      // This result is inside a subtree that is invisible to hit-testing.
      APZCTM_LOG("skipping due to FEHR subtree.\n");
      continue;
    }

    if (!CheckInvertibleWithFinitePrecision(
            mTreeManager->GetScreenToApzcTransform(node->GetApzc())
                .ToUnknownMatrix())) {
      APZCTM_LOG("skipping due to check inverse accuracy\n");
      continue;
    }

    APZCTM_LOG("selecting as chosen result.\n");
    chosenResult = Some(result);
    hit.mTargetApzc = node->GetApzc();
    if (flags & EventRegionsOverride::ForceDispatchToContent) {
      chosenResult->mHitInfo += CompositorHitTestFlags::eApzAwareListeners;
    }
    break;
  }
  if (!chosenResult) {
    return hit;
  }

  MOZ_ASSERT(hit.mTargetApzc);
  hit.mLayersId = chosenResult->mLayersId;
  ScrollableLayerGuid::ViewID scrollId = chosenResult->mScrollId;
  gfx::CompositorHitTestInfo hitInfo = chosenResult->mHitInfo;
  Maybe<uint64_t> animationId = chosenResult->mAnimationId;
  SideBits sideBits = chosenResult->mSideBits;

  APZCTM_LOG("Successfully matched APZC %p (hit result 0x%x)\n",
             hit.mTargetApzc.get(), hitInfo.serialize());

  const bool isScrollbar =
      hitInfo.contains(gfx::CompositorHitTestFlags::eScrollbar);
  const bool isScrollbarThumb =
      hitInfo.contains(gfx::CompositorHitTestFlags::eScrollbarThumb);
  const ScrollDirection direction =
      hitInfo.contains(gfx::CompositorHitTestFlags::eScrollbarVertical)
          ? ScrollDirection::eVertical
          : ScrollDirection::eHorizontal;
  HitTestingTreeNode* scrollbarNode = nullptr;
  if (isScrollbar || isScrollbarThumb) {
    scrollbarNode = BreadthFirstSearch<ReverseIterator>(
        GetRootNode(), [&](HitTestingTreeNode* aNode) {
          return (aNode->GetLayersId() == hit.mLayersId) &&
                 (aNode->IsScrollbarNode() == isScrollbar) &&
                 (aNode->IsScrollThumbNode() == isScrollbarThumb) &&
                 (aNode->GetScrollbarDirection() == direction) &&
                 (aNode->GetScrollTargetId() == scrollId);
        });
  }

  hit.mHitResult = hitInfo;

  if (scrollbarNode) {
    RefPtr<HitTestingTreeNode> scrollbarRef = scrollbarNode;
    InitializeHitTestingTreeNodeAutoLock(hit.mScrollbarNode, aProofOfTreeLock,
                                         scrollbarRef);
  }

  hit.mFixedPosSides = sideBits;
  if (animationId.isSome()) {
    RefPtr<HitTestingTreeNode> positionedNode = nullptr;

    positionedNode = BreadthFirstSearch<ReverseIterator>(
        GetRootNode(), [&](HitTestingTreeNode* aNode) {
          return (aNode->GetFixedPositionAnimationId() == animationId ||
                  aNode->GetStickyPositionAnimationId() == animationId);
        });

    if (positionedNode) {
      MOZ_ASSERT(positionedNode->GetLayersId() == chosenResult->mLayersId,
                 "Found node layers id does not match the hit result");
      MOZ_ASSERT((positionedNode->GetFixedPositionAnimationId().isSome() ||
                  positionedNode->GetStickyPositionAnimationId().isSome()),
                 "A a matching fixed/sticky position node should be found");
      InitializeHitTestingTreeNodeAutoLock(hit.mNode, aProofOfTreeLock,
                                           positionedNode);
    }

#if defined(MOZ_WIDGET_ANDROID)
    if (hit.mNode && hit.mNode->GetFixedPositionAnimationId().isSome()) {
      // If the hit element is a fixed position element, the side bits from
      // the hit-result item tag are used. For now just ensure that these
      // match what is found in the hit-testing tree node.
      MOZ_ASSERT(sideBits == hit.mNode->GetFixedPosSides(),
                 "Fixed position side bits do not match");
    } else if (hit.mTargetApzc && hit.mTargetApzc->IsRootContent()) {
      // If the hit element is not a fixed position element, then the hit test
      // result item's side bits should not be populated.
      MOZ_ASSERT(sideBits == SideBits::eNone,
                 "Hit test results have side bits only for pos:fixed");
    }
#endif
  }

  hit.mHitOverscrollGutter =
      hit.mTargetApzc && hit.mTargetApzc->IsInOverscrollGutter(aHitTestPoint);

  return hit;
}

}  // namespace layers
}  // namespace mozilla