Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "HitTestingTreeNode.h"
6
7
#include "AsyncPanZoomController.h" // for AsyncPanZoomController
8
#include "LayersLogging.h" // for Stringify
9
#include "mozilla/StaticPrefs_layout.h"
10
#include "mozilla/gfx/Point.h" // for Point4D
11
#include "mozilla/layers/APZUtils.h" // for CompleteAsyncTransform
12
#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform::operator Matrix4x4()
13
#include "mozilla/layers/AsyncDragMetrics.h" // for AsyncDragMetrics
14
#include "nsPrintfCString.h" // for nsPrintfCString
15
#include "UnitTransforms.h" // for ViewAs
16
17
static mozilla::LazyLogModule sApzMgrLog("apz.manager");
18
19
namespace mozilla {
20
namespace layers {
21
22
using gfx::CompositorHitTestFlags;
23
using gfx::CompositorHitTestInfo;
24
using gfx::CompositorHitTestInvisibleToHit;
25
using gfx::CompositorHitTestTouchActionMask;
26
27
HitTestingTreeNode::HitTestingTreeNode(AsyncPanZoomController* aApzc,
28
bool aIsPrimaryHolder,
29
LayersId aLayersId)
30
: mApzc(aApzc),
31
mIsPrimaryApzcHolder(aIsPrimaryHolder),
32
mLockCount(0),
33
mLayersId(aLayersId),
34
mFixedPosTarget(ScrollableLayerGuid::NULL_SCROLL_ID),
35
mStickyPosTarget(ScrollableLayerGuid::NULL_SCROLL_ID),
36
mIsBackfaceHidden(false),
37
mIsAsyncZoomContainer(false),
38
mOverride(EventRegionsOverride::NoOverride) {
39
if (mIsPrimaryApzcHolder) {
40
MOZ_ASSERT(mApzc);
41
}
42
MOZ_ASSERT(!mApzc || mApzc->GetLayersId() == mLayersId);
43
}
44
45
void HitTestingTreeNode::RecycleWith(
46
const RecursiveMutexAutoLock& aProofOfTreeLock,
47
AsyncPanZoomController* aApzc, LayersId aLayersId) {
48
MOZ_ASSERT(IsRecyclable(aProofOfTreeLock));
49
Destroy(); // clear out tree pointers
50
mApzc = aApzc;
51
mLayersId = aLayersId;
52
MOZ_ASSERT(!mApzc || mApzc->GetLayersId() == mLayersId);
53
// The caller is expected to call appropriate setters (SetHitTestData,
54
// SetScrollbarData, SetFixedPosData, SetStickyPosData, etc.) to repopulate
55
// all the data fields before using this node for "real work". Otherwise
56
// those data fields may contain stale information from the previous use
57
// of this node object.
58
}
59
60
HitTestingTreeNode::~HitTestingTreeNode() = default;
61
62
void HitTestingTreeNode::Destroy() {
63
// This runs on the updater thread, it's not worth passing around extra raw
64
// pointers just to assert it.
65
66
mPrevSibling = nullptr;
67
mLastChild = nullptr;
68
mParent = nullptr;
69
70
if (mApzc) {
71
if (mIsPrimaryApzcHolder) {
72
mApzc->Destroy();
73
}
74
mApzc = nullptr;
75
}
76
}
77
78
bool HitTestingTreeNode::IsRecyclable(
79
const RecursiveMutexAutoLock& aProofOfTreeLock) {
80
return !(IsPrimaryHolder() || (mLockCount > 0));
81
}
82
83
void HitTestingTreeNode::SetLastChild(HitTestingTreeNode* aChild) {
84
mLastChild = aChild;
85
if (aChild) {
86
aChild->mParent = this;
87
88
if (aChild->GetApzc()) {
89
AsyncPanZoomController* parent = GetNearestContainingApzc();
90
// We assume that HitTestingTreeNodes with an ancestor/descendant
91
// relationship cannot both point to the same APZC instance. This
92
// assertion only covers a subset of cases in which that might occur,
93
// but it's better than nothing.
94
MOZ_ASSERT(aChild->GetApzc() != parent);
95
aChild->SetApzcParent(parent);
96
}
97
}
98
}
99
100
void HitTestingTreeNode::SetScrollbarData(
101
const Maybe<uint64_t>& aScrollbarAnimationId,
102
const ScrollbarData& aScrollbarData) {
103
mScrollbarAnimationId = aScrollbarAnimationId;
104
mScrollbarData = aScrollbarData;
105
}
106
107
bool HitTestingTreeNode::MatchesScrollDragMetrics(
108
const AsyncDragMetrics& aDragMetrics) const {
109
return IsScrollThumbNode() &&
110
mScrollbarData.mDirection == aDragMetrics.mDirection &&
111
mScrollbarData.mTargetViewId == aDragMetrics.mViewId;
112
}
113
114
bool HitTestingTreeNode::IsScrollThumbNode() const {
115
return mScrollbarData.mScrollbarLayerType ==
116
layers::ScrollbarLayerType::Thumb;
117
}
118
119
bool HitTestingTreeNode::IsScrollbarNode() const {
120
return mScrollbarData.mScrollbarLayerType != layers::ScrollbarLayerType::None;
121
}
122
123
bool HitTestingTreeNode::IsScrollbarContainerNode() const {
124
return mScrollbarData.mScrollbarLayerType ==
125
layers::ScrollbarLayerType::Container;
126
}
127
128
ScrollDirection HitTestingTreeNode::GetScrollbarDirection() const {
129
MOZ_ASSERT(IsScrollbarNode());
130
MOZ_ASSERT(mScrollbarData.mDirection.isSome());
131
return *mScrollbarData.mDirection;
132
}
133
134
ScrollableLayerGuid::ViewID HitTestingTreeNode::GetScrollTargetId() const {
135
return mScrollbarData.mTargetViewId;
136
}
137
138
Maybe<uint64_t> HitTestingTreeNode::GetScrollbarAnimationId() const {
139
return mScrollbarAnimationId;
140
}
141
142
const ScrollbarData& HitTestingTreeNode::GetScrollbarData() const {
143
return mScrollbarData;
144
}
145
146
void HitTestingTreeNode::SetFixedPosData(
147
ScrollableLayerGuid::ViewID aFixedPosTarget, SideBits aFixedPosSides,
148
const Maybe<uint64_t>& aFixedPositionAnimationId) {
149
mFixedPosTarget = aFixedPosTarget;
150
mFixedPosSides = aFixedPosSides;
151
mFixedPositionAnimationId = aFixedPositionAnimationId;
152
}
153
154
ScrollableLayerGuid::ViewID HitTestingTreeNode::GetFixedPosTarget() const {
155
return mFixedPosTarget;
156
}
157
158
SideBits HitTestingTreeNode::GetFixedPosSides() const { return mFixedPosSides; }
159
160
Maybe<uint64_t> HitTestingTreeNode::GetFixedPositionAnimationId() const {
161
return mFixedPositionAnimationId;
162
}
163
164
void HitTestingTreeNode::SetPrevSibling(HitTestingTreeNode* aSibling) {
165
mPrevSibling = aSibling;
166
if (aSibling) {
167
aSibling->mParent = mParent;
168
169
if (aSibling->GetApzc()) {
170
AsyncPanZoomController* parent =
171
mParent ? mParent->GetNearestContainingApzc() : nullptr;
172
aSibling->SetApzcParent(parent);
173
}
174
}
175
}
176
177
void HitTestingTreeNode::SetStickyPosData(
178
ScrollableLayerGuid::ViewID aStickyPosTarget,
179
const LayerRectAbsolute& aScrollRangeOuter,
180
const LayerRectAbsolute& aScrollRangeInner) {
181
mStickyPosTarget = aStickyPosTarget;
182
mStickyScrollRangeOuter = aScrollRangeOuter;
183
mStickyScrollRangeInner = aScrollRangeInner;
184
}
185
186
ScrollableLayerGuid::ViewID HitTestingTreeNode::GetStickyPosTarget() const {
187
return mStickyPosTarget;
188
}
189
190
const LayerRectAbsolute& HitTestingTreeNode::GetStickyScrollRangeOuter() const {
191
return mStickyScrollRangeOuter;
192
}
193
194
const LayerRectAbsolute& HitTestingTreeNode::GetStickyScrollRangeInner() const {
195
return mStickyScrollRangeInner;
196
}
197
198
void HitTestingTreeNode::MakeRoot() {
199
mParent = nullptr;
200
201
if (GetApzc()) {
202
SetApzcParent(nullptr);
203
}
204
}
205
206
HitTestingTreeNode* HitTestingTreeNode::GetFirstChild() const {
207
HitTestingTreeNode* child = GetLastChild();
208
while (child && child->GetPrevSibling()) {
209
child = child->GetPrevSibling();
210
}
211
return child;
212
}
213
214
HitTestingTreeNode* HitTestingTreeNode::GetLastChild() const {
215
return mLastChild;
216
}
217
218
HitTestingTreeNode* HitTestingTreeNode::GetPrevSibling() const {
219
return mPrevSibling;
220
}
221
222
HitTestingTreeNode* HitTestingTreeNode::GetParent() const { return mParent; }
223
224
bool HitTestingTreeNode::IsAncestorOf(const HitTestingTreeNode* aOther) const {
225
for (const HitTestingTreeNode* cur = aOther; cur; cur = cur->GetParent()) {
226
if (cur == this) {
227
return true;
228
}
229
}
230
return false;
231
}
232
233
AsyncPanZoomController* HitTestingTreeNode::GetApzc() const { return mApzc; }
234
235
AsyncPanZoomController* HitTestingTreeNode::GetNearestContainingApzc() const {
236
for (const HitTestingTreeNode* n = this; n; n = n->GetParent()) {
237
if (n->GetApzc()) {
238
return n->GetApzc();
239
}
240
}
241
return nullptr;
242
}
243
244
bool HitTestingTreeNode::IsPrimaryHolder() const {
245
return mIsPrimaryApzcHolder;
246
}
247
248
LayersId HitTestingTreeNode::GetLayersId() const { return mLayersId; }
249
250
void HitTestingTreeNode::SetHitTestData(
251
const EventRegions& aRegions, const LayerIntRegion& aVisibleRegion,
252
const LayerIntSize& aRemoteDocumentSize,
253
const CSSTransformMatrix& aTransform,
254
const Maybe<ParentLayerIntRegion>& aClipRegion,
255
const EventRegionsOverride& aOverride, bool aIsBackfaceHidden,
256
bool aIsAsyncZoomContainer) {
257
mEventRegions = aRegions;
258
mVisibleRegion = aVisibleRegion;
259
mRemoteDocumentSize = aRemoteDocumentSize;
260
mTransform = aTransform;
261
mClipRegion = aClipRegion;
262
mOverride = aOverride;
263
mIsBackfaceHidden = aIsBackfaceHidden;
264
mIsAsyncZoomContainer = aIsAsyncZoomContainer;
265
}
266
267
bool HitTestingTreeNode::IsOutsideClip(const ParentLayerPoint& aPoint) const {
268
// test against clip rect in ParentLayer coordinate space
269
return (mClipRegion.isSome() && !mClipRegion->Contains(aPoint.x, aPoint.y));
270
}
271
272
Maybe<LayerPoint> HitTestingTreeNode::Untransform(
273
const ParentLayerPoint& aPoint,
274
const LayerToParentLayerMatrix4x4& aTransform) const {
275
Maybe<ParentLayerToLayerMatrix4x4> inverse = aTransform.MaybeInverse();
276
if (inverse) {
277
return UntransformBy(inverse.ref(), aPoint);
278
}
279
return Nothing();
280
}
281
282
CompositorHitTestInfo HitTestingTreeNode::HitTest(
283
const LayerPoint& aPoint) const {
284
CompositorHitTestInfo result = CompositorHitTestInvisibleToHit;
285
286
if (mOverride & EventRegionsOverride::ForceEmptyHitRegion) {
287
return result;
288
}
289
290
auto point = LayerIntPoint::Round(aPoint);
291
292
// If the layer's backface is showing and it's hidden, don't hit it.
293
// This matches the behavior of main-thread hit testing in
294
// nsDisplayTransform::HitTest().
295
if (mIsBackfaceHidden) {
296
return result;
297
}
298
299
// test against event regions in Layer coordinate space
300
if (!mEventRegions.mHitRegion.Contains(point.x, point.y)) {
301
return result;
302
}
303
304
result = CompositorHitTestFlags::eVisibleToHitTest;
305
306
if (mOverride & EventRegionsOverride::ForceDispatchToContent) {
307
result += CompositorHitTestFlags::eApzAwareListeners;
308
}
309
if (mEventRegions.mDispatchToContentHitRegion.Contains(point.x, point.y)) {
310
// Technically this might be some combination of eInactiveScrollframe,
311
// eApzAwareListeners, and eIrregularArea, because the round-trip through
312
// mEventRegions is lossy. We just convert it back to eIrregularArea
313
// because that's the most conservative option (i.e. eIrregularArea makes
314
// APZ rely on the main thread for everything).
315
result += CompositorHitTestFlags::eIrregularArea;
316
if (mEventRegions.mDTCRequiresTargetConfirmation) {
317
result += CompositorHitTestFlags::eRequiresTargetConfirmation;
318
}
319
} else if (StaticPrefs::layout_css_touch_action_enabled()) {
320
if (mEventRegions.mNoActionRegion.Contains(point.x, point.y)) {
321
// set all the touch-action flags as disabled
322
result += CompositorHitTestTouchActionMask;
323
} else {
324
bool panX = mEventRegions.mHorizontalPanRegion.Contains(point.x, point.y);
325
bool panY = mEventRegions.mVerticalPanRegion.Contains(point.x, point.y);
326
if (panX && panY) {
327
// touch-action: pan-x pan-y
328
result += CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled;
329
result += CompositorHitTestFlags::eTouchActionPinchZoomDisabled;
330
} else if (panX) {
331
// touch-action: pan-x
332
result += CompositorHitTestFlags::eTouchActionPanYDisabled;
333
result += CompositorHitTestFlags::eTouchActionPinchZoomDisabled;
334
result += CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled;
335
} else if (panY) {
336
// touch-action: pan-y
337
result += CompositorHitTestFlags::eTouchActionPanXDisabled;
338
result += CompositorHitTestFlags::eTouchActionPinchZoomDisabled;
339
result += CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled;
340
} // else we're in the touch-action: auto or touch-action: manipulation
341
// cases and we'll allow all actions. Technically we shouldn't allow
342
// double-tap zooming in the manipulation case but apparently this has
343
// been broken since the dawn of time.
344
}
345
}
346
347
// The scrollbar flags are set at the call site in GetAPZCAtPoint, because
348
// those require walking up the tree to see if we are contained inside a
349
// scrollbar or scrollthumb, and we do that there anyway to get the scrollbar
350
// node.
351
352
return result;
353
}
354
355
EventRegionsOverride HitTestingTreeNode::GetEventRegionsOverride() const {
356
return mOverride;
357
}
358
359
const CSSTransformMatrix& HitTestingTreeNode::GetTransform() const {
360
return mTransform;
361
}
362
363
LayerToScreenMatrix4x4 HitTestingTreeNode::GetTransformToGecko() const {
364
if (mParent) {
365
LayerToParentLayerMatrix4x4 thisToParent =
366
mTransform * AsyncTransformMatrix();
367
if (mApzc) {
368
thisToParent =
369
thisToParent * ViewAs<ParentLayerToParentLayerMatrix4x4>(
370
mApzc->GetTransformToLastDispatchedPaint());
371
}
372
ParentLayerToScreenMatrix4x4 parentToRoot =
373
ViewAs<ParentLayerToScreenMatrix4x4>(
374
mParent->GetTransformToGecko(),
375
PixelCastJustification::MovingDownToChildren);
376
return thisToParent * parentToRoot;
377
}
378
379
return ViewAs<LayerToScreenMatrix4x4>(
380
mTransform * AsyncTransformMatrix(),
381
PixelCastJustification::ScreenIsParentLayerForRoot);
382
}
383
384
const LayerIntRegion& HitTestingTreeNode::GetVisibleRegion() const {
385
return mVisibleRegion;
386
}
387
388
ScreenRect HitTestingTreeNode::GetRemoteDocumentScreenRect() const {
389
ScreenRect result = TransformBy(
390
GetTransformToGecko(),
391
IntRectToRect(LayerIntRect(LayerIntPoint(), mRemoteDocumentSize)));
392
393
for (const HitTestingTreeNode* node = this; node; node = node->GetParent()) {
394
if (!node->GetApzc()) {
395
continue;
396
}
397
398
ParentLayerRect compositionBounds = node->GetApzc()->GetCompositionBounds();
399
if (compositionBounds.IsEmpty()) {
400
return ScreenRect();
401
}
402
403
ScreenRect scrollPortOnScreenCoordinate = TransformBy(
404
node->GetParent() ? node->GetParent()->GetTransformToGecko()
405
: LayerToScreenMatrix4x4(),
406
ViewAs<LayerPixel>(compositionBounds,
407
PixelCastJustification::MovingDownToChildren));
408
if (scrollPortOnScreenCoordinate.IsEmpty()) {
409
return ScreenRect();
410
}
411
412
result = result.Intersect(scrollPortOnScreenCoordinate);
413
if (result.IsEmpty()) {
414
return ScreenRect();
415
}
416
}
417
return result;
418
}
419
420
bool HitTestingTreeNode::IsAsyncZoomContainer() const {
421
return mIsAsyncZoomContainer;
422
}
423
424
void HitTestingTreeNode::Dump(const char* aPrefix) const {
425
MOZ_LOG(
426
sApzMgrLog, LogLevel::Debug,
427
("%sHitTestingTreeNode (%p) APZC (%p) g=(%s) %s%s%sr=(%s) t=(%s) "
428
"c=(%s)%s%s\n",
429
aPrefix, this, mApzc.get(),
430
mApzc ? Stringify(mApzc->GetGuid()).c_str()
431
: nsPrintfCString("l=0x%" PRIx64, uint64_t(mLayersId)).get(),
432
(mOverride & EventRegionsOverride::ForceDispatchToContent) ? "fdtc "
433
: "",
434
(mOverride & EventRegionsOverride::ForceEmptyHitRegion) ? "fehr " : "",
435
(mFixedPosTarget != ScrollableLayerGuid::NULL_SCROLL_ID)
436
? nsPrintfCString("fixed=%" PRIu64 " ", mFixedPosTarget).get()
437
: "",
438
Stringify(mEventRegions).c_str(), Stringify(mTransform).c_str(),
439
mClipRegion ? Stringify(mClipRegion.ref()).c_str() : "none",
440
mScrollbarData.mDirection.isSome() ? " scrollbar" : "",
441
IsScrollThumbNode() ? " scrollthumb" : ""));
442
443
if (!mLastChild) {
444
return;
445
}
446
447
// Dump the children in order from first child to last child
448
std::stack<HitTestingTreeNode*> children;
449
for (HitTestingTreeNode* child = mLastChild.get(); child;
450
child = child->mPrevSibling) {
451
children.push(child);
452
}
453
nsPrintfCString childPrefix("%s ", aPrefix);
454
while (!children.empty()) {
455
children.top()->Dump(childPrefix.get());
456
children.pop();
457
}
458
}
459
460
void HitTestingTreeNode::SetApzcParent(AsyncPanZoomController* aParent) {
461
// precondition: GetApzc() is non-null
462
MOZ_ASSERT(GetApzc() != nullptr);
463
if (IsPrimaryHolder()) {
464
GetApzc()->SetParent(aParent);
465
} else {
466
MOZ_ASSERT(GetApzc()->GetParent() == aParent);
467
}
468
}
469
470
void HitTestingTreeNode::Lock(const RecursiveMutexAutoLock& aProofOfTreeLock) {
471
mLockCount++;
472
}
473
474
void HitTestingTreeNode::Unlock(
475
const RecursiveMutexAutoLock& aProofOfTreeLock) {
476
MOZ_ASSERT(mLockCount > 0);
477
mLockCount--;
478
}
479
480
HitTestingTreeNodeAutoLock::HitTestingTreeNodeAutoLock()
481
: mTreeMutex(nullptr) {}
482
483
HitTestingTreeNodeAutoLock::~HitTestingTreeNodeAutoLock() { Clear(); }
484
485
void HitTestingTreeNodeAutoLock::Initialize(
486
const RecursiveMutexAutoLock& aProofOfTreeLock,
487
already_AddRefed<HitTestingTreeNode> aNode, RecursiveMutex& aTreeMutex) {
488
MOZ_ASSERT(!mNode);
489
490
mNode = aNode;
491
mTreeMutex = &aTreeMutex;
492
493
mNode->Lock(aProofOfTreeLock);
494
}
495
496
void HitTestingTreeNodeAutoLock::Clear() {
497
if (!mNode) {
498
return;
499
}
500
MOZ_ASSERT(mTreeMutex);
501
502
{ // scope lock
503
RecursiveMutexAutoLock lock(*mTreeMutex);
504
mNode->Unlock(lock);
505
}
506
mNode = nullptr;
507
mTreeMutex = nullptr;
508
}
509
510
} // namespace layers
511
} // namespace mozilla