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 <stack>
8 #include <unordered_set>
9 #include "APZCTreeManager.h"
10 #include "AsyncPanZoomController.h"
11 #include "Compositor.h" // for Compositor
12 #include "DragTracker.h" // for DragTracker
13 #include "GenericFlingAnimation.h" // for FLING_LOG
14 #include "HitTestingTreeNode.h" // for HitTestingTreeNode
15 #include "InputBlockState.h" // for InputBlockState
16 #include "InputData.h" // for InputData, etc
17 #include "Layers.h" // for Layer, etc
18 #include "mozilla/RecursiveMutex.h"
19 #include "mozilla/dom/MouseEventBinding.h" // for MouseEvent constants
20 #include "mozilla/dom/BrowserParent.h" // for AreRecordReplayTabsActive
21 #include "mozilla/dom/Touch.h" // for Touch
22 #include "mozilla/gfx/CompositorHitTestInfo.h"
23 #include "mozilla/gfx/LoggingConstants.h"
24 #include "mozilla/gfx/gfxVars.h" // for gfxVars
25 #include "mozilla/gfx/GPUParent.h" // for GPUParent
26 #include "mozilla/gfx/Logging.h" // for gfx::TreeLog
27 #include "mozilla/gfx/Point.h" // for Point
28 #include "mozilla/layers/APZSampler.h" // for APZSampler
29 #include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread, etc
30 #include "mozilla/layers/APZUpdater.h" // for APZUpdater
31 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
32 #include "mozilla/layers/AsyncDragMetrics.h" // for AsyncDragMetrics
33 #include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc
34 #include "mozilla/layers/LayerMetricsWrapper.h"
35 #include "mozilla/layers/MatrixMessage.h"
36 #include "mozilla/layers/WebRenderScrollDataWrapper.h"
37 #include "mozilla/MouseEvents.h"
38 #include "mozilla/mozalloc.h" // for operator new
39 #include "mozilla/Preferences.h" // for Preferences
40 #include "mozilla/StaticPrefs_accessibility.h"
41 #include "mozilla/StaticPrefs_apz.h"
42 #include "mozilla/StaticPrefs_layout.h"
43 #include "mozilla/TouchEvents.h"
44 #include "mozilla/EventStateManager.h" // for WheelPrefs
45 #include "mozilla/webrender/WebRenderAPI.h"
46 #include "nsDebug.h" // for NS_WARNING
47 #include "nsPoint.h" // for nsIntPoint
48 #include "nsThreadUtils.h" // for NS_IsMainThread
49 #include "OverscrollHandoffState.h" // for OverscrollHandoffState
50 #include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch, etc
51 #include "LayersLogging.h" // for Stringify
52 #include "Units.h" // for ParentlayerPixel
53 #include "GestureEventListener.h" // for GestureEventListener::setLongTapEnabled
54 #include "UnitTransforms.h" // for ViewAs
55
56 static mozilla::LazyLogModule sApzMgrLog("apz.manager");
57 #define APZCTM_LOG(...) MOZ_LOG(sApzMgrLog, LogLevel::Debug, (__VA_ARGS__))
58
59 static mozilla::LazyLogModule sApzKeyLog("apz.key");
60 #define APZ_KEY_LOG(...) MOZ_LOG(sApzKeyLog, LogLevel::Debug, (__VA_ARGS__))
61
62 namespace mozilla {
63 namespace layers {
64
65 using mozilla::gfx::CompositorHitTestDispatchToContent;
66 using mozilla::gfx::CompositorHitTestFlags;
67 using mozilla::gfx::CompositorHitTestInfo;
68 using mozilla::gfx::CompositorHitTestInvisibleToHit;
69 using mozilla::gfx::LOG_DEFAULT;
70
71 typedef mozilla::gfx::Point Point;
72 typedef mozilla::gfx::Point4D Point4D;
73 typedef mozilla::gfx::Matrix4x4 Matrix4x4;
74
75 typedef CompositorBridgeParent::LayerTreeState LayerTreeState;
76
77 struct APZCTreeManager::TreeBuildingState {
TreeBuildingStatemozilla::layers::APZCTreeManager::TreeBuildingState78 TreeBuildingState(LayersId aRootLayersId, bool aIsFirstPaint,
79 LayersId aOriginatingLayersId, APZTestData* aTestData,
80 uint32_t aPaintSequence)
81 : mIsFirstPaint(aIsFirstPaint),
82 mOriginatingLayersId(aOriginatingLayersId),
83 mPaintLogger(aTestData, aPaintSequence) {
84 CompositorBridgeParent::CallWithIndirectShadowTree(
85 aRootLayersId, [this](LayerTreeState& aState) -> void {
86 mCompositorController = aState.GetCompositorController();
87 mInProcessSharingController = aState.InProcessSharingController();
88 });
89 }
90
91 typedef std::unordered_map<AsyncPanZoomController*, gfx::Matrix4x4>
92 DeferredTransformMap;
93
94 // State that doesn't change as we recurse in the tree building
95 RefPtr<CompositorController> mCompositorController;
96 RefPtr<MetricsSharingController> mInProcessSharingController;
97 const bool mIsFirstPaint;
98 const LayersId mOriginatingLayersId;
99 const APZPaintLogHelper mPaintLogger;
100
101 // State that is updated as we perform the tree build
102
103 // A list of nodes that need to be destroyed at the end of the tree building.
104 // This is initialized with all nodes in the old tree, and nodes are removed
105 // from it as we reuse them in the new tree.
106 nsTArray<RefPtr<HitTestingTreeNode>> mNodesToDestroy;
107
108 // This map is populated as we place APZCs into the new tree. Its purpose is
109 // to facilitate re-using the same APZC for different layers that scroll
110 // together (and thus have the same ScrollableLayerGuid). The presShellId
111 // doesn't matter for this purpose, and we move the map to the APZCTreeManager
112 // after we're done building, so it's useful to have the presshell-ignoring
113 // map for that.
114 std::unordered_map<ScrollableLayerGuid, ApzcMapData,
115 ScrollableLayerGuid::HashIgnoringPresShellFn,
116 ScrollableLayerGuid::EqualIgnoringPresShellFn>
117 mApzcMap;
118
119 // This is populated with all the HitTestingTreeNodes that are scroll thumbs
120 // and have a scrollthumb animation id (which indicates that they need to be
121 // sampled for WebRender on the sampler thread).
122 std::vector<HitTestingTreeNode*> mScrollThumbs;
123 // This is populated with all the scroll target nodes. We use in conjunction
124 // with mScrollThumbs to build APZCTreeManager::mScrollThumbInfo.
125 std::unordered_map<ScrollableLayerGuid, HitTestingTreeNode*,
126 ScrollableLayerGuid::HashIgnoringPresShellFn,
127 ScrollableLayerGuid::EqualIgnoringPresShellFn>
128 mScrollTargets;
129
130 // As the tree is traversed, the top element of this stack tracks whether
131 // the parent scroll node has a perspective transform.
132 std::stack<bool> mParentHasPerspective;
133
134 // During the tree building process, the perspective transform component
135 // of the ancestor transforms of some APZCs can be "deferred" to their
136 // children, meaning they are added to the children's ancestor transforms
137 // instead. Those deferred transforms are tracked here.
138 DeferredTransformMap mPerspectiveTransformsDeferredToChildren;
139
140 // As we recurse down through the tree, this picks up the zoom animation id
141 // from a node in the layer tree, and propagates it downwards to the nearest
142 // APZC instance that is for an RCD node. Generally it will be set on the
143 // root node of the layers (sub-)tree, which may not be same as the RCD node
144 // for the subtree, and so we need this mechanism to ensure it gets propagated
145 // to the RCD's APZC instance. Once it is set on the APZC instance, the value
146 // is cleared back to Nothing(). Note that this is only used in the WebRender
147 // codepath.
148 Maybe<uint64_t> mZoomAnimationId;
149
150 // See corresponding members of APZCTreeManager. These are the same thing, but
151 // on the tree-walking state. They are populated while walking the tree in
152 // a layers update, and then moved into APZCTreeManager.
153 std::vector<FixedPositionInfo> mFixedPositionInfo;
154 std::vector<RootScrollbarInfo> mRootScrollbarInfo;
155 std::vector<StickyPositionInfo> mStickyPositionInfo;
156
157 // As we recurse down through reflayers in the tree, this picks up the
158 // cumulative EventRegionsOverride flags from the reflayers, and is used to
159 // apply them to descendant layers.
160 std::stack<EventRegionsOverride> mOverrideFlags;
161 };
162
163 class APZCTreeManager::CheckerboardFlushObserver : public nsIObserver {
164 public:
165 NS_DECL_ISUPPORTS
166 NS_DECL_NSIOBSERVER
167
CheckerboardFlushObserver(APZCTreeManager * aTreeManager)168 explicit CheckerboardFlushObserver(APZCTreeManager* aTreeManager)
169 : mTreeManager(aTreeManager) {
170 MOZ_ASSERT(NS_IsMainThread());
171 nsCOMPtr<nsIObserverService> obsSvc =
172 mozilla::services::GetObserverService();
173 MOZ_ASSERT(obsSvc);
174 if (obsSvc) {
175 obsSvc->AddObserver(this, "APZ:FlushActiveCheckerboard", false);
176 }
177 }
178
Unregister()179 void Unregister() {
180 MOZ_ASSERT(NS_IsMainThread());
181 nsCOMPtr<nsIObserverService> obsSvc =
182 mozilla::services::GetObserverService();
183 if (obsSvc) {
184 obsSvc->RemoveObserver(this, "APZ:FlushActiveCheckerboard");
185 }
186 mTreeManager = nullptr;
187 }
188
189 protected:
190 virtual ~CheckerboardFlushObserver() = default;
191
192 private:
193 RefPtr<APZCTreeManager> mTreeManager;
194 };
195
NS_IMPL_ISUPPORTS(APZCTreeManager::CheckerboardFlushObserver,nsIObserver)196 NS_IMPL_ISUPPORTS(APZCTreeManager::CheckerboardFlushObserver, nsIObserver)
197
198 NS_IMETHODIMP
199 APZCTreeManager::CheckerboardFlushObserver::Observe(nsISupports* aSubject,
200 const char* aTopic,
201 const char16_t*) {
202 MOZ_ASSERT(NS_IsMainThread());
203 MOZ_ASSERT(mTreeManager.get());
204
205 RecursiveMutexAutoLock lock(mTreeManager->mTreeLock);
206 if (mTreeManager->mRootNode) {
207 ForEachNode<ReverseIterator>(
208 mTreeManager->mRootNode.get(), [](HitTestingTreeNode* aNode) {
209 if (aNode->IsPrimaryHolder()) {
210 MOZ_ASSERT(aNode->GetApzc());
211 aNode->GetApzc()->FlushActiveCheckerboardReport();
212 }
213 });
214 }
215 if (XRE_IsGPUProcess()) {
216 if (gfx::GPUParent* gpu = gfx::GPUParent::GetSingleton()) {
217 nsCString topic("APZ:FlushActiveCheckerboard:Done");
218 Unused << gpu->SendNotifyUiObservers(topic);
219 }
220 } else {
221 MOZ_ASSERT(XRE_IsParentProcess());
222 nsCOMPtr<nsIObserverService> obsSvc =
223 mozilla::services::GetObserverService();
224 if (obsSvc) {
225 obsSvc->NotifyObservers(nullptr, "APZ:FlushActiveCheckerboard:Done",
226 nullptr);
227 }
228 }
229 return NS_OK;
230 }
231
232 /**
233 * A RAII class used for setting the focus sequence number on input events
234 * as they are being processed. Any input event is assumed to be potentially
235 * focus changing unless explicitly marked otherwise.
236 */
237 class MOZ_RAII AutoFocusSequenceNumberSetter {
238 public:
AutoFocusSequenceNumberSetter(FocusState & aFocusState,InputData & aEvent)239 AutoFocusSequenceNumberSetter(FocusState& aFocusState, InputData& aEvent)
240 : mFocusState(aFocusState), mEvent(aEvent), mMayChangeFocus(true) {}
241
MarkAsNonFocusChanging()242 void MarkAsNonFocusChanging() { mMayChangeFocus = false; }
243
~AutoFocusSequenceNumberSetter()244 ~AutoFocusSequenceNumberSetter() {
245 if (mMayChangeFocus) {
246 mFocusState.ReceiveFocusChangingEvent();
247
248 APZ_KEY_LOG(
249 "Marking input with type=%d as focus changing with seq=%" PRIu64 "\n",
250 static_cast<int>(mEvent.mInputType),
251 mFocusState.LastAPZProcessedEvent());
252 } else {
253 APZ_KEY_LOG(
254 "Marking input with type=%d as non focus changing with seq=%" PRIu64
255 "\n",
256 static_cast<int>(mEvent.mInputType),
257 mFocusState.LastAPZProcessedEvent());
258 }
259
260 mEvent.mFocusSequenceNumber = mFocusState.LastAPZProcessedEvent();
261 }
262
263 private:
264 FocusState& mFocusState;
265 InputData& mEvent;
266 bool mMayChangeFocus;
267 };
268
APZCTreeManager(LayersId aRootLayersId)269 APZCTreeManager::APZCTreeManager(LayersId aRootLayersId)
270 : mInputQueue(new InputQueue()),
271 mRootLayersId(aRootLayersId),
272 mSampler(nullptr),
273 mUpdater(nullptr),
274 mTreeLock("APZCTreeLock"),
275 mUsingAsyncZoomContainer(false),
276 mMapLock("APZCMapLock"),
277 mRetainedTouchIdentifier(-1),
278 mInScrollbarTouchDrag(false),
279 mApzcTreeLog("apzctree"),
280 mTestDataLock("APZTestDataLock"),
281 mDPI(160.0) {
282 RefPtr<APZCTreeManager> self(this);
283 NS_DispatchToMainThread(NS_NewRunnableFunction(
284 "layers::APZCTreeManager::APZCTreeManager",
285 [self] { self->mFlushObserver = new CheckerboardFlushObserver(self); }));
286 AsyncPanZoomController::InitializeGlobalState();
287 mApzcTreeLog.ConditionOnPrefFunction(StaticPrefs::apz_printtree);
288 }
289
290 APZCTreeManager::~APZCTreeManager() = default;
291
SetSampler(APZSampler * aSampler)292 void APZCTreeManager::SetSampler(APZSampler* aSampler) {
293 // We're either setting the sampler or clearing it
294 MOZ_ASSERT((mSampler == nullptr) != (aSampler == nullptr));
295 mSampler = aSampler;
296 }
297
SetUpdater(APZUpdater * aUpdater)298 void APZCTreeManager::SetUpdater(APZUpdater* aUpdater) {
299 // We're either setting the updater or clearing it
300 MOZ_ASSERT((mUpdater == nullptr) != (aUpdater == nullptr));
301 mUpdater = aUpdater;
302 }
303
NotifyLayerTreeAdopted(LayersId aLayersId,const RefPtr<APZCTreeManager> & aOldApzcTreeManager)304 void APZCTreeManager::NotifyLayerTreeAdopted(
305 LayersId aLayersId, const RefPtr<APZCTreeManager>& aOldApzcTreeManager) {
306 AssertOnUpdaterThread();
307
308 if (aOldApzcTreeManager) {
309 aOldApzcTreeManager->mFocusState.RemoveFocusTarget(aLayersId);
310 // While we could move the focus target information from the old APZC tree
311 // manager into this one, it's safer to not do that, as we'll probably have
312 // that information repopulated soon anyway (on the next layers update).
313 }
314
315 UniquePtr<APZTestData> adoptedData;
316 if (aOldApzcTreeManager) {
317 MutexAutoLock lock(aOldApzcTreeManager->mTestDataLock);
318 auto it = aOldApzcTreeManager->mTestData.find(aLayersId);
319 if (it != aOldApzcTreeManager->mTestData.end()) {
320 adoptedData = std::move(it->second);
321 aOldApzcTreeManager->mTestData.erase(it);
322 }
323 }
324 if (adoptedData) {
325 MutexAutoLock lock(mTestDataLock);
326 mTestData[aLayersId] = std::move(adoptedData);
327 }
328 }
329
NotifyLayerTreeRemoved(LayersId aLayersId)330 void APZCTreeManager::NotifyLayerTreeRemoved(LayersId aLayersId) {
331 AssertOnUpdaterThread();
332
333 mFocusState.RemoveFocusTarget(aLayersId);
334
335 { // scope lock
336 MutexAutoLock lock(mTestDataLock);
337 mTestData.erase(aLayersId);
338 }
339 }
340
NewAPZCInstance(LayersId aLayersId,GeckoContentController * aController)341 AsyncPanZoomController* APZCTreeManager::NewAPZCInstance(
342 LayersId aLayersId, GeckoContentController* aController) {
343 return new AsyncPanZoomController(
344 aLayersId, this, mInputQueue, aController,
345 AsyncPanZoomController::USE_GESTURE_DETECTOR);
346 }
347
SetTestSampleTime(const Maybe<TimeStamp> & aTime)348 void APZCTreeManager::SetTestSampleTime(const Maybe<TimeStamp>& aTime) {
349 mTestSampleTime = aTime;
350 }
351
GetFrameTime()352 TimeStamp APZCTreeManager::GetFrameTime() {
353 if (mTestSampleTime) {
354 return *mTestSampleTime;
355 }
356 return TimeStamp::Now();
357 }
358
SetAllowedTouchBehavior(uint64_t aInputBlockId,const nsTArray<TouchBehaviorFlags> & aValues)359 void APZCTreeManager::SetAllowedTouchBehavior(
360 uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aValues) {
361 APZThreadUtils::AssertOnControllerThread();
362
363 mInputQueue->SetAllowedTouchBehavior(aInputBlockId, aValues);
364 }
365
366 template <class ScrollNode>
367 void // ScrollNode is a LayerMetricsWrapper or a WebRenderScrollDataWrapper
UpdateHitTestingTreeImpl(const ScrollNode & aRoot,bool aIsFirstPaint,LayersId aOriginatingLayersId,uint32_t aPaintSequenceNumber)368 APZCTreeManager::UpdateHitTestingTreeImpl(const ScrollNode& aRoot,
369 bool aIsFirstPaint,
370 LayersId aOriginatingLayersId,
371 uint32_t aPaintSequenceNumber) {
372 RecursiveMutexAutoLock lock(mTreeLock);
373
374 // For testing purposes, we log some data to the APZTestData associated with
375 // the layers id that originated this update.
376 APZTestData* testData = nullptr;
377 if (StaticPrefs::apz_test_logging_enabled()) {
378 MutexAutoLock lock(mTestDataLock);
379 UniquePtr<APZTestData> ptr = MakeUnique<APZTestData>();
380 auto result =
381 mTestData.insert(std::make_pair(aOriginatingLayersId, std::move(ptr)));
382 testData = result.first->second.get();
383 testData->StartNewPaint(aPaintSequenceNumber);
384 }
385
386 TreeBuildingState state(mRootLayersId, aIsFirstPaint, aOriginatingLayersId,
387 testData, aPaintSequenceNumber);
388
389 // We do this business with collecting the entire tree into an array because
390 // otherwise it's very hard to determine which APZC instances need to be
391 // destroyed. In the worst case, there are two scenarios: (a) a layer with an
392 // APZC is removed from the layer tree and (b) a layer with an APZC is moved
393 // in the layer tree from one place to a completely different place. In
394 // scenario (a) we would want to destroy the APZC while walking the layer tree
395 // and noticing that the layer/APZC is no longer there. But if we do that then
396 // we run into a problem in scenario (b) because we might encounter that layer
397 // later during the walk. To handle both of these we have to 'remember' that
398 // the layer was not found, and then do the destroy only at the end of the
399 // tree walk after we are sure that the layer was removed and not just
400 // transplanted elsewhere. Doing that as part of a recursive tree walk is hard
401 // and so maintaining a list and removing APZCs that are still alive is much
402 // simpler.
403 ForEachNode<ReverseIterator>(mRootNode.get(),
404 [&state](HitTestingTreeNode* aNode) {
405 state.mNodesToDestroy.AppendElement(aNode);
406 });
407 mRootNode = nullptr;
408 mUsingAsyncZoomContainer = false;
409 int asyncZoomContainerNestingDepth = 0;
410 bool haveMultipleAsyncZoomContainers = false;
411 bool haveRootContentOutsideAsyncZoomContainer = false;
412
413 if (aRoot) {
414 std::unordered_set<LayersId, LayersId::HashFn> seenLayersIds;
415 std::stack<gfx::TreeAutoIndent<LOG_DEFAULT>> indents;
416 std::stack<AncestorTransform> ancestorTransforms;
417 HitTestingTreeNode* parent = nullptr;
418 HitTestingTreeNode* next = nullptr;
419 LayersId layersId = mRootLayersId;
420 seenLayersIds.insert(mRootLayersId);
421 ancestorTransforms.push(AncestorTransform());
422 state.mParentHasPerspective.push(false);
423 state.mOverrideFlags.push(EventRegionsOverride::NoOverride);
424
425 mApzcTreeLog << "[start]\n";
426 mTreeLock.AssertCurrentThreadIn();
427
428 ForEachNode<ReverseIterator>(
429 aRoot,
430 [&](ScrollNode aLayerMetrics) {
431 mApzcTreeLog << aLayerMetrics.Name() << '\t';
432
433 if (aLayerMetrics.IsAsyncZoomContainer()) {
434 if (mUsingAsyncZoomContainer) {
435 haveMultipleAsyncZoomContainers = true;
436 }
437 mUsingAsyncZoomContainer = true;
438 ++asyncZoomContainerNestingDepth;
439 }
440
441 if (aLayerMetrics.Metrics().IsRootContent()) {
442 MutexAutoLock lock(mMapLock);
443 mGeckoFixedLayerMargins =
444 aLayerMetrics.Metrics().GetFixedLayerMargins();
445 } else {
446 MOZ_ASSERT(aLayerMetrics.Metrics().GetFixedLayerMargins() ==
447 ScreenMargin(),
448 "fixed-layer-margins should be 0 on non-root layer");
449 }
450
451 // Note that this check happens after the potential increment of
452 // asyncZoomContainerNestingDepth, to allow the root content
453 // metadata to be on the same node as the async zoom container.
454 if (aLayerMetrics.Metrics().IsRootContent() &&
455 asyncZoomContainerNestingDepth == 0) {
456 haveRootContentOutsideAsyncZoomContainer = true;
457 }
458
459 HitTestingTreeNode* node = PrepareNodeForLayer(
460 lock, aLayerMetrics, aLayerMetrics.Metrics(), layersId,
461 ancestorTransforms.top(), parent, next, state);
462 MOZ_ASSERT(node);
463 AsyncPanZoomController* apzc = node->GetApzc();
464 aLayerMetrics.SetApzc(apzc);
465
466 // GetScrollbarAnimationId is only set when webrender is enabled,
467 // which limits the extra thumb mapping work to the webrender-enabled
468 // case where it is needed.
469 // Note also that when webrender is enabled, a "valid" animation id
470 // is always nonzero, so we don't need to worry about handling the
471 // case where WR is enabled and the animation id is zero.
472 if (node->GetScrollbarAnimationId()) {
473 if (node->IsScrollThumbNode()) {
474 state.mScrollThumbs.push_back(node);
475 } else if (node->IsScrollbarContainerNode()) {
476 // Only scrollbar containers for the root have an animation id.
477 state.mRootScrollbarInfo.emplace_back(
478 *(node->GetScrollbarAnimationId()),
479 node->GetScrollbarDirection());
480 }
481 }
482
483 // GetFixedPositionAnimationId is only set when webrender is enabled.
484 if (node->GetFixedPositionAnimationId().isSome()) {
485 state.mFixedPositionInfo.emplace_back(node);
486 }
487 // GetStickyPositionAnimationId is only set when webrender is enabled.
488 if (node->GetStickyPositionAnimationId().isSome()) {
489 state.mStickyPositionInfo.emplace_back(node);
490 }
491 if (apzc && node->IsPrimaryHolder()) {
492 state.mScrollTargets[apzc->GetGuid()] = node;
493 }
494
495 mApzcTreeLog << '\n';
496
497 // Accumulate the CSS transform between layers that have an APZC.
498 // In the terminology of the big comment above
499 // APZCTreeManager::GetScreenToApzcTransform, if we are at layer M,
500 // then aAncestorTransform is NC * OC * PC, and we left-multiply MC
501 // and compute ancestorTransform to be MC * NC * OC * PC. This gets
502 // passed down as the ancestor transform to layer L when we recurse
503 // into the children below. If we are at a layer with an APZC, such as
504 // P, then we reset the ancestorTransform to just PC, to start the new
505 // accumulation as we go down.
506 AncestorTransform currentTransform{
507 aLayerMetrics.GetTransform(),
508 aLayerMetrics.TransformIsPerspective()};
509 if (!apzc) {
510 currentTransform = currentTransform * ancestorTransforms.top();
511 }
512 ancestorTransforms.push(currentTransform);
513
514 // Note that |node| at this point will not have any children,
515 // otherwise we we would have to set next to node->GetFirstChild().
516 MOZ_ASSERT(!node->GetFirstChild());
517 parent = node;
518 next = nullptr;
519
520 // Update the layersId if we have a new one
521 if (Maybe<LayersId> newLayersId = aLayerMetrics.GetReferentId()) {
522 layersId = *newLayersId;
523 seenLayersIds.insert(layersId);
524
525 // Propagate any event region override flags down into all
526 // descendant nodes from the reflayer that has the flag. This is an
527 // optimization to avoid having to walk up the tree to check the
528 // override flags. Note that we don't keep the flags on the reflayer
529 // itself, because the semantics of the flags are that they apply
530 // to all content in the layer subtree being referenced. This
531 // matters with the WR hit-test codepath, because this reflayer may
532 // be just one of many nodes associated with a particular APZC, and
533 // calling GetTargetNode with a guid may return any one of the
534 // nodes. If different nodes have different flags on them that can
535 // make the WR hit-test result incorrect, but being strict about
536 // only putting the flags on descendant layers avoids this problem.
537 state.mOverrideFlags.push(state.mOverrideFlags.top() |
538 aLayerMetrics.GetEventRegionsOverride());
539 }
540
541 indents.push(gfx::TreeAutoIndent<LOG_DEFAULT>(mApzcTreeLog));
542 state.mParentHasPerspective.push(
543 aLayerMetrics.TransformIsPerspective());
544 },
545 [&](ScrollNode aLayerMetrics) {
546 if (aLayerMetrics.IsAsyncZoomContainer()) {
547 --asyncZoomContainerNestingDepth;
548 }
549 if (aLayerMetrics.GetReferentId()) {
550 state.mOverrideFlags.pop();
551 }
552
553 next = parent;
554 parent = parent->GetParent();
555 layersId = next->GetLayersId();
556 ancestorTransforms.pop();
557 indents.pop();
558 state.mParentHasPerspective.pop();
559 });
560
561 mApzcTreeLog << "[end]\n";
562
563 MOZ_ASSERT(
564 !mUsingAsyncZoomContainer || !haveRootContentOutsideAsyncZoomContainer,
565 "If there is an async zoom container, all scroll nodes with root "
566 "content scroll metadata should be inside it");
567 // TODO(bug 1534459): Avoid multiple async zoom containers. They
568 // can't currently occur in production code, but that will become
569 // possible with either OOP iframes or desktop zooming (due to
570 // RDM), and will need to be guarded against.
571 // MOZ_ASSERT(!haveMultipleAsyncZoomContainers,
572 // "Should only have one async zoom container");
573
574 // If we have perspective transforms deferred to children, do another
575 // walk of the tree and actually apply them to the children.
576 // We can't do this "as we go" in the previous traversal, because by the
577 // time we realize we need to defer a perspective transform for an APZC,
578 // we may already have processed a previous layer (including children
579 // found in its subtree) that shares that APZC.
580 if (!state.mPerspectiveTransformsDeferredToChildren.empty()) {
581 ForEachNode<ReverseIterator>(
582 mRootNode.get(), [&state](HitTestingTreeNode* aNode) {
583 AsyncPanZoomController* apzc = aNode->GetApzc();
584 if (!apzc) {
585 return;
586 }
587 if (!aNode->IsPrimaryHolder()) {
588 return;
589 }
590
591 AsyncPanZoomController* parent = apzc->GetParent();
592 if (!parent) {
593 return;
594 }
595
596 auto it =
597 state.mPerspectiveTransformsDeferredToChildren.find(parent);
598 if (it != state.mPerspectiveTransformsDeferredToChildren.end()) {
599 apzc->SetAncestorTransform(AncestorTransform{
600 it->second * apzc->GetAncestorTransform(), false});
601 }
602 });
603 }
604
605 // Remove any layers ids for which we no longer have content from
606 // mDetachedLayersIds.
607 for (auto iter = mDetachedLayersIds.begin();
608 iter != mDetachedLayersIds.end();) {
609 // unordered_set::erase() invalidates the iterator pointing to the
610 // element being erased, but returns an iterator to the next element.
611 if (seenLayersIds.find(*iter) == seenLayersIds.end()) {
612 iter = mDetachedLayersIds.erase(iter);
613 } else {
614 ++iter;
615 }
616 }
617 }
618
619 // We do not support tree structures where the root node has siblings.
620 MOZ_ASSERT(!(mRootNode && mRootNode->GetPrevSibling()));
621
622 { // scope lock and update our mApzcMap before we destroy all the unused
623 // APZC instances
624 MutexAutoLock lock(mMapLock);
625 mApzcMap = std::move(state.mApzcMap);
626
627 for (auto& mapping : mApzcMap) {
628 AsyncPanZoomController* parent = mapping.second.apzc->GetParent();
629 mapping.second.parent = parent ? Some(parent->GetGuid()) : Nothing();
630 }
631
632 mScrollThumbInfo.clear();
633 // For non-webrender, state.mScrollThumbs will be empty so this will be a
634 // no-op.
635 for (HitTestingTreeNode* thumb : state.mScrollThumbs) {
636 MOZ_ASSERT(thumb->IsScrollThumbNode());
637 ScrollableLayerGuid targetGuid(thumb->GetLayersId(), 0,
638 thumb->GetScrollTargetId());
639 auto it = state.mScrollTargets.find(targetGuid);
640 if (it == state.mScrollTargets.end()) {
641 // It could be that |thumb| is a scrollthumb for content which didn't
642 // have an APZC, for example if the content isn't layerized. Regardless,
643 // we can't async-scroll it so we don't need to worry about putting it
644 // in mScrollThumbInfo.
645 continue;
646 }
647 HitTestingTreeNode* target = it->second;
648 mScrollThumbInfo.emplace_back(
649 *(thumb->GetScrollbarAnimationId()), thumb->GetTransform(),
650 thumb->GetScrollbarData(), targetGuid, target->GetTransform(),
651 target->IsAncestorOf(thumb));
652 }
653
654 mRootScrollbarInfo = std::move(state.mRootScrollbarInfo);
655 mFixedPositionInfo = std::move(state.mFixedPositionInfo);
656 mStickyPositionInfo = std::move(state.mStickyPositionInfo);
657 }
658
659 for (size_t i = 0; i < state.mNodesToDestroy.Length(); i++) {
660 APZCTM_LOG("Destroying node at %p with APZC %p\n",
661 state.mNodesToDestroy[i].get(),
662 state.mNodesToDestroy[i]->GetApzc());
663 state.mNodesToDestroy[i]->Destroy();
664 }
665
666 APZCTM_LOG("APZCTreeManager (%p)\n", this);
667 if (mRootNode && MOZ_LOG_TEST(sApzMgrLog, LogLevel::Debug)) {
668 mRootNode->Dump(" ");
669 }
670 SendSubtreeTransformsToChromeMainThread(nullptr);
671 }
672
UpdateFocusState(LayersId aRootLayerTreeId,LayersId aOriginatingLayersId,const FocusTarget & aFocusTarget)673 void APZCTreeManager::UpdateFocusState(LayersId aRootLayerTreeId,
674 LayersId aOriginatingLayersId,
675 const FocusTarget& aFocusTarget) {
676 AssertOnUpdaterThread();
677
678 if (!StaticPrefs::apz_keyboard_enabled_AtStartup()) {
679 return;
680 }
681
682 mFocusState.Update(aRootLayerTreeId, aOriginatingLayersId, aFocusTarget);
683 }
684
UpdateHitTestingTree(Layer * aRoot,bool aIsFirstPaint,LayersId aOriginatingLayersId,uint32_t aPaintSequenceNumber)685 void APZCTreeManager::UpdateHitTestingTree(Layer* aRoot, bool aIsFirstPaint,
686 LayersId aOriginatingLayersId,
687 uint32_t aPaintSequenceNumber) {
688 AssertOnUpdaterThread();
689
690 LayerMetricsWrapper root(aRoot);
691 UpdateHitTestingTreeImpl(root, aIsFirstPaint, aOriginatingLayersId,
692 aPaintSequenceNumber);
693 }
694
UpdateHitTestingTree(const WebRenderScrollDataWrapper & aScrollWrapper,bool aIsFirstPaint,LayersId aOriginatingLayersId,uint32_t aPaintSequenceNumber)695 void APZCTreeManager::UpdateHitTestingTree(
696 const WebRenderScrollDataWrapper& aScrollWrapper, bool aIsFirstPaint,
697 LayersId aOriginatingLayersId, uint32_t aPaintSequenceNumber) {
698 AssertOnUpdaterThread();
699
700 UpdateHitTestingTreeImpl(aScrollWrapper, aIsFirstPaint, aOriginatingLayersId,
701 aPaintSequenceNumber);
702 }
703
SampleForWebRender(wr::TransactionWrapper & aTxn,const TimeStamp & aSampleTime,const wr::WrPipelineIdEpochs * aEpochsBeingRendered)704 void APZCTreeManager::SampleForWebRender(
705 wr::TransactionWrapper& aTxn, const TimeStamp& aSampleTime,
706 const wr::WrPipelineIdEpochs* aEpochsBeingRendered) {
707 AssertOnSamplerThread();
708 MutexAutoLock lock(mMapLock);
709
710 bool activeAnimations = AdvanceAnimationsInternal(lock, aSampleTime);
711 if (activeAnimations) {
712 RefPtr<CompositorController> controller;
713 CompositorBridgeParent::CallWithIndirectShadowTree(
714 mRootLayersId, [&](LayerTreeState& aState) -> void {
715 controller = aState.GetCompositorController();
716 });
717 if (controller) {
718 controller->ScheduleRenderOnCompositorThread();
719 }
720 }
721
722 nsTArray<wr::WrTransformProperty> transforms;
723
724 // Sample async transforms on scrollable layers.
725 for (const auto& mapping : mApzcMap) {
726 AsyncPanZoomController* apzc = mapping.second.apzc;
727
728 const AsyncTransformComponents asyncTransformComponents =
729 apzc->GetZoomAnimationId()
730 ? AsyncTransformComponents{AsyncTransformComponent::eLayout}
731 : LayoutAndVisual;
732 ParentLayerPoint layerTranslation =
733 apzc->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing,
734 asyncTransformComponents)
735 .mTranslation;
736
737 if (Maybe<CompositionPayload> payload = apzc->NotifyScrollSampling()) {
738 RefPtr<WebRenderBridgeParent> wrBridgeParent;
739 LayersId layersId = apzc->GetGuid().mLayersId;
740 CompositorBridgeParent::CallWithIndirectShadowTree(
741 layersId, [&](LayerTreeState& aState) -> void {
742 wrBridgeParent = aState.mWrBridge;
743 });
744
745 if (wrBridgeParent) {
746 wr::PipelineId pipelineId = wr::AsPipelineId(layersId);
747 for (size_t i = 0; i < aEpochsBeingRendered->Length(); i++) {
748 if ((*aEpochsBeingRendered)[i].pipeline_id == pipelineId) {
749 auto& epoch = (*aEpochsBeingRendered)[i].epoch;
750 wrBridgeParent->AddPendingScrollPayload(
751 *payload, std::make_pair(pipelineId, epoch));
752 break;
753 }
754 }
755 }
756 }
757
758 if (Maybe<uint64_t> zoomAnimationId = apzc->GetZoomAnimationId()) {
759 // for now we only support zooming on root content APZCs
760 MOZ_ASSERT(apzc->IsRootContent());
761
762 LayoutDeviceToParentLayerScale zoom = apzc->GetCurrentPinchZoomScale(
763 AsyncPanZoomController::eForCompositing);
764
765 AsyncTransform asyncVisualTransform = apzc->GetCurrentAsyncTransform(
766 AsyncPanZoomController::eForCompositing,
767 AsyncTransformComponents{AsyncTransformComponent::eVisual});
768
769 transforms.AppendElement(wr::ToWrTransformProperty(
770 *zoomAnimationId, LayoutDeviceToParentLayerMatrix4x4::Scaling(
771 zoom.scale, zoom.scale, 1.0f) *
772 AsyncTransformComponentMatrix::Translation(
773 asyncVisualTransform.mTranslation)));
774
775 aTxn.UpdateIsTransformAsyncZooming(*zoomAnimationId,
776 apzc->IsAsyncZooming());
777 }
778
779 // If layerTranslation includes only the layout component of the async
780 // transform then it has not been scaled by the async zoom, so we want to
781 // divide it by the resolution. If layerTranslation includes the visual
782 // component, then we should use the pinch zoom scale, which includes the
783 // async zoom. However, we only use LayoutAndVisual for non-zoomable APZCs,
784 // so it makes no difference.
785 LayoutDeviceToParentLayerScale resolution =
786 apzc->GetCumulativeResolution().ToScaleFactor() *
787 LayerToParentLayerScale(1.0f);
788 // The positive translation means the painted content is supposed to
789 // move down (or to the right), and that corresponds to a reduction in
790 // the scroll offset. Since we are effectively giving WR the async
791 // scroll delta here, we want to negate the translation.
792 LayoutDevicePoint asyncScrollDelta = -layerTranslation / resolution;
793 aTxn.UpdateScrollPosition(wr::AsPipelineId(apzc->GetGuid().mLayersId),
794 apzc->GetGuid().mScrollId,
795 wr::ToLayoutPoint(asyncScrollDelta));
796
797 #if defined(MOZ_WIDGET_ANDROID)
798 // Send the root frame metrics to java through the UIController
799 RefPtr<UiCompositorControllerParent> uiController =
800 UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayersId);
801 if (uiController &&
802 apzc->UpdateRootFrameMetricsIfChanged(mLastRootMetrics)) {
803 uiController->NotifyUpdateScreenMetrics(mLastRootMetrics);
804 }
805 #endif
806 }
807
808 // Now collect all the async transforms needed for the scrollthumbs.
809 for (const ScrollThumbInfo& info : mScrollThumbInfo) {
810 auto it = mApzcMap.find(info.mTargetGuid);
811 if (it == mApzcMap.end()) {
812 // It could be that |info| is a scrollthumb for content which didn't
813 // have an APZC, for example if the content isn't layerized. Regardless,
814 // we can't async-scroll it so we don't need to worry about putting it
815 // in mScrollThumbInfo.
816 continue;
817 }
818 AsyncPanZoomController* scrollTargetApzc = it->second.apzc;
819 MOZ_ASSERT(scrollTargetApzc);
820 LayerToParentLayerMatrix4x4 transform =
821 scrollTargetApzc->CallWithLastContentPaintMetrics(
822 [&](const FrameMetrics& aMetrics) {
823 return ComputeTransformForScrollThumb(
824 info.mThumbTransform * AsyncTransformMatrix(),
825 info.mTargetTransform.ToUnknownMatrix(), scrollTargetApzc,
826 aMetrics, info.mThumbData, info.mTargetIsAncestor, nullptr);
827 });
828 transforms.AppendElement(
829 wr::ToWrTransformProperty(info.mThumbAnimationId, transform));
830 }
831
832 // Move the root scrollbar in response to the dynamic toolbar transition.
833 for (const RootScrollbarInfo& info : mRootScrollbarInfo) {
834 // We only care about the horizontal scrollbar.
835 if (info.mScrollDirection == ScrollDirection::eHorizontal) {
836 ScreenPoint translation =
837 AsyncCompositionManager::ComputeFixedMarginsOffset(
838 GetCompositorFixedLayerMargins(lock), SideBits::eBottom,
839 ScreenMargin());
840
841 LayerToParentLayerMatrix4x4 transform =
842 LayerToParentLayerMatrix4x4::Translation(ViewAs<ParentLayerPixel>(
843 translation, PixelCastJustification::ScreenIsParentLayerForRoot));
844
845 transforms.AppendElement(
846 wr::ToWrTransformProperty(info.mScrollbarAnimationId, transform));
847 }
848 }
849
850 for (const FixedPositionInfo& info : mFixedPositionInfo) {
851 MOZ_ASSERT(info.mFixedPositionAnimationId.isSome());
852 if (!IsFixedToRootContent(info, lock)) {
853 continue;
854 }
855
856 ScreenPoint translation =
857 AsyncCompositionManager::ComputeFixedMarginsOffset(
858 GetCompositorFixedLayerMargins(lock), info.mFixedPosSides,
859 mGeckoFixedLayerMargins);
860
861 LayerToParentLayerMatrix4x4 transform =
862 LayerToParentLayerMatrix4x4::Translation(ViewAs<ParentLayerPixel>(
863 translation, PixelCastJustification::ScreenIsParentLayerForRoot));
864
865 transforms.AppendElement(
866 wr::ToWrTransformProperty(*info.mFixedPositionAnimationId, transform));
867 }
868
869 for (const StickyPositionInfo& info : mStickyPositionInfo) {
870 MOZ_ASSERT(info.mStickyPositionAnimationId.isSome());
871 SideBits sides = SidesStuckToRootContent(info, lock);
872 if (sides == SideBits::eNone) {
873 continue;
874 }
875
876 ScreenPoint translation =
877 AsyncCompositionManager::ComputeFixedMarginsOffset(
878 GetCompositorFixedLayerMargins(lock), sides,
879 // For sticky layers, we don't need to factor
880 // mGeckoFixedLayerMargins because Gecko doesn't shift the
881 // position of sticky elements for dynamic toolbar movements.
882 ScreenMargin());
883
884 LayerToParentLayerMatrix4x4 transform =
885 LayerToParentLayerMatrix4x4::Translation(ViewAs<ParentLayerPixel>(
886 translation, PixelCastJustification::ScreenIsParentLayerForRoot));
887
888 transforms.AppendElement(
889 wr::ToWrTransformProperty(*info.mStickyPositionAnimationId, transform));
890 }
891
892 aTxn.AppendTransformProperties(transforms);
893 }
894
AdvanceAnimations(const TimeStamp & aSampleTime)895 bool APZCTreeManager::AdvanceAnimations(const TimeStamp& aSampleTime) {
896 MutexAutoLock lock(mMapLock);
897 return AdvanceAnimationsInternal(lock, aSampleTime);
898 }
899
ComputeClippedCompositionBounds(const MutexAutoLock & aProofOfMapLock,ClippedCompositionBoundsMap & aDestMap,ScrollableLayerGuid aGuid)900 ParentLayerRect APZCTreeManager::ComputeClippedCompositionBounds(
901 const MutexAutoLock& aProofOfMapLock, ClippedCompositionBoundsMap& aDestMap,
902 ScrollableLayerGuid aGuid) {
903 if (auto iter = aDestMap.find(aGuid); iter != aDestMap.end()) {
904 // We already computed it for this one, early-exit. This might happen
905 // because on a later iteration of mApzcMap we might encounter an ancestor
906 // of an APZC that we processed on an earlier iteration. In this case we
907 // would have computed the ancestor's clipped composition bounds when
908 // recursing up on the earlier iteration.
909 return iter->second;
910 }
911
912 ParentLayerRect bounds = mApzcMap[aGuid].apzc->GetCompositionBounds();
913 const auto& mapEntry = mApzcMap.find(aGuid);
914 MOZ_ASSERT(mapEntry != mApzcMap.end());
915 if (mapEntry->second.parent.isNothing()) {
916 // Recursion base case, where the APZC with guid `aGuid` has no parent.
917 // In this case, we don't need to clip `bounds` any further and can just
918 // early exit.
919 aDestMap.emplace(aGuid, bounds);
920 return bounds;
921 }
922
923 ScrollableLayerGuid parentGuid = mapEntry->second.parent.value();
924 auto parentBoundsEntry = aDestMap.find(parentGuid);
925 // If aDestMap doesn't contain the parent entry yet, we recurse to compute
926 // that one first.
927 ParentLayerRect parentClippedBounds =
928 (parentBoundsEntry == aDestMap.end())
929 ? ComputeClippedCompositionBounds(aProofOfMapLock, aDestMap,
930 parentGuid)
931 : parentBoundsEntry->second;
932
933 // The parent layer's async transform applies to the current layer to take
934 // `bounds` into the same coordinate space as `parentClippedBounds`. However,
935 // we're going to do the inverse operation and unapply this transform to
936 // `parentClippedBounds` to bring it into the same coordinate space as
937 // `bounds`.
938 AsyncTransform appliesToLayer =
939 mApzcMap[parentGuid].apzc->GetCurrentAsyncTransform(
940 AsyncPanZoomController::eForCompositing);
941
942 // Do the unapplication
943 LayerRect parentClippedBoundsInParentLayerSpace =
944 (parentClippedBounds - appliesToLayer.mTranslation) /
945 appliesToLayer.mScale;
946
947 // And then clip `bounds` by the parent's comp bounds in the current space.
948 bounds = bounds.Intersect(
949 ViewAs<ParentLayerPixel>(parentClippedBoundsInParentLayerSpace,
950 PixelCastJustification::MovingDownToChildren));
951
952 // Done!
953 aDestMap.emplace(aGuid, bounds);
954 return bounds;
955 }
956
AdvanceAnimationsInternal(const MutexAutoLock & aProofOfMapLock,const TimeStamp & aSampleTime)957 bool APZCTreeManager::AdvanceAnimationsInternal(
958 const MutexAutoLock& aProofOfMapLock, const TimeStamp& aSampleTime) {
959 ClippedCompositionBoundsMap clippedCompBounds;
960 bool activeAnimations = false;
961 for (const auto& mapping : mApzcMap) {
962 AsyncPanZoomController* apzc = mapping.second.apzc;
963 // Note that this call is recursive, but it early-exits if called again
964 // with the same guid. So this loop is still amortized O(n) with respect to
965 // the number of APZCs.
966 ParentLayerRect clippedBounds = ComputeClippedCompositionBounds(
967 aProofOfMapLock, clippedCompBounds, mapping.first);
968
969 apzc->ReportCheckerboard(aSampleTime, clippedBounds);
970 activeAnimations |= apzc->AdvanceAnimations(aSampleTime);
971 }
972 return activeAnimations;
973 }
974
975 // Compute the clip region to be used for a layer with an APZC. This function
976 // is only called for layers which actually have scrollable metrics and an APZC.
977 template <class ScrollNode>
ComputeClipRegion(const ScrollNode & aLayer)978 Maybe<ParentLayerIntRegion> APZCTreeManager::ComputeClipRegion(
979 const ScrollNode& aLayer) {
980 Maybe<ParentLayerIntRegion> clipRegion;
981 if (aLayer.GetClipRect()) {
982 clipRegion.emplace(*aLayer.GetClipRect());
983 } else if (aLayer.Metrics().IsRootContent() && mUsingAsyncZoomContainer) {
984 // If we are using containerless scrolling, part of the root content
985 // layers' async transform has been lifted to the async zoom container
986 // layer. The composition bounds clip, which applies after the async
987 // transform, needs to be lifted too. Layout code already takes care of
988 // this for us, we just need to not mess it up by introducing a
989 // composition bounds clip here, so we leave the clip empty.
990 } else {
991 // if there is no clip on this layer (which should only happen for the
992 // root scrollable layer in a process, or for some of the LayerMetrics
993 // expansions of a multi-metrics layer), fall back to using the comp
994 // bounds which should be equivalent.
995 clipRegion.emplace(RoundedToInt(aLayer.Metrics().GetCompositionBounds()));
996 }
997
998 return clipRegion;
999 }
1000
1001 template <class ScrollNode>
PrintAPZCInfo(const ScrollNode & aLayer,const AsyncPanZoomController * apzc)1002 void APZCTreeManager::PrintAPZCInfo(const ScrollNode& aLayer,
1003 const AsyncPanZoomController* apzc) {
1004 const FrameMetrics& metrics = aLayer.Metrics();
1005 mApzcTreeLog << "APZC " << apzc->GetGuid()
1006 << "\tcb=" << metrics.GetCompositionBounds()
1007 << "\tsr=" << metrics.GetScrollableRect()
1008 << (metrics.IsScrollInfoLayer() ? "\tscrollinfo" : "")
1009 << (apzc->HasScrollgrab() ? "\tscrollgrab" : "") << "\t"
1010 << aLayer.Metadata().GetContentDescription().get();
1011 }
1012
AttachNodeToTree(HitTestingTreeNode * aNode,HitTestingTreeNode * aParent,HitTestingTreeNode * aNextSibling)1013 void APZCTreeManager::AttachNodeToTree(HitTestingTreeNode* aNode,
1014 HitTestingTreeNode* aParent,
1015 HitTestingTreeNode* aNextSibling) {
1016 if (aNextSibling) {
1017 aNextSibling->SetPrevSibling(aNode);
1018 } else if (aParent) {
1019 aParent->SetLastChild(aNode);
1020 } else {
1021 MOZ_ASSERT(!mRootNode);
1022 mRootNode = aNode;
1023 aNode->MakeRoot();
1024 }
1025 }
1026
1027 template <class ScrollNode>
GetEventRegions(const ScrollNode & aLayer)1028 static EventRegions GetEventRegions(const ScrollNode& aLayer) {
1029 if (aLayer.Metrics().IsScrollInfoLayer()) {
1030 ParentLayerIntRect compositionBounds(
1031 RoundedToInt(aLayer.Metrics().GetCompositionBounds()));
1032 nsIntRegion hitRegion(compositionBounds.ToUnknownRect());
1033 EventRegions eventRegions(hitRegion);
1034 eventRegions.mDispatchToContentHitRegion = eventRegions.mHitRegion;
1035 return eventRegions;
1036 }
1037 return aLayer.GetEventRegions();
1038 }
1039
RecycleOrCreateNode(const RecursiveMutexAutoLock & aProofOfTreeLock,TreeBuildingState & aState,AsyncPanZoomController * aApzc,LayersId aLayersId)1040 already_AddRefed<HitTestingTreeNode> APZCTreeManager::RecycleOrCreateNode(
1041 const RecursiveMutexAutoLock& aProofOfTreeLock, TreeBuildingState& aState,
1042 AsyncPanZoomController* aApzc, LayersId aLayersId) {
1043 // Find a node without an APZC and return it. Note that unless the layer tree
1044 // actually changes, this loop should generally do an early-return on the
1045 // first iteration, so it should be cheap in the common case.
1046 for (int32_t i = aState.mNodesToDestroy.Length() - 1; i >= 0; i--) {
1047 RefPtr<HitTestingTreeNode> node = aState.mNodesToDestroy[i];
1048 if (node->IsRecyclable(aProofOfTreeLock)) {
1049 aState.mNodesToDestroy.RemoveElementAt(i);
1050 node->RecycleWith(aProofOfTreeLock, aApzc, aLayersId);
1051 return node.forget();
1052 }
1053 }
1054 RefPtr<HitTestingTreeNode> node =
1055 new HitTestingTreeNode(aApzc, false, aLayersId);
1056 return node.forget();
1057 }
1058
StartScrollbarDrag(const ScrollableLayerGuid & aGuid,const AsyncDragMetrics & aDragMetrics)1059 void APZCTreeManager::StartScrollbarDrag(const ScrollableLayerGuid& aGuid,
1060 const AsyncDragMetrics& aDragMetrics) {
1061 APZThreadUtils::AssertOnControllerThread();
1062
1063 RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
1064 if (!apzc) {
1065 NotifyScrollbarDragRejected(aGuid);
1066 return;
1067 }
1068
1069 uint64_t inputBlockId = aDragMetrics.mDragStartSequenceNumber;
1070 mInputQueue->ConfirmDragBlock(inputBlockId, apzc, aDragMetrics);
1071 }
1072
StartAutoscroll(const ScrollableLayerGuid & aGuid,const ScreenPoint & aAnchorLocation)1073 bool APZCTreeManager::StartAutoscroll(const ScrollableLayerGuid& aGuid,
1074 const ScreenPoint& aAnchorLocation) {
1075 APZThreadUtils::AssertOnControllerThread();
1076
1077 RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
1078 if (!apzc) {
1079 if (XRE_IsGPUProcess()) {
1080 // If we're in the compositor process, the "return false" will be
1081 // ignored because the query comes over the PAPZCTreeManager protocol
1082 // via an async message. In this case, send an explicit rejection
1083 // message to content.
1084 NotifyAutoscrollRejected(aGuid);
1085 }
1086 return false;
1087 }
1088
1089 apzc->StartAutoscroll(aAnchorLocation);
1090 return true;
1091 }
1092
StopAutoscroll(const ScrollableLayerGuid & aGuid)1093 void APZCTreeManager::StopAutoscroll(const ScrollableLayerGuid& aGuid) {
1094 APZThreadUtils::AssertOnControllerThread();
1095
1096 if (RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid)) {
1097 apzc->StopAutoscroll();
1098 }
1099 }
1100
NotifyScrollbarDragInitiated(uint64_t aDragBlockId,const ScrollableLayerGuid & aGuid,ScrollDirection aDirection) const1101 void APZCTreeManager::NotifyScrollbarDragInitiated(
1102 uint64_t aDragBlockId, const ScrollableLayerGuid& aGuid,
1103 ScrollDirection aDirection) const {
1104 RefPtr<GeckoContentController> controller =
1105 GetContentController(aGuid.mLayersId);
1106 if (controller) {
1107 controller->NotifyAsyncScrollbarDragInitiated(aDragBlockId, aGuid.mScrollId,
1108 aDirection);
1109 }
1110 }
1111
NotifyScrollbarDragRejected(const ScrollableLayerGuid & aGuid) const1112 void APZCTreeManager::NotifyScrollbarDragRejected(
1113 const ScrollableLayerGuid& aGuid) const {
1114 RefPtr<GeckoContentController> controller =
1115 GetContentController(aGuid.mLayersId);
1116 if (controller) {
1117 controller->NotifyAsyncScrollbarDragRejected(aGuid.mScrollId);
1118 }
1119 }
1120
NotifyAutoscrollRejected(const ScrollableLayerGuid & aGuid) const1121 void APZCTreeManager::NotifyAutoscrollRejected(
1122 const ScrollableLayerGuid& aGuid) const {
1123 RefPtr<GeckoContentController> controller =
1124 GetContentController(aGuid.mLayersId);
1125 MOZ_ASSERT(controller);
1126 controller->NotifyAsyncAutoscrollRejected(aGuid.mScrollId);
1127 }
1128
1129 template <class ScrollNode>
SetHitTestData(HitTestingTreeNode * aNode,const ScrollNode & aLayer,const Maybe<ParentLayerIntRegion> & aClipRegion,const EventRegionsOverride & aOverrideFlags)1130 void SetHitTestData(HitTestingTreeNode* aNode, const ScrollNode& aLayer,
1131 const Maybe<ParentLayerIntRegion>& aClipRegion,
1132 const EventRegionsOverride& aOverrideFlags) {
1133 aNode->SetHitTestData(GetEventRegions(aLayer), aLayer.GetVisibleRegion(),
1134 aLayer.GetRemoteDocumentSize(),
1135 aLayer.GetTransformTyped(), aClipRegion, aOverrideFlags,
1136 aLayer.IsBackfaceHidden(),
1137 !!aLayer.IsAsyncZoomContainer());
1138 }
1139
1140 template <class ScrollNode>
PrepareNodeForLayer(const RecursiveMutexAutoLock & aProofOfTreeLock,const ScrollNode & aLayer,const FrameMetrics & aMetrics,LayersId aLayersId,const AncestorTransform & aAncestorTransform,HitTestingTreeNode * aParent,HitTestingTreeNode * aNextSibling,TreeBuildingState & aState)1141 HitTestingTreeNode* APZCTreeManager::PrepareNodeForLayer(
1142 const RecursiveMutexAutoLock& aProofOfTreeLock, const ScrollNode& aLayer,
1143 const FrameMetrics& aMetrics, LayersId aLayersId,
1144 const AncestorTransform& aAncestorTransform, HitTestingTreeNode* aParent,
1145 HitTestingTreeNode* aNextSibling, TreeBuildingState& aState) {
1146 bool needsApzc = true;
1147 if (!aMetrics.IsScrollable()) {
1148 needsApzc = false;
1149 }
1150
1151 // XXX: As a future optimization we can probably stick these things on the
1152 // TreeBuildingState, and update them as we change layers id during the
1153 // traversal
1154 RefPtr<GeckoContentController> geckoContentController;
1155 RefPtr<MetricsSharingController> crossProcessSharingController;
1156 CompositorBridgeParent::CallWithIndirectShadowTree(
1157 aLayersId, [&](LayerTreeState& lts) -> void {
1158 geckoContentController = lts.mController;
1159 crossProcessSharingController = lts.CrossProcessSharingController();
1160 });
1161
1162 if (!geckoContentController) {
1163 needsApzc = false;
1164 }
1165
1166 bool parentHasPerspective = aState.mParentHasPerspective.top();
1167
1168 if (Maybe<uint64_t> zoomAnimationId = aLayer.GetZoomAnimationId()) {
1169 aState.mZoomAnimationId = zoomAnimationId;
1170 }
1171
1172 RefPtr<HitTestingTreeNode> node = nullptr;
1173 if (!needsApzc) {
1174 // Note: if layer properties must be propagated to nodes, RecvUpdate in
1175 // LayerTransactionParent.cpp must ensure that APZ will be notified
1176 // when those properties change.
1177 node = RecycleOrCreateNode(aProofOfTreeLock, aState, nullptr, aLayersId);
1178 AttachNodeToTree(node, aParent, aNextSibling);
1179 SetHitTestData(node, aLayer,
1180 (!parentHasPerspective && aLayer.GetClipRect())
1181 ? Some(ParentLayerIntRegion(*aLayer.GetClipRect()))
1182 : Nothing(),
1183 aState.mOverrideFlags.top());
1184 node->SetScrollbarData(aLayer.GetScrollbarAnimationId(),
1185 aLayer.GetScrollbarData());
1186 node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId(),
1187 aLayer.GetFixedPositionSides(),
1188 aLayer.GetFixedPositionAnimationId());
1189 node->SetStickyPosData(aLayer.GetStickyScrollContainerId(),
1190 aLayer.GetStickyScrollRangeOuter(),
1191 aLayer.GetStickyScrollRangeInner(),
1192 aLayer.GetStickyPositionAnimationId());
1193 return node;
1194 }
1195
1196 AsyncPanZoomController* apzc = nullptr;
1197 // If we get here, aLayer is a scrollable layer and somebody
1198 // has registered a GeckoContentController for it, so we need to ensure
1199 // it has an APZC instance to manage its scrolling.
1200
1201 // aState.mApzcMap allows reusing the exact same APZC instance for different
1202 // layers with the same FrameMetrics data. This is needed because in some
1203 // cases content that is supposed to scroll together is split into multiple
1204 // layers because of e.g. non-scrolling content interleaved in z-index order.
1205 ScrollableLayerGuid guid(aLayersId, aMetrics.GetPresShellId(),
1206 aMetrics.GetScrollId());
1207 auto insertResult = aState.mApzcMap.insert(std::make_pair(
1208 guid,
1209 ApzcMapData{static_cast<AsyncPanZoomController*>(nullptr), Nothing()}));
1210 if (!insertResult.second) {
1211 apzc = insertResult.first->second.apzc;
1212 PrintAPZCInfo(aLayer, apzc);
1213 }
1214 APZCTM_LOG("Found APZC %p for layer %p with identifiers %" PRIx64 " %" PRId64
1215 "\n",
1216 apzc, aLayer.GetLayer(), uint64_t(guid.mLayersId), guid.mScrollId);
1217
1218 // If we haven't encountered a layer already with the same metrics, then we
1219 // need to do the full reuse-or-make-an-APZC algorithm, which is contained
1220 // inside the block below.
1221 if (apzc == nullptr) {
1222 apzc = aLayer.GetApzc();
1223
1224 // If the content represented by the scrollable layer has changed (which may
1225 // be possible because of DLBI heuristics) then we don't want to keep using
1226 // the same old APZC for the new content. Also, when reparenting a tab into
1227 // a new window a layer might get moved to a different layer tree with a
1228 // different APZCTreeManager. In these cases we don't want to reuse the same
1229 // APZC, so null it out so we run through the code to find another one or
1230 // create one.
1231 if (apzc && (!apzc->Matches(guid) || !apzc->HasTreeManager(this))) {
1232 apzc = nullptr;
1233 }
1234
1235 // See if we can find an APZC from the previous tree that matches the
1236 // ScrollableLayerGuid from this layer. If there is one, then we know that
1237 // the layout of the page changed causing the layer tree to be rebuilt, but
1238 // the underlying content for the APZC is still there somewhere. Therefore,
1239 // we want to find the APZC instance and continue using it here.
1240 //
1241 // We particularly want to find the primary-holder node from the previous
1242 // tree that matches, because we don't want that node to get destroyed. If
1243 // it does get destroyed, then the APZC will get destroyed along with it by
1244 // definition, but we want to keep that APZC around in the new tree.
1245 // We leave non-primary-holder nodes in the destroy list because we don't
1246 // care about those nodes getting destroyed.
1247 for (size_t i = 0; i < aState.mNodesToDestroy.Length(); i++) {
1248 RefPtr<HitTestingTreeNode> n = aState.mNodesToDestroy[i];
1249 if (n->IsPrimaryHolder() && n->GetApzc() && n->GetApzc()->Matches(guid)) {
1250 node = n;
1251 if (apzc != nullptr) {
1252 // If there is an APZC already then it should match the one from the
1253 // old primary-holder node
1254 MOZ_ASSERT(apzc == node->GetApzc());
1255 }
1256 apzc = node->GetApzc();
1257 break;
1258 }
1259 }
1260
1261 // The APZC we get off the layer may have been destroyed previously if the
1262 // layer was inactive or omitted from the layer tree for whatever reason
1263 // from a layers update. If it later comes back it will have a reference to
1264 // a destroyed APZC and so we need to throw that out and make a new one.
1265 bool newApzc = (apzc == nullptr || apzc->IsDestroyed());
1266 if (newApzc) {
1267 apzc = NewAPZCInstance(aLayersId, geckoContentController);
1268 apzc->SetCompositorController(aState.mCompositorController.get());
1269 if (crossProcessSharingController) {
1270 apzc->SetMetricsSharingController(crossProcessSharingController);
1271 } else {
1272 apzc->SetMetricsSharingController(
1273 aState.mInProcessSharingController.get());
1274 }
1275 MOZ_ASSERT(node == nullptr);
1276 node = new HitTestingTreeNode(apzc, true, aLayersId);
1277 } else {
1278 // If we are re-using a node for this layer clear the tree pointers
1279 // so that it doesn't continue pointing to nodes that might no longer
1280 // be in the tree. These pointers will get reset properly as we continue
1281 // building the tree. Also remove it from the set of nodes that are going
1282 // to be destroyed, because it's going to remain active.
1283 aState.mNodesToDestroy.RemoveElement(node);
1284 node->SetPrevSibling(nullptr);
1285 node->SetLastChild(nullptr);
1286 }
1287
1288 if (aMetrics.IsRootContent()) {
1289 apzc->SetZoomAnimationId(aState.mZoomAnimationId);
1290 aState.mZoomAnimationId = Nothing();
1291 }
1292
1293 APZCTM_LOG(
1294 "Using APZC %p for layer %p with identifiers %" PRIx64 " %" PRId64 "\n",
1295 apzc, aLayer.GetLayer(), uint64_t(aLayersId), aMetrics.GetScrollId());
1296
1297 apzc->NotifyLayersUpdated(aLayer.Metadata(), aState.mIsFirstPaint,
1298 aLayersId == aState.mOriginatingLayersId);
1299
1300 // Since this is the first time we are encountering an APZC with this guid,
1301 // the node holding it must be the primary holder. It may be newly-created
1302 // or not, depending on whether it went through the newApzc branch above.
1303 MOZ_ASSERT(node->IsPrimaryHolder() && node->GetApzc() &&
1304 node->GetApzc()->Matches(guid));
1305
1306 Maybe<ParentLayerIntRegion> clipRegion =
1307 parentHasPerspective ? Nothing() : ComputeClipRegion(aLayer);
1308 SetHitTestData(node, aLayer, clipRegion, aState.mOverrideFlags.top());
1309 apzc->SetAncestorTransform(aAncestorTransform);
1310
1311 PrintAPZCInfo(aLayer, apzc);
1312
1313 // Bind the APZC instance into the tree of APZCs
1314 AttachNodeToTree(node, aParent, aNextSibling);
1315
1316 // For testing, log the parent scroll id of every APZC that has a
1317 // parent. This allows test code to reconstruct the APZC tree.
1318 // Note that we currently only do this for APZCs in the layer tree
1319 // that originated the update, because the only identifying information
1320 // we are logging about APZCs is the scroll id, and otherwise we could
1321 // confuse APZCs from different layer trees with the same scroll id.
1322 if (aLayersId == aState.mOriginatingLayersId) {
1323 if (apzc->HasNoParentWithSameLayersId()) {
1324 aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
1325 "hasNoParentWithSameLayersId", true);
1326 } else {
1327 MOZ_ASSERT(apzc->GetParent());
1328 aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
1329 "parentScrollId",
1330 apzc->GetParent()->GetGuid().mScrollId);
1331 }
1332 if (aMetrics.IsRootContent()) {
1333 aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(), "isRootContent",
1334 true);
1335 }
1336 // Note that the async scroll offset is in ParentLayer pixels
1337 aState.mPaintLogger.LogTestData(
1338 aMetrics.GetScrollId(), "asyncScrollOffset",
1339 apzc->GetCurrentAsyncScrollOffset(
1340 AsyncPanZoomController::eForHitTesting));
1341 aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
1342 "hasAsyncKeyScrolled",
1343 apzc->TestHasAsyncKeyScrolled());
1344 }
1345
1346 if (newApzc) {
1347 auto it = mZoomConstraints.find(guid);
1348 if (it != mZoomConstraints.end()) {
1349 // We have a zoomconstraints for this guid, apply it.
1350 apzc->UpdateZoomConstraints(it->second);
1351 } else if (!apzc->HasNoParentWithSameLayersId()) {
1352 // This is a sub-APZC, so inherit the zoom constraints from its parent.
1353 // This ensures that if e.g. user-scalable=no was specified, none of the
1354 // APZCs for that subtree allow double-tap to zoom.
1355 apzc->UpdateZoomConstraints(apzc->GetParent()->GetZoomConstraints());
1356 }
1357 // Otherwise, this is the root of a layers id, but we didn't have a saved
1358 // zoom constraints. Leave it empty for now.
1359 }
1360
1361 // Add a guid -> APZC mapping for the newly created APZC.
1362 insertResult.first->second.apzc = apzc;
1363 } else {
1364 // We already built an APZC earlier in this tree walk, but we have another
1365 // layer now that will also be using that APZC. The hit-test region on the
1366 // APZC needs to be updated to deal with the new layer's hit region.
1367
1368 node = RecycleOrCreateNode(aProofOfTreeLock, aState, apzc, aLayersId);
1369 AttachNodeToTree(node, aParent, aNextSibling);
1370
1371 // Even though different layers associated with a given APZC may be at
1372 // different levels in the layer tree (e.g. one being an uncle of another),
1373 // we require from Layout that the CSS transforms up to their common
1374 // ancestor be roughly the same. There are cases in which the transforms
1375 // are not exactly the same, for example if the parent is container layer
1376 // for an opacity, and this container layer has a resolution-induced scale
1377 // as its base transform and a prescale that is supposed to undo that scale.
1378 // Due to floating point inaccuracies those transforms can end up not quite
1379 // canceling each other. That's why we're using a fuzzy comparison here
1380 // instead of an exact one.
1381 // In addition, two ancestor transforms are allowed to differ if one of
1382 // them contains a perspective transform component and the other does not.
1383 // This represents situations where some content in a scrollable frame
1384 // is subject to a perspective transform and other content does not.
1385 // In such cases, go with the one that does not include the perspective
1386 // component; the perspective transform is remembered and applied to the
1387 // children instead.
1388 if (!aAncestorTransform.CombinedTransform().FuzzyEqualsMultiplicative(
1389 apzc->GetAncestorTransform())) {
1390 typedef TreeBuildingState::DeferredTransformMap::value_type PairType;
1391 if (!aAncestorTransform.ContainsPerspectiveTransform() &&
1392 !apzc->AncestorTransformContainsPerspective()) {
1393 MOZ_ASSERT(false,
1394 "Two layers that scroll together have different ancestor "
1395 "transforms");
1396 } else if (!aAncestorTransform.ContainsPerspectiveTransform()) {
1397 aState.mPerspectiveTransformsDeferredToChildren.insert(
1398 PairType{apzc, apzc->GetAncestorTransformPerspective()});
1399 apzc->SetAncestorTransform(aAncestorTransform);
1400 } else {
1401 aState.mPerspectiveTransformsDeferredToChildren.insert(
1402 PairType{apzc, aAncestorTransform.GetPerspectiveTransform()});
1403 }
1404 }
1405
1406 Maybe<ParentLayerIntRegion> clipRegion =
1407 parentHasPerspective ? Nothing() : ComputeClipRegion(aLayer);
1408 SetHitTestData(node, aLayer, clipRegion, aState.mOverrideFlags.top());
1409 }
1410
1411 // Note: if layer properties must be propagated to nodes, RecvUpdate in
1412 // LayerTransactionParent.cpp must ensure that APZ will be notified
1413 // when those properties change.
1414 node->SetScrollbarData(aLayer.GetScrollbarAnimationId(),
1415 aLayer.GetScrollbarData());
1416 node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId(),
1417 aLayer.GetFixedPositionSides(),
1418 aLayer.GetFixedPositionAnimationId());
1419 node->SetStickyPosData(aLayer.GetStickyScrollContainerId(),
1420 aLayer.GetStickyScrollRangeOuter(),
1421 aLayer.GetStickyScrollRangeInner(),
1422 aLayer.GetStickyPositionAnimationId());
1423 return node;
1424 }
1425
1426 template <typename PanGestureOrScrollWheelInput>
WillHandleInput(const PanGestureOrScrollWheelInput & aPanInput)1427 static bool WillHandleInput(const PanGestureOrScrollWheelInput& aPanInput) {
1428 if (!XRE_IsParentProcess() || !NS_IsMainThread()) {
1429 return true;
1430 }
1431
1432 WidgetWheelEvent wheelEvent = aPanInput.ToWidgetWheelEvent(nullptr);
1433 return APZInputBridge::ActionForWheelEvent(&wheelEvent).isSome();
1434 }
1435
1436 /*static*/
FlushApzRepaints(LayersId aLayersId)1437 void APZCTreeManager::FlushApzRepaints(LayersId aLayersId) {
1438 // Previously, paints were throttled and therefore this method was used to
1439 // ensure any pending paints were flushed. Now, paints are flushed
1440 // immediately, so it is safe to simply send a notification now.
1441 APZCTM_LOG("Flushing repaints for layers id 0x%" PRIx64 "\n",
1442 uint64_t(aLayersId));
1443 RefPtr<GeckoContentController> controller = GetContentController(aLayersId);
1444 #ifndef MOZ_WIDGET_ANDROID
1445 // On Android, this code is run in production and may actually get a nullptr
1446 // controller here. On other platforms this code is test-only and should never
1447 // get a nullptr.
1448 MOZ_ASSERT(controller);
1449 #endif
1450 if (controller) {
1451 controller->DispatchToRepaintThread(NewRunnableMethod(
1452 "layers::GeckoContentController::NotifyFlushComplete", controller,
1453 &GeckoContentController::NotifyFlushComplete));
1454 }
1455 }
1456
MarkAsDetached(LayersId aLayersId)1457 void APZCTreeManager::MarkAsDetached(LayersId aLayersId) {
1458 RecursiveMutexAutoLock lock(mTreeLock);
1459 mDetachedLayersIds.insert(aLayersId);
1460 }
1461
ReceiveInputEvent(InputData & aEvent)1462 APZEventResult APZCTreeManager::ReceiveInputEvent(InputData& aEvent) {
1463 APZThreadUtils::AssertOnControllerThread();
1464 APZEventResult result;
1465
1466 // Use a RAII class for updating the focus sequence number of this event
1467 AutoFocusSequenceNumberSetter focusSetter(mFocusState, aEvent);
1468
1469 CompositorHitTestInfo hitResult = CompositorHitTestInvisibleToHit;
1470 switch (aEvent.mInputType) {
1471 case MULTITOUCH_INPUT: {
1472 MultiTouchInput& touchInput = aEvent.AsMultiTouchInput();
1473 result = ProcessTouchInput(touchInput);
1474 break;
1475 }
1476 case MOUSE_INPUT: {
1477 MouseInput& mouseInput = aEvent.AsMouseInput();
1478 mouseInput.mHandledByAPZ = true;
1479
1480 mCurrentMousePosition = mouseInput.mOrigin;
1481
1482 bool startsDrag = DragTracker::StartsDrag(mouseInput);
1483 if (startsDrag) {
1484 // If this is the start of a drag we need to unambiguously know if it's
1485 // going to land on a scrollbar or not. We can't apply an untransform
1486 // here without knowing that, so we need to ensure the untransform is
1487 // a no-op.
1488 FlushRepaintsToClearScreenToGeckoTransform();
1489 }
1490
1491 HitTestResult hit = GetTargetAPZC(mouseInput.mOrigin);
1492 aEvent.mLayersId = hit.mLayersId;
1493 hitResult = hit.mHitResult;
1494 bool hitScrollbar = (bool)hit.mScrollbarNode;
1495
1496 // When the mouse is outside the window we still want to handle dragging
1497 // but we won't find an APZC. Fallback to root APZC then.
1498 { // scope lock
1499 RecursiveMutexAutoLock lock(mTreeLock);
1500 if (!hit.mTargetApzc && mRootNode) {
1501 hit.mTargetApzc = mRootNode->GetApzc();
1502 }
1503 }
1504
1505 if (hit.mTargetApzc) {
1506 if (StaticPrefs::apz_test_logging_enabled() &&
1507 mouseInput.mType == MouseInput::MOUSE_HITTEST) {
1508 ScrollableLayerGuid guid = hit.mTargetApzc->GetGuid();
1509
1510 MutexAutoLock lock(mTestDataLock);
1511 auto it = mTestData.find(guid.mLayersId);
1512 MOZ_ASSERT(it != mTestData.end());
1513 it->second->RecordHitResult(mouseInput.mOrigin, hitResult,
1514 guid.mLayersId, guid.mScrollId);
1515 }
1516
1517 TargetConfirmationFlags confFlags{hitResult};
1518 bool apzDragEnabled = StaticPrefs::apz_drag_enabled();
1519 if (apzDragEnabled && hitScrollbar) {
1520 // If scrollbar dragging is enabled and we hit a scrollbar, wait
1521 // for the main-thread confirmation because it contains drag metrics
1522 // that we need.
1523 confFlags.mTargetConfirmed = false;
1524 }
1525 result.mStatus = mInputQueue->ReceiveInputEvent(
1526 hit.mTargetApzc, confFlags, mouseInput, &result.mInputBlockId);
1527
1528 // If we're starting an async scrollbar drag
1529 if (apzDragEnabled && startsDrag && hit.mScrollbarNode &&
1530 hit.mScrollbarNode->IsScrollThumbNode() &&
1531 hit.mScrollbarNode->GetScrollbarData().mThumbIsAsyncDraggable) {
1532 SetupScrollbarDrag(mouseInput, hit.mScrollbarNode,
1533 hit.mTargetApzc.get());
1534 }
1535
1536 if (result.mStatus == nsEventStatus_eConsumeDoDefault) {
1537 // This input event is part of a drag block, so whether or not it is
1538 // directed at a scrollbar depends on whether the drag block started
1539 // on a scrollbar.
1540 hitScrollbar = mInputQueue->IsDragOnScrollbar(hitScrollbar);
1541 }
1542
1543 // Update the out-parameters so they are what the caller expects.
1544 hit.mTargetApzc->GetGuid(&result.mTargetGuid);
1545 result.mTargetIsRoot = hit.TargetIsConfirmedRoot();
1546
1547 if (!hitScrollbar) {
1548 // The input was not targeted at a scrollbar, so we untransform it
1549 // like we do for other content. Scrollbars are "special" because they
1550 // have special handling in AsyncCompositionManager when resolution is
1551 // applied. TODO: we should find a better way to deal with this.
1552 ScreenToParentLayerMatrix4x4 transformToApzc =
1553 GetScreenToApzcTransform(hit.mTargetApzc);
1554 ParentLayerToScreenMatrix4x4 transformToGecko =
1555 GetApzcToGeckoTransform(hit.mTargetApzc);
1556 ScreenToScreenMatrix4x4 outTransform =
1557 transformToApzc * transformToGecko;
1558 Maybe<ScreenPoint> untransformedRefPoint =
1559 UntransformBy(outTransform, mouseInput.mOrigin);
1560 if (untransformedRefPoint) {
1561 mouseInput.mOrigin = *untransformedRefPoint;
1562 }
1563 } else {
1564 // Likewise, if the input was targeted at a scrollbar, we don't want
1565 // to apply the callback transform in the main thread, so we remove
1566 // the scrollid from the guid. We need to keep the layersId intact so
1567 // that the response from the child process doesn't get discarded.
1568 result.mTargetGuid.mScrollId = ScrollableLayerGuid::NULL_SCROLL_ID;
1569 }
1570 }
1571 break;
1572 }
1573 case SCROLLWHEEL_INPUT: {
1574 FlushRepaintsToClearScreenToGeckoTransform();
1575
1576 // Do this before early return for Fission hit testing.
1577 ScrollWheelInput& wheelInput = aEvent.AsScrollWheelInput();
1578 HitTestResult hit = GetTargetAPZC(wheelInput.mOrigin);
1579 aEvent.mLayersId = hit.mLayersId;
1580 hitResult = hit.mHitResult;
1581
1582 wheelInput.mHandledByAPZ = WillHandleInput(wheelInput);
1583 if (!wheelInput.mHandledByAPZ) {
1584 return result;
1585 }
1586
1587 if (hit.mTargetApzc) {
1588 MOZ_ASSERT(hitResult != CompositorHitTestInvisibleToHit);
1589
1590 if (wheelInput.mAPZAction == APZWheelAction::PinchZoom) {
1591 // The mousewheel may have hit a subframe, but we want to send the
1592 // pinch-zoom events to the root-content APZC.
1593 {
1594 RecursiveMutexAutoLock lock(mTreeLock);
1595 hit.mTargetApzc =
1596 FindRootContentApzcForLayersId(hit.mTargetApzc->GetLayersId());
1597 }
1598 if (hit.mTargetApzc) {
1599 SynthesizePinchGestureFromMouseWheel(wheelInput, hit.mTargetApzc);
1600 }
1601 result.mStatus = nsEventStatus_eConsumeNoDefault;
1602 return result;
1603 }
1604
1605 MOZ_ASSERT(wheelInput.mAPZAction == APZWheelAction::Scroll);
1606
1607 // For wheel events, the call to ReceiveInputEvent below may result in
1608 // scrolling, which changes the async transform. However, the event we
1609 // want to pass to gecko should be the pre-scroll event coordinates,
1610 // transformed into the gecko space. (pre-scroll because the mouse
1611 // cursor is stationary during wheel scrolling, unlike touchmove
1612 // events). Since we just flushed the pending repaints the transform to
1613 // gecko space should only consist of overscroll-cancelling transforms.
1614 ScreenToScreenMatrix4x4 transformToGecko =
1615 GetScreenToApzcTransform(hit.mTargetApzc) *
1616 GetApzcToGeckoTransform(hit.mTargetApzc);
1617 Maybe<ScreenPoint> untransformedOrigin =
1618 UntransformBy(transformToGecko, wheelInput.mOrigin);
1619
1620 if (!untransformedOrigin) {
1621 return result;
1622 }
1623
1624 result.mStatus = mInputQueue->ReceiveInputEvent(
1625 hit.mTargetApzc, TargetConfirmationFlags{hitResult}, wheelInput,
1626 &result.mInputBlockId);
1627
1628 // Update the out-parameters so they are what the caller expects.
1629 hit.mTargetApzc->GetGuid(&result.mTargetGuid);
1630 result.mTargetIsRoot = hit.TargetIsConfirmedRoot();
1631 wheelInput.mOrigin = *untransformedOrigin;
1632 }
1633 break;
1634 }
1635 case PANGESTURE_INPUT: {
1636 FlushRepaintsToClearScreenToGeckoTransform();
1637
1638 // Do this before early return for Fission hit testing.
1639 PanGestureInput& panInput = aEvent.AsPanGestureInput();
1640 HitTestResult hit = GetTargetAPZC(panInput.mPanStartPoint);
1641 aEvent.mLayersId = hit.mLayersId;
1642 hitResult = hit.mHitResult;
1643
1644 panInput.mHandledByAPZ = WillHandleInput(panInput);
1645 if (!panInput.mHandledByAPZ) {
1646 return result;
1647 }
1648
1649 // If/when we enable support for pan inputs off-main-thread, we'll need
1650 // to duplicate this EventStateManager code or something. See the call to
1651 // GetUserPrefsForWheelEvent in IAPZCTreeManager.cpp for why these fields
1652 // are stored separately.
1653 MOZ_ASSERT(NS_IsMainThread());
1654 WidgetWheelEvent wheelEvent = panInput.ToWidgetWheelEvent(nullptr);
1655 EventStateManager::GetUserPrefsForWheelEvent(
1656 &wheelEvent, &panInput.mUserDeltaMultiplierX,
1657 &panInput.mUserDeltaMultiplierY);
1658
1659 if (hit.mTargetApzc) {
1660 MOZ_ASSERT(hitResult != CompositorHitTestInvisibleToHit);
1661
1662 // For pan gesture events, the call to ReceiveInputEvent below may
1663 // result in scrolling, which changes the async transform. However, the
1664 // event we want to pass to gecko should be the pre-scroll event
1665 // coordinates, transformed into the gecko space. (pre-scroll because
1666 // the mouse cursor is stationary during pan gesture scrolling, unlike
1667 // touchmove events). Since we just flushed the pending repaints the
1668 // transform to gecko space should only consist of overscroll-cancelling
1669 // transforms.
1670 ScreenToScreenMatrix4x4 transformToGecko =
1671 GetScreenToApzcTransform(hit.mTargetApzc) *
1672 GetApzcToGeckoTransform(hit.mTargetApzc);
1673 Maybe<ScreenPoint> untransformedStartPoint =
1674 UntransformBy(transformToGecko, panInput.mPanStartPoint);
1675 Maybe<ScreenPoint> untransformedDisplacement =
1676 UntransformVector(transformToGecko, panInput.mPanDisplacement,
1677 panInput.mPanStartPoint);
1678
1679 if (!untransformedStartPoint || !untransformedDisplacement) {
1680 return result;
1681 }
1682
1683 result.mStatus = mInputQueue->ReceiveInputEvent(
1684 hit.mTargetApzc, TargetConfirmationFlags{hitResult}, panInput,
1685 &result.mInputBlockId);
1686
1687 // Update the out-parameters so they are what the caller expects.
1688 hit.mTargetApzc->GetGuid(&result.mTargetGuid);
1689 result.mTargetIsRoot = hit.TargetIsConfirmedRoot();
1690 panInput.mPanStartPoint = *untransformedStartPoint;
1691 panInput.mPanDisplacement = *untransformedDisplacement;
1692
1693 panInput.mOverscrollBehaviorAllowsSwipe =
1694 hit.mTargetApzc->OverscrollBehaviorAllowsSwipe();
1695 }
1696 break;
1697 }
1698 case PINCHGESTURE_INPUT: {
1699 PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
1700 HitTestResult hit = GetTargetAPZC(pinchInput.mFocusPoint);
1701 aEvent.mLayersId = hit.mLayersId;
1702 hitResult = hit.mHitResult;
1703
1704 // We always handle pinch gestures as pinch zooms.
1705 pinchInput.mHandledByAPZ = true;
1706
1707 if (hit.mTargetApzc) {
1708 MOZ_ASSERT(hitResult != CompositorHitTestInvisibleToHit);
1709
1710 if (!hit.mTargetApzc->IsRootContent()) {
1711 hit.mTargetApzc = FindZoomableApzc(hit.mTargetApzc);
1712 }
1713 }
1714
1715 if (hit.mTargetApzc) {
1716 ScreenToScreenMatrix4x4 outTransform =
1717 GetScreenToApzcTransform(hit.mTargetApzc) *
1718 GetApzcToGeckoTransform(hit.mTargetApzc);
1719 Maybe<ScreenPoint> untransformedFocusPoint =
1720 UntransformBy(outTransform, pinchInput.mFocusPoint);
1721
1722 if (!untransformedFocusPoint) {
1723 return result;
1724 }
1725
1726 result.mStatus = mInputQueue->ReceiveInputEvent(
1727 hit.mTargetApzc, TargetConfirmationFlags{hitResult}, pinchInput,
1728 &result.mInputBlockId);
1729
1730 // Update the out-parameters so they are what the caller expects.
1731 hit.mTargetApzc->GetGuid(&result.mTargetGuid);
1732 result.mTargetIsRoot = hit.TargetIsConfirmedRoot();
1733 pinchInput.mFocusPoint = *untransformedFocusPoint;
1734 }
1735 break;
1736 }
1737 case TAPGESTURE_INPUT: { // note: no one currently sends these
1738 TapGestureInput& tapInput = aEvent.AsTapGestureInput();
1739 HitTestResult hit = GetTargetAPZC(tapInput.mPoint);
1740 aEvent.mLayersId = hit.mLayersId;
1741 hitResult = hit.mHitResult;
1742
1743 if (hit.mTargetApzc) {
1744 MOZ_ASSERT(hitResult != CompositorHitTestInvisibleToHit);
1745
1746 ScreenToScreenMatrix4x4 outTransform =
1747 GetScreenToApzcTransform(hit.mTargetApzc) *
1748 GetApzcToGeckoTransform(hit.mTargetApzc);
1749 Maybe<ScreenIntPoint> untransformedPoint =
1750 UntransformBy(outTransform, tapInput.mPoint);
1751
1752 if (!untransformedPoint) {
1753 return result;
1754 }
1755
1756 result.mStatus = mInputQueue->ReceiveInputEvent(
1757 hit.mTargetApzc, TargetConfirmationFlags{hitResult}, tapInput,
1758 &result.mInputBlockId);
1759
1760 // Update the out-parameters so they are what the caller expects.
1761 hit.mTargetApzc->GetGuid(&result.mTargetGuid);
1762 result.mTargetIsRoot = hit.TargetIsConfirmedRoot();
1763 tapInput.mPoint = *untransformedPoint;
1764 }
1765 break;
1766 }
1767 case KEYBOARD_INPUT: {
1768 // Disable async keyboard scrolling when accessibility.browsewithcaret is
1769 // enabled
1770 if (!StaticPrefs::apz_keyboard_enabled_AtStartup() ||
1771 StaticPrefs::accessibility_browsewithcaret()) {
1772 APZ_KEY_LOG("Skipping key input from invalid prefs\n");
1773 return result;
1774 }
1775
1776 KeyboardInput& keyInput = aEvent.AsKeyboardInput();
1777
1778 // Try and find a matching shortcut for this keyboard input
1779 Maybe<KeyboardShortcut> shortcut = mKeyboardMap.FindMatch(keyInput);
1780
1781 if (!shortcut) {
1782 APZ_KEY_LOG("Skipping key input with no shortcut\n");
1783
1784 // If we don't have a shortcut for this key event, then we can keep our
1785 // focus only if we know there are no key event listeners for this
1786 // target
1787 if (mFocusState.CanIgnoreKeyboardShortcutMisses()) {
1788 focusSetter.MarkAsNonFocusChanging();
1789 }
1790 return result;
1791 }
1792
1793 // Check if this shortcut needs to be dispatched to content. Anything
1794 // matching this is assumed to be able to change focus.
1795 if (shortcut->mDispatchToContent) {
1796 APZ_KEY_LOG("Skipping key input with dispatch-to-content shortcut\n");
1797 return result;
1798 }
1799
1800 // We know we have an action to execute on whatever is the current focus
1801 // target
1802 const KeyboardScrollAction& action = shortcut->mAction;
1803
1804 // The current focus target depends on which direction the scroll is to
1805 // happen
1806 Maybe<ScrollableLayerGuid> targetGuid;
1807 switch (action.mType) {
1808 case KeyboardScrollAction::eScrollCharacter: {
1809 targetGuid = mFocusState.GetHorizontalTarget();
1810 break;
1811 }
1812 case KeyboardScrollAction::eScrollLine:
1813 case KeyboardScrollAction::eScrollPage:
1814 case KeyboardScrollAction::eScrollComplete: {
1815 targetGuid = mFocusState.GetVerticalTarget();
1816 break;
1817 }
1818 }
1819
1820 // If we don't have a scroll target then either we have a stale focus
1821 // target, the focused element has event listeners, or the focused element
1822 // doesn't have a layerized scroll frame. In any case we need to dispatch
1823 // to content.
1824 if (!targetGuid) {
1825 APZ_KEY_LOG("Skipping key input with no current focus target\n");
1826 return result;
1827 }
1828
1829 RefPtr<AsyncPanZoomController> targetApzc =
1830 GetTargetAPZC(targetGuid->mLayersId, targetGuid->mScrollId);
1831
1832 if (!targetApzc) {
1833 APZ_KEY_LOG("Skipping key input with focus target but no APZC\n");
1834 return result;
1835 }
1836
1837 // Attach the keyboard scroll action to the input event for processing
1838 // by the input queue.
1839 keyInput.mAction = action;
1840
1841 APZ_KEY_LOG("Dispatching key input with apzc=%p\n", targetApzc.get());
1842
1843 // Dispatch the event to the input queue.
1844 result.mStatus = mInputQueue->ReceiveInputEvent(
1845 targetApzc, TargetConfirmationFlags{true}, keyInput,
1846 &result.mInputBlockId);
1847
1848 // Any keyboard event that is dispatched to the input queue at this point
1849 // should have been consumed
1850 MOZ_ASSERT(result.mStatus == nsEventStatus_eConsumeDoDefault ||
1851 result.mStatus == nsEventStatus_eConsumeNoDefault);
1852
1853 keyInput.mHandledByAPZ = true;
1854 focusSetter.MarkAsNonFocusChanging();
1855
1856 break;
1857 }
1858 }
1859 return result;
1860 }
1861
ConvertToTouchBehavior(const CompositorHitTestInfo & info)1862 static TouchBehaviorFlags ConvertToTouchBehavior(
1863 const CompositorHitTestInfo& info) {
1864 TouchBehaviorFlags result = AllowedTouchBehavior::UNKNOWN;
1865 if (info == CompositorHitTestInvisibleToHit) {
1866 result = AllowedTouchBehavior::NONE;
1867 } else if (info.contains(CompositorHitTestFlags::eIrregularArea)) {
1868 // Note that eApzAwareListeners and eInactiveScrollframe are similar
1869 // to eIrregularArea in some respects, but are not relevant for the
1870 // purposes of this function, which deals specifically with touch-action.
1871 result = AllowedTouchBehavior::UNKNOWN;
1872 } else {
1873 result = AllowedTouchBehavior::VERTICAL_PAN |
1874 AllowedTouchBehavior::HORIZONTAL_PAN |
1875 AllowedTouchBehavior::PINCH_ZOOM |
1876 AllowedTouchBehavior::DOUBLE_TAP_ZOOM;
1877 if (info.contains(CompositorHitTestFlags::eTouchActionPanXDisabled)) {
1878 result &= ~AllowedTouchBehavior::HORIZONTAL_PAN;
1879 }
1880 if (info.contains(CompositorHitTestFlags::eTouchActionPanYDisabled)) {
1881 result &= ~AllowedTouchBehavior::VERTICAL_PAN;
1882 }
1883 if (info.contains(CompositorHitTestFlags::eTouchActionPinchZoomDisabled)) {
1884 result &= ~AllowedTouchBehavior::PINCH_ZOOM;
1885 }
1886 if (info.contains(
1887 CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled)) {
1888 result &= ~AllowedTouchBehavior::DOUBLE_TAP_ZOOM;
1889 }
1890 }
1891 return result;
1892 }
1893
GetTouchInputBlockAPZC(const MultiTouchInput & aEvent,nsTArray<TouchBehaviorFlags> * aOutTouchBehaviors)1894 APZCTreeManager::HitTestResult APZCTreeManager::GetTouchInputBlockAPZC(
1895 const MultiTouchInput& aEvent,
1896 nsTArray<TouchBehaviorFlags>* aOutTouchBehaviors) {
1897 HitTestResult hit;
1898 if (aEvent.mTouches.Length() == 0) {
1899 return hit;
1900 }
1901
1902 FlushRepaintsToClearScreenToGeckoTransform();
1903
1904 hit = GetTargetAPZC(aEvent.mTouches[0].mScreenPoint);
1905 // Don't set a layers id on multi-touch events.
1906 if (aEvent.mTouches.Length() != 1) {
1907 hit.mLayersId = LayersId{0};
1908 }
1909
1910 if (aOutTouchBehaviors) {
1911 aOutTouchBehaviors->AppendElement(ConvertToTouchBehavior(hit.mHitResult));
1912 }
1913 for (size_t i = 1; i < aEvent.mTouches.Length(); i++) {
1914 HitTestResult hit2 = GetTargetAPZC(aEvent.mTouches[i].mScreenPoint);
1915 if (aOutTouchBehaviors) {
1916 aOutTouchBehaviors->AppendElement(
1917 ConvertToTouchBehavior(hit2.mHitResult));
1918 }
1919 hit.mTargetApzc = GetZoomableTarget(hit.mTargetApzc, hit2.mTargetApzc);
1920 APZCTM_LOG("Using APZC %p as the root APZC for multi-touch\n",
1921 hit.mTargetApzc.get());
1922 // A multi-touch gesture will not be a scrollbar drag, even if the
1923 // first touch point happened to hit a scrollbar.
1924 hit.mScrollbarNode.Clear();
1925
1926 // XXX we should probably be combining the hit results from the different
1927 // touch points somehow, instead of just using the last one.
1928 hit.mHitResult = hit2.mHitResult;
1929 }
1930
1931 return hit;
1932 }
1933
1934 /**
1935 * Returns whether |aHitResult| *may* indicate that we hit a region with
1936 * APZ-aware listeners.
1937 */
MayHaveApzAwareListeners(CompositorHitTestInfo aHitResult)1938 bool MayHaveApzAwareListeners(CompositorHitTestInfo aHitResult) {
1939 // With WebRender, we can answer this accurately.
1940 if (gfx::gfxVars::UseWebRender()) {
1941 return aHitResult.contains(CompositorHitTestFlags::eApzAwareListeners);
1942 }
1943 // With non-WebRender, several hit results including eApzAwareListeners
1944 // get lumped together into the dispatch-to-content region. We err on
1945 // the side of false positives.
1946 return !((aHitResult & CompositorHitTestDispatchToContent).isEmpty());
1947 }
1948
ProcessTouchInput(MultiTouchInput & aInput)1949 APZEventResult APZCTreeManager::ProcessTouchInput(MultiTouchInput& aInput) {
1950 APZEventResult result; // mStatus == eIgnore
1951 aInput.mHandledByAPZ = true;
1952 nsTArray<TouchBehaviorFlags> touchBehaviors;
1953 HitTestingTreeNodeAutoLock hitScrollbarNode;
1954 if (aInput.mType == MultiTouchInput::MULTITOUCH_START) {
1955 // If we are panned into overscroll and a second finger goes down,
1956 // ignore that second touch point completely. The touch-start for it is
1957 // dropped completely; subsequent touch events until the touch-end for it
1958 // will have this touch point filtered out.
1959 // (By contrast, if we're in overscroll but not panning, such as after
1960 // putting two fingers down during an overscroll animation, we process the
1961 // second touch and proceed to pinch.)
1962 if (mTouchBlockHitResult.mTargetApzc &&
1963 mTouchBlockHitResult.mTargetApzc->IsInPanningState() &&
1964 BuildOverscrollHandoffChain(mTouchBlockHitResult.mTargetApzc)
1965 ->HasOverscrolledApzc()) {
1966 if (mRetainedTouchIdentifier == -1) {
1967 mRetainedTouchIdentifier =
1968 mTouchBlockHitResult.mTargetApzc->GetLastTouchIdentifier();
1969 }
1970 result.mStatus = nsEventStatus_eConsumeNoDefault;
1971 return result;
1972 }
1973
1974 HitTestResult hit = GetTouchInputBlockAPZC(aInput, &touchBehaviors);
1975 // Repopulate mTouchBlockHitResult with the fields we care about.
1976 mTouchBlockHitResult = HitTestResult();
1977 mTouchBlockHitResult.mTargetApzc = hit.mTargetApzc;
1978 mTouchBlockHitResult.mHitResult = hit.mHitResult;
1979 mTouchBlockHitResult.mFixedPosSides = hit.mFixedPosSides;
1980 if (hit.mLayersId.IsValid()) {
1981 // Check for validity because we won't get a layers id for multi-touch
1982 // events.
1983 aInput.mLayersId = hit.mLayersId;
1984 }
1985 hitScrollbarNode = std::move(hit.mScrollbarNode);
1986
1987 // Check if this event starts a scrollbar touch-drag. The conditions
1988 // checked are similar to the ones we check for MOUSE_INPUT starting
1989 // a scrollbar mouse-drag.
1990 mInScrollbarTouchDrag =
1991 StaticPrefs::apz_drag_enabled() &&
1992 StaticPrefs::apz_drag_touch_enabled() && hitScrollbarNode &&
1993 hitScrollbarNode->IsScrollThumbNode() &&
1994 hitScrollbarNode->GetScrollbarData().mThumbIsAsyncDraggable;
1995
1996 MOZ_ASSERT(touchBehaviors.Length() == aInput.mTouches.Length());
1997 for (size_t i = 0; i < touchBehaviors.Length(); i++) {
1998 APZCTM_LOG("Touch point has allowed behaviours 0x%02x\n",
1999 touchBehaviors[i]);
2000 if (touchBehaviors[i] == AllowedTouchBehavior::UNKNOWN) {
2001 // If there's any unknown items in the list, throw it out and we'll
2002 // wait for the main thread to send us a notification.
2003 touchBehaviors.Clear();
2004 break;
2005 }
2006 }
2007 } else if (mTouchBlockHitResult.mTargetApzc) {
2008 APZCTM_LOG("Re-using APZC %p as continuation of event block\n",
2009 mTouchBlockHitResult.mTargetApzc.get());
2010 }
2011
2012 if (mInScrollbarTouchDrag) {
2013 result = ProcessTouchInputForScrollbarDrag(aInput, hitScrollbarNode);
2014 } else {
2015 // If we receive a touch-cancel, it means all touches are finished, so we
2016 // can stop ignoring any that we were ignoring.
2017 if (aInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
2018 mRetainedTouchIdentifier = -1;
2019 }
2020
2021 // If we are currently ignoring any touch points, filter them out from the
2022 // set of touch points included in this event. Note that we modify aInput
2023 // itself, so that the touch points are also filtered out when the caller
2024 // passes the event on to content.
2025 if (mRetainedTouchIdentifier != -1) {
2026 for (size_t j = 0; j < aInput.mTouches.Length(); ++j) {
2027 if (aInput.mTouches[j].mIdentifier != mRetainedTouchIdentifier) {
2028 aInput.mTouches.RemoveElementAt(j);
2029 if (!touchBehaviors.IsEmpty()) {
2030 MOZ_ASSERT(touchBehaviors.Length() > j);
2031 touchBehaviors.RemoveElementAt(j);
2032 }
2033 --j;
2034 }
2035 }
2036 if (aInput.mTouches.IsEmpty()) {
2037 result.mStatus = nsEventStatus_eConsumeNoDefault;
2038 return result;
2039 }
2040 }
2041
2042 if (mTouchBlockHitResult.mTargetApzc) {
2043 MOZ_ASSERT(mTouchBlockHitResult.mHitResult !=
2044 CompositorHitTestInvisibleToHit);
2045
2046 mTouchBlockHitResult.mTargetApzc->GetGuid(&result.mTargetGuid);
2047 result.mTargetIsRoot = mTouchBlockHitResult.TargetIsConfirmedRoot();
2048 result.mStatus = mInputQueue->ReceiveInputEvent(
2049 mTouchBlockHitResult.mTargetApzc,
2050 TargetConfirmationFlags{mTouchBlockHitResult.mHitResult}, aInput,
2051 &result.mInputBlockId,
2052 touchBehaviors.IsEmpty() ? Nothing()
2053 : Some(std::move(touchBehaviors)));
2054 result.mHitRegionWithApzAwareListeners =
2055 MayHaveApzAwareListeners(mTouchBlockHitResult.mHitResult);
2056
2057 // For computing the event to pass back to Gecko, use up-to-date
2058 // transforms (i.e. not anything cached in an input block). This ensures
2059 // that transformToApzc and transformToGecko are in sync.
2060 // Note: we are not using ConvertToGecko() here, because we don't
2061 // want to multiply transformToApzc and transformToGecko once
2062 // for each touch point.
2063 ScreenToParentLayerMatrix4x4 transformToApzc =
2064 GetScreenToApzcTransform(mTouchBlockHitResult.mTargetApzc);
2065 ParentLayerToScreenMatrix4x4 transformToGecko =
2066 GetApzcToGeckoTransform(mTouchBlockHitResult.mTargetApzc);
2067 ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko;
2068
2069 for (size_t i = 0; i < aInput.mTouches.Length(); i++) {
2070 SingleTouchData& touchData = aInput.mTouches[i];
2071 Maybe<ScreenIntPoint> untransformedScreenPoint =
2072 UntransformBy(outTransform, touchData.mScreenPoint);
2073 if (!untransformedScreenPoint) {
2074 result.mStatus = nsEventStatus_eIgnore;
2075 return result;
2076 }
2077 touchData.mScreenPoint = *untransformedScreenPoint;
2078 if (mTouchBlockHitResult.mFixedPosSides != SideBits::eNone) {
2079 MutexAutoLock lock(mMapLock);
2080 touchData.mScreenPoint -=
2081 RoundedToInt(AsyncCompositionManager::ComputeFixedMarginsOffset(
2082 GetCompositorFixedLayerMargins(lock),
2083 mTouchBlockHitResult.mFixedPosSides,
2084 mGeckoFixedLayerMargins));
2085 }
2086 }
2087 }
2088 }
2089
2090 mTouchCounter.Update(aInput);
2091
2092 // If it's the end of the touch sequence then clear out variables so we
2093 // don't keep dangling references and leak things.
2094 if (mTouchCounter.GetActiveTouchCount() == 0) {
2095 mTouchBlockHitResult = HitTestResult();
2096 mRetainedTouchIdentifier = -1;
2097 mInScrollbarTouchDrag = false;
2098 }
2099
2100 return result;
2101 }
2102
MultiTouchTypeToMouseType(MultiTouchInput::MultiTouchType aType)2103 static MouseInput::MouseType MultiTouchTypeToMouseType(
2104 MultiTouchInput::MultiTouchType aType) {
2105 switch (aType) {
2106 case MultiTouchInput::MULTITOUCH_START:
2107 return MouseInput::MOUSE_DOWN;
2108 case MultiTouchInput::MULTITOUCH_MOVE:
2109 return MouseInput::MOUSE_MOVE;
2110 case MultiTouchInput::MULTITOUCH_END:
2111 case MultiTouchInput::MULTITOUCH_CANCEL:
2112 return MouseInput::MOUSE_UP;
2113 }
2114 MOZ_ASSERT_UNREACHABLE("Invalid multi-touch type");
2115 return MouseInput::MOUSE_NONE;
2116 }
2117
ProcessTouchInputForScrollbarDrag(MultiTouchInput & aTouchInput,const HitTestingTreeNodeAutoLock & aScrollThumbNode)2118 APZEventResult APZCTreeManager::ProcessTouchInputForScrollbarDrag(
2119 MultiTouchInput& aTouchInput,
2120 const HitTestingTreeNodeAutoLock& aScrollThumbNode) {
2121 MOZ_ASSERT(mRetainedTouchIdentifier == -1);
2122 MOZ_ASSERT(mTouchBlockHitResult.mTargetApzc);
2123 MOZ_ASSERT(aTouchInput.mTouches.Length() == 1);
2124
2125 // Synthesize a mouse event based on the touch event, so that we can
2126 // reuse code in InputQueue and APZC for handling scrollbar mouse-drags.
2127 MouseInput mouseInput{MultiTouchTypeToMouseType(aTouchInput.mType),
2128 MouseInput::LEFT_BUTTON,
2129 dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH,
2130 MouseButtonsFlag::eLeftFlag,
2131 aTouchInput.mTouches[0].mScreenPoint,
2132 aTouchInput.mTime,
2133 aTouchInput.mTimeStamp,
2134 aTouchInput.modifiers};
2135 mouseInput.mHandledByAPZ = true;
2136
2137 // The value of |targetConfirmed| passed to InputQueue::ReceiveInputEvent()
2138 // only matters for the first event, which creates the drag block. For
2139 // that event, the correct value is false, since the drag block will, at the
2140 // earliest, be confirmed in the subsequent SetupScrollbarDrag() call.
2141 TargetConfirmationFlags targetConfirmed{false};
2142
2143 APZEventResult result;
2144 result.mStatus = mInputQueue->ReceiveInputEvent(
2145 mTouchBlockHitResult.mTargetApzc, targetConfirmed, mouseInput,
2146 &result.mInputBlockId);
2147
2148 // |aScrollThumbNode| is non-null iff. this is the event that starts the drag.
2149 // If so, set up the drag.
2150 if (aScrollThumbNode) {
2151 SetupScrollbarDrag(mouseInput, aScrollThumbNode,
2152 mTouchBlockHitResult.mTargetApzc.get());
2153 }
2154
2155 mTouchBlockHitResult.mTargetApzc->GetGuid(&result.mTargetGuid);
2156 result.mTargetIsRoot = mTouchBlockHitResult.TargetIsConfirmedRoot();
2157
2158 // Since the input was targeted at a scrollbar:
2159 // - The original touch event (which will be sent on to content) will
2160 // not be untransformed.
2161 // - We don't want to apply the callback transform in the main thread,
2162 // so we remove the scrollid from the guid.
2163 // Both of these match the behaviour of mouse events that target a scrollbar;
2164 // see the code for handling mouse events in ReceiveInputEvent() for
2165 // additional explanation.
2166 result.mTargetGuid.mScrollId = ScrollableLayerGuid::NULL_SCROLL_ID;
2167
2168 return result;
2169 }
2170
SetupScrollbarDrag(MouseInput & aMouseInput,const HitTestingTreeNodeAutoLock & aScrollThumbNode,AsyncPanZoomController * aApzc)2171 void APZCTreeManager::SetupScrollbarDrag(
2172 MouseInput& aMouseInput, const HitTestingTreeNodeAutoLock& aScrollThumbNode,
2173 AsyncPanZoomController* aApzc) {
2174 DragBlockState* dragBlock = mInputQueue->GetCurrentDragBlock();
2175 if (!dragBlock) {
2176 return;
2177 }
2178
2179 const ScrollbarData& thumbData = aScrollThumbNode->GetScrollbarData();
2180 MOZ_ASSERT(thumbData.mDirection.isSome());
2181
2182 // Record the thumb's position at the start of the drag.
2183 // We snap back to this position if, during the drag, the mouse
2184 // gets sufficiently far away from the scrollbar.
2185 dragBlock->SetInitialThumbPos(thumbData.mThumbStart);
2186
2187 // Under some conditions, we can confirm the drag block right away.
2188 // Otherwise, we have to wait for a main-thread confirmation.
2189 if (StaticPrefs::apz_drag_initial_enabled() &&
2190 // check that the scrollbar's target scroll frame is layerized
2191 aScrollThumbNode->GetScrollTargetId() == aApzc->GetGuid().mScrollId &&
2192 !aApzc->IsScrollInfoLayer()) {
2193 uint64_t dragBlockId = dragBlock->GetBlockId();
2194 // AsyncPanZoomController::HandleInputEvent() will call
2195 // TransformToLocal() on the event, but we need its mLocalOrigin now
2196 // to compute a drag start offset for the AsyncDragMetrics.
2197 aMouseInput.TransformToLocal(aApzc->GetTransformToThis());
2198 CSSCoord dragStart =
2199 aApzc->ConvertScrollbarPoint(aMouseInput.mLocalOrigin, thumbData);
2200 // ConvertScrollbarPoint() got the drag start offset relative to
2201 // the scroll track. Now get it relative to the thumb.
2202 // ScrollThumbData::mThumbStart stores the offset of the thumb
2203 // relative to the scroll track at the time of the last paint.
2204 // Since that paint, the thumb may have acquired an async transform
2205 // due to async scrolling, so look that up and apply it.
2206 LayerToParentLayerMatrix4x4 thumbTransform;
2207 {
2208 RecursiveMutexAutoLock lock(mTreeLock);
2209 thumbTransform = ComputeTransformForNode(aScrollThumbNode.Get(lock));
2210 }
2211 // Only consider the translation, since we do not support both
2212 // zooming and scrollbar dragging on any platform.
2213 CSSCoord thumbStart =
2214 thumbData.mThumbStart +
2215 ((*thumbData.mDirection == ScrollDirection::eHorizontal)
2216 ? thumbTransform._41
2217 : thumbTransform._42);
2218 dragStart -= thumbStart;
2219
2220 // Content can't prevent scrollbar dragging with preventDefault(),
2221 // so we don't need to wait for a content response. It's important
2222 // to do this before calling ConfirmDragBlock() since that can
2223 // potentially process and consume the block.
2224 dragBlock->SetContentResponse(false);
2225
2226 NotifyScrollbarDragInitiated(dragBlockId, aApzc->GetGuid(),
2227 *thumbData.mDirection);
2228
2229 mInputQueue->ConfirmDragBlock(
2230 dragBlockId, aApzc,
2231 AsyncDragMetrics(aApzc->GetGuid().mScrollId,
2232 aApzc->GetGuid().mPresShellId, dragBlockId, dragStart,
2233 *thumbData.mDirection));
2234 }
2235 }
2236
SynthesizePinchGestureFromMouseWheel(const ScrollWheelInput & aWheelInput,const RefPtr<AsyncPanZoomController> & aTarget)2237 void APZCTreeManager::SynthesizePinchGestureFromMouseWheel(
2238 const ScrollWheelInput& aWheelInput,
2239 const RefPtr<AsyncPanZoomController>& aTarget) {
2240 MOZ_ASSERT(aTarget);
2241
2242 ScreenPoint focusPoint = aWheelInput.mOrigin;
2243
2244 // Compute span values based on the wheel delta.
2245 ScreenCoord oldSpan = 100;
2246 ScreenCoord newSpan = oldSpan + aWheelInput.mDeltaY;
2247
2248 // There's no ambiguity as to the target for pinch gesture events.
2249 TargetConfirmationFlags confFlags{true};
2250
2251 PinchGestureInput pinchStart{PinchGestureInput::PINCHGESTURE_START,
2252 aWheelInput.mTime,
2253 aWheelInput.mTimeStamp,
2254 ExternalPoint(0, 0),
2255 focusPoint,
2256 oldSpan,
2257 oldSpan,
2258 aWheelInput.modifiers};
2259 PinchGestureInput pinchScale1{PinchGestureInput::PINCHGESTURE_SCALE,
2260 aWheelInput.mTime,
2261 aWheelInput.mTimeStamp,
2262 ExternalPoint(0, 0),
2263 focusPoint,
2264 oldSpan,
2265 oldSpan,
2266 aWheelInput.modifiers};
2267 PinchGestureInput pinchScale2{PinchGestureInput::PINCHGESTURE_SCALE,
2268 aWheelInput.mTime,
2269 aWheelInput.mTimeStamp,
2270 ExternalPoint(0, 0),
2271 focusPoint,
2272 oldSpan,
2273 newSpan,
2274 aWheelInput.modifiers};
2275 PinchGestureInput pinchEnd{PinchGestureInput::PINCHGESTURE_END,
2276 aWheelInput.mTime,
2277 aWheelInput.mTimeStamp,
2278 ExternalPoint(0, 0),
2279 focusPoint,
2280 newSpan,
2281 newSpan,
2282 aWheelInput.modifiers};
2283
2284 mInputQueue->ReceiveInputEvent(aTarget, confFlags, pinchStart, nullptr);
2285 mInputQueue->ReceiveInputEvent(aTarget, confFlags, pinchScale1, nullptr);
2286 mInputQueue->ReceiveInputEvent(aTarget, confFlags, pinchScale2, nullptr);
2287 mInputQueue->ReceiveInputEvent(aTarget, confFlags, pinchEnd, nullptr);
2288 }
2289
UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint,EventMessage aEventMessage)2290 void APZCTreeManager::UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint,
2291 EventMessage aEventMessage) {
2292 APZThreadUtils::AssertOnControllerThread();
2293
2294 WheelBlockState* txn = mInputQueue->GetActiveWheelTransaction();
2295 if (!txn) {
2296 return;
2297 }
2298
2299 // If the transaction has simply timed out, we don't need to do anything
2300 // else.
2301 if (txn->MaybeTimeout(TimeStamp::Now())) {
2302 return;
2303 }
2304
2305 switch (aEventMessage) {
2306 case eMouseMove:
2307 case eDragOver: {
2308 ScreenIntPoint point = ViewAs<ScreenPixel>(
2309 aRefPoint,
2310 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
2311
2312 txn->OnMouseMove(point);
2313
2314 return;
2315 }
2316 case eKeyPress:
2317 case eKeyUp:
2318 case eKeyDown:
2319 case eMouseUp:
2320 case eMouseDown:
2321 case eMouseDoubleClick:
2322 case eMouseAuxClick:
2323 case eMouseClick:
2324 case eContextMenu:
2325 case eDrop:
2326 txn->EndTransaction();
2327 return;
2328 default:
2329 break;
2330 }
2331 }
2332
ProcessUnhandledEvent(LayoutDeviceIntPoint * aRefPoint,ScrollableLayerGuid * aOutTargetGuid,uint64_t * aOutFocusSequenceNumber,LayersId * aOutLayersId)2333 void APZCTreeManager::ProcessUnhandledEvent(LayoutDeviceIntPoint* aRefPoint,
2334 ScrollableLayerGuid* aOutTargetGuid,
2335 uint64_t* aOutFocusSequenceNumber,
2336 LayersId* aOutLayersId) {
2337 APZThreadUtils::AssertOnControllerThread();
2338
2339 // Transform the aRefPoint.
2340 // If the event hits an overscrolled APZC, instruct the caller to ignore it.
2341 PixelCastJustification LDIsScreen =
2342 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent;
2343 ScreenIntPoint refPointAsScreen = ViewAs<ScreenPixel>(*aRefPoint, LDIsScreen);
2344 HitTestResult hit = GetTargetAPZC(refPointAsScreen);
2345 if (aOutLayersId) {
2346 *aOutLayersId = hit.mLayersId;
2347 }
2348 if (hit.mTargetApzc) {
2349 MOZ_ASSERT(hit.mHitResult != CompositorHitTestInvisibleToHit);
2350 hit.mTargetApzc->GetGuid(aOutTargetGuid);
2351 ScreenToParentLayerMatrix4x4 transformToApzc =
2352 GetScreenToApzcTransform(hit.mTargetApzc);
2353 ParentLayerToScreenMatrix4x4 transformToGecko =
2354 GetApzcToGeckoTransform(hit.mTargetApzc);
2355 ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko;
2356 Maybe<ScreenIntPoint> untransformedRefPoint =
2357 UntransformBy(outTransform, refPointAsScreen);
2358 if (untransformedRefPoint) {
2359 *aRefPoint =
2360 ViewAs<LayoutDevicePixel>(*untransformedRefPoint, LDIsScreen);
2361 }
2362 }
2363
2364 // Update the focus sequence number and attach it to the event
2365 mFocusState.ReceiveFocusChangingEvent();
2366 *aOutFocusSequenceNumber = mFocusState.LastAPZProcessedEvent();
2367 }
2368
SetKeyboardMap(const KeyboardMap & aKeyboardMap)2369 void APZCTreeManager::SetKeyboardMap(const KeyboardMap& aKeyboardMap) {
2370 APZThreadUtils::AssertOnControllerThread();
2371
2372 mKeyboardMap = aKeyboardMap;
2373 }
2374
ZoomToRect(const ScrollableLayerGuid & aGuid,const CSSRect & aRect,const uint32_t aFlags)2375 void APZCTreeManager::ZoomToRect(const ScrollableLayerGuid& aGuid,
2376 const CSSRect& aRect, const uint32_t aFlags) {
2377 // We could probably move this to run on the updater thread if needed, but
2378 // either way we should restrict it to a single thread. For now let's use the
2379 // controller thread.
2380 APZThreadUtils::AssertOnControllerThread();
2381
2382 RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
2383 if (apzc) {
2384 apzc->ZoomToRect(aRect, aFlags);
2385 }
2386 }
2387
ContentReceivedInputBlock(uint64_t aInputBlockId,bool aPreventDefault)2388 void APZCTreeManager::ContentReceivedInputBlock(uint64_t aInputBlockId,
2389 bool aPreventDefault) {
2390 APZThreadUtils::AssertOnControllerThread();
2391
2392 mInputQueue->ContentReceivedInputBlock(aInputBlockId, aPreventDefault);
2393 }
2394
SetTargetAPZC(uint64_t aInputBlockId,const nsTArray<ScrollableLayerGuid> & aTargets)2395 void APZCTreeManager::SetTargetAPZC(
2396 uint64_t aInputBlockId, const nsTArray<ScrollableLayerGuid>& aTargets) {
2397 APZThreadUtils::AssertOnControllerThread();
2398
2399 RefPtr<AsyncPanZoomController> target = nullptr;
2400 if (aTargets.Length() > 0) {
2401 target = GetTargetAPZC(aTargets[0]);
2402 }
2403 for (size_t i = 1; i < aTargets.Length(); i++) {
2404 RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aTargets[i]);
2405 target = GetZoomableTarget(target, apzc);
2406 }
2407 if (InputBlockState* block = mInputQueue->GetBlockForId(aInputBlockId)) {
2408 if (block->AsPinchGestureBlock() && aTargets.Length() == 1) {
2409 target = FindZoomableApzc(target);
2410 }
2411 }
2412 mInputQueue->SetConfirmedTargetApzc(aInputBlockId, target);
2413 }
2414
UpdateZoomConstraints(const ScrollableLayerGuid & aGuid,const Maybe<ZoomConstraints> & aConstraints)2415 void APZCTreeManager::UpdateZoomConstraints(
2416 const ScrollableLayerGuid& aGuid,
2417 const Maybe<ZoomConstraints>& aConstraints) {
2418 if (!GetUpdater()->IsUpdaterThread()) {
2419 // This can happen if we're in the UI process and got a call directly from
2420 // nsBaseWidget or from a content process over PAPZCTreeManager. In that
2421 // case we get this call on the compositor thread, which may be different
2422 // from the updater thread. It can also happen in the GPU process if that is
2423 // enabled, since the call will go over PAPZCTreeManager and arrive on the
2424 // compositor thread in the GPU process.
2425 GetUpdater()->RunOnUpdaterThread(
2426 aGuid.mLayersId,
2427 NewRunnableMethod<ScrollableLayerGuid, Maybe<ZoomConstraints>>(
2428 "APZCTreeManager::UpdateZoomConstraints", this,
2429 &APZCTreeManager::UpdateZoomConstraints, aGuid, aConstraints));
2430 return;
2431 }
2432
2433 AssertOnUpdaterThread();
2434
2435 RecursiveMutexAutoLock lock(mTreeLock);
2436 RefPtr<HitTestingTreeNode> node = GetTargetNode(aGuid, nullptr);
2437 MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC
2438
2439 // Propagate the zoom constraints down to the subtree, stopping at APZCs
2440 // which have their own zoom constraints or are in a different layers id.
2441 if (aConstraints) {
2442 APZCTM_LOG("Recording constraints %s for guid %s\n",
2443 Stringify(aConstraints.value()).c_str(),
2444 Stringify(aGuid).c_str());
2445 mZoomConstraints[aGuid] = aConstraints.ref();
2446 } else {
2447 APZCTM_LOG("Removing constraints for guid %s\n", Stringify(aGuid).c_str());
2448 mZoomConstraints.erase(aGuid);
2449 }
2450 if (node && aConstraints) {
2451 ForEachNode<ReverseIterator>(
2452 node.get(), [&aConstraints, &node, this](HitTestingTreeNode* aNode) {
2453 if (aNode != node) {
2454 if (AsyncPanZoomController* childApzc = aNode->GetApzc()) {
2455 // We can have subtrees with their own zoom constraints or
2456 // separate layers id - leave these alone.
2457 if (childApzc->HasNoParentWithSameLayersId() ||
2458 this->mZoomConstraints.find(childApzc->GetGuid()) !=
2459 this->mZoomConstraints.end()) {
2460 return TraversalFlag::Skip;
2461 }
2462 }
2463 }
2464 if (aNode->IsPrimaryHolder()) {
2465 MOZ_ASSERT(aNode->GetApzc());
2466 aNode->GetApzc()->UpdateZoomConstraints(aConstraints.ref());
2467 }
2468 return TraversalFlag::Continue;
2469 });
2470 }
2471 }
2472
FlushRepaintsToClearScreenToGeckoTransform()2473 void APZCTreeManager::FlushRepaintsToClearScreenToGeckoTransform() {
2474 // As the name implies, we flush repaint requests for the entire APZ tree in
2475 // order to clear the screen-to-gecko transform (aka the "untransform" applied
2476 // to incoming input events before they can be passed on to Gecko).
2477 //
2478 // The primary reason we do this is to avoid the problem where input events,
2479 // after being untransformed, end up hit-testing differently in Gecko. This
2480 // might happen in cases where the input event lands on content that is async-
2481 // scrolled into view, but Gecko still thinks it is out of view given the
2482 // visible area of a scrollframe.
2483 //
2484 // Another reason we want to clear the untransform is that if our APZ hit-test
2485 // hits a dispatch-to-content region then that's an ambiguous result and we
2486 // need to ask Gecko what actually got hit. In order to do this we need to
2487 // untransform the input event into Gecko space - but to do that we need to
2488 // know which APZC got hit! This leads to a circular dependency; the only way
2489 // to get out of it is to make sure that the untransform for all the possible
2490 // matched APZCs is the same. It is simplest to ensure that by flushing the
2491 // pending repaint requests, which makes all of the untransforms empty (and
2492 // therefore equal).
2493 RecursiveMutexAutoLock lock(mTreeLock);
2494
2495 ForEachNode<ReverseIterator>(mRootNode.get(), [](HitTestingTreeNode* aNode) {
2496 if (aNode->IsPrimaryHolder()) {
2497 MOZ_ASSERT(aNode->GetApzc());
2498 aNode->GetApzc()->FlushRepaintForNewInputBlock();
2499 }
2500 });
2501 }
2502
ClearTree()2503 void APZCTreeManager::ClearTree() {
2504 AssertOnUpdaterThread();
2505
2506 // Ensure that no references to APZCs are alive in any lingering input
2507 // blocks. This breaks cycles from InputBlockState::mTargetApzc back to
2508 // the InputQueue.
2509 APZThreadUtils::RunOnControllerThread(NewRunnableMethod(
2510 "layers::InputQueue::Clear", mInputQueue, &InputQueue::Clear));
2511
2512 RecursiveMutexAutoLock lock(mTreeLock);
2513
2514 // Collect the nodes into a list, and then destroy each one.
2515 // We can't destroy them as we collect them, because ForEachNode()
2516 // does a pre-order traversal of the tree, and Destroy() nulls out
2517 // the fields needed to reach the children of the node.
2518 nsTArray<RefPtr<HitTestingTreeNode>> nodesToDestroy;
2519 ForEachNode<ReverseIterator>(mRootNode.get(),
2520 [&nodesToDestroy](HitTestingTreeNode* aNode) {
2521 nodesToDestroy.AppendElement(aNode);
2522 });
2523
2524 for (size_t i = 0; i < nodesToDestroy.Length(); i++) {
2525 nodesToDestroy[i]->Destroy();
2526 }
2527 mRootNode = nullptr;
2528
2529 RefPtr<APZCTreeManager> self(this);
2530 NS_DispatchToMainThread(
2531 NS_NewRunnableFunction("layers::APZCTreeManager::ClearTree", [self] {
2532 self->mFlushObserver->Unregister();
2533 self->mFlushObserver = nullptr;
2534 }));
2535 }
2536
GetRootNode() const2537 RefPtr<HitTestingTreeNode> APZCTreeManager::GetRootNode() const {
2538 RecursiveMutexAutoLock lock(mTreeLock);
2539 return mRootNode;
2540 }
2541
2542 /**
2543 * Transform a displacement from the ParentLayer coordinates of a source APZC
2544 * to the ParentLayer coordinates of a target APZC.
2545 * @param aTreeManager the tree manager for the APZC tree containing |aSource|
2546 * and |aTarget|
2547 * @param aSource the source APZC
2548 * @param aTarget the target APZC
2549 * @param aStartPoint the start point of the displacement
2550 * @param aEndPoint the end point of the displacement
2551 * @return true on success, false if aStartPoint or aEndPoint cannot be
2552 * transformed into target's coordinate space
2553 */
TransformDisplacement(APZCTreeManager * aTreeManager,AsyncPanZoomController * aSource,AsyncPanZoomController * aTarget,ParentLayerPoint & aStartPoint,ParentLayerPoint & aEndPoint)2554 static bool TransformDisplacement(APZCTreeManager* aTreeManager,
2555 AsyncPanZoomController* aSource,
2556 AsyncPanZoomController* aTarget,
2557 ParentLayerPoint& aStartPoint,
2558 ParentLayerPoint& aEndPoint) {
2559 if (aSource == aTarget) {
2560 return true;
2561 }
2562
2563 // Convert start and end points to Screen coordinates.
2564 ParentLayerToScreenMatrix4x4 untransformToApzc =
2565 aTreeManager->GetScreenToApzcTransform(aSource).Inverse();
2566 ScreenPoint screenStart = TransformBy(untransformToApzc, aStartPoint);
2567 ScreenPoint screenEnd = TransformBy(untransformToApzc, aEndPoint);
2568
2569 // Convert start and end points to aTarget's ParentLayer coordinates.
2570 ScreenToParentLayerMatrix4x4 transformToApzc =
2571 aTreeManager->GetScreenToApzcTransform(aTarget);
2572 Maybe<ParentLayerPoint> startPoint =
2573 UntransformBy(transformToApzc, screenStart);
2574 Maybe<ParentLayerPoint> endPoint = UntransformBy(transformToApzc, screenEnd);
2575 if (!startPoint || !endPoint) {
2576 return false;
2577 }
2578 aEndPoint = *endPoint;
2579 aStartPoint = *startPoint;
2580
2581 return true;
2582 }
2583
DispatchScroll(AsyncPanZoomController * aPrev,ParentLayerPoint & aStartPoint,ParentLayerPoint & aEndPoint,OverscrollHandoffState & aOverscrollHandoffState)2584 bool APZCTreeManager::DispatchScroll(
2585 AsyncPanZoomController* aPrev, ParentLayerPoint& aStartPoint,
2586 ParentLayerPoint& aEndPoint,
2587 OverscrollHandoffState& aOverscrollHandoffState) {
2588 const OverscrollHandoffChain& overscrollHandoffChain =
2589 aOverscrollHandoffState.mChain;
2590 uint32_t overscrollHandoffChainIndex = aOverscrollHandoffState.mChainIndex;
2591 RefPtr<AsyncPanZoomController> next;
2592 // If we have reached the end of the overscroll handoff chain, there is
2593 // nothing more to scroll, so we ignore the rest of the pan gesture.
2594 if (overscrollHandoffChainIndex >= overscrollHandoffChain.Length()) {
2595 // Nothing more to scroll - ignore the rest of the pan gesture.
2596 return false;
2597 }
2598
2599 next = overscrollHandoffChain.GetApzcAtIndex(overscrollHandoffChainIndex);
2600
2601 if (next == nullptr || next->IsDestroyed()) {
2602 return false;
2603 }
2604
2605 // Convert the start and end points from |aPrev|'s coordinate space to
2606 // |next|'s coordinate space.
2607 if (!TransformDisplacement(this, aPrev, next, aStartPoint, aEndPoint)) {
2608 return false;
2609 }
2610
2611 // Scroll |next|. If this causes overscroll, it will call DispatchScroll()
2612 // again with an incremented index.
2613 if (!next->AttemptScroll(aStartPoint, aEndPoint, aOverscrollHandoffState)) {
2614 // Transform |aStartPoint| and |aEndPoint| (which now represent the
2615 // portion of the displacement that wasn't consumed by APZCs later
2616 // in the handoff chain) back into |aPrev|'s coordinate space. This
2617 // allows the caller (which is |aPrev|) to interpret the unconsumed
2618 // displacement in its own coordinate space, and make use of it
2619 // (e.g. by going into overscroll).
2620 if (!TransformDisplacement(this, next, aPrev, aStartPoint, aEndPoint)) {
2621 NS_WARNING("Failed to untransform scroll points during dispatch");
2622 }
2623 return false;
2624 }
2625
2626 // Return true to indicate the scroll was consumed entirely.
2627 return true;
2628 }
2629
DispatchFling(AsyncPanZoomController * aPrev,const FlingHandoffState & aHandoffState)2630 ParentLayerPoint APZCTreeManager::DispatchFling(
2631 AsyncPanZoomController* aPrev, const FlingHandoffState& aHandoffState) {
2632 // If immediate handoff is disallowed, do not allow handoff beyond the
2633 // single APZC that's scrolled by the input block that triggered this fling.
2634 if (aHandoffState.mIsHandoff && !StaticPrefs::apz_allow_immediate_handoff() &&
2635 aHandoffState.mScrolledApzc == aPrev) {
2636 FLING_LOG("APZCTM dropping handoff due to disallowed immediate handoff\n");
2637 return aHandoffState.mVelocity;
2638 }
2639
2640 const OverscrollHandoffChain* chain = aHandoffState.mChain;
2641 RefPtr<AsyncPanZoomController> current;
2642 uint32_t overscrollHandoffChainLength = chain->Length();
2643 uint32_t startIndex;
2644
2645 // The fling's velocity needs to be transformed from the screen coordinates
2646 // of |aPrev| to the screen coordinates of |next|. To transform a velocity
2647 // correctly, we need to convert it to a displacement. For now, we do this
2648 // by anchoring it to a start point of (0, 0).
2649 // TODO: For this to be correct in the presence of 3D transforms, we should
2650 // use the end point of the touch that started the fling as the start point
2651 // rather than (0, 0).
2652 ParentLayerPoint startPoint; // (0, 0)
2653 ParentLayerPoint endPoint;
2654
2655 if (aHandoffState.mIsHandoff) {
2656 startIndex = chain->IndexOf(aPrev) + 1;
2657
2658 // IndexOf will return aOverscrollHandoffChain->Length() if
2659 // |aPrev| is not found.
2660 if (startIndex >= overscrollHandoffChainLength) {
2661 return aHandoffState.mVelocity;
2662 }
2663 } else {
2664 startIndex = 0;
2665 }
2666
2667 // This will store any velocity left over after the entire handoff.
2668 ParentLayerPoint finalResidualVelocity = aHandoffState.mVelocity;
2669
2670 ParentLayerPoint currentVelocity = aHandoffState.mVelocity;
2671 for (; startIndex < overscrollHandoffChainLength; startIndex++) {
2672 current = chain->GetApzcAtIndex(startIndex);
2673
2674 // Make sure the apzc about to be handled can be handled
2675 if (current == nullptr || current->IsDestroyed()) {
2676 break;
2677 }
2678
2679 endPoint = startPoint + currentVelocity;
2680
2681 RefPtr<AsyncPanZoomController> prevApzc =
2682 (startIndex > 0) ? chain->GetApzcAtIndex(startIndex - 1) : nullptr;
2683
2684 // Only transform when current apzc can be transformed with previous
2685 if (prevApzc) {
2686 if (!TransformDisplacement(this, prevApzc, current, startPoint,
2687 endPoint)) {
2688 break;
2689 }
2690 }
2691
2692 ParentLayerPoint availableVelocity = (endPoint - startPoint);
2693 ParentLayerPoint residualVelocity;
2694
2695 FlingHandoffState transformedHandoffState = aHandoffState;
2696 transformedHandoffState.mVelocity = availableVelocity;
2697
2698 // Obey overscroll-behavior.
2699 if (prevApzc) {
2700 residualVelocity += prevApzc->AdjustHandoffVelocityForOverscrollBehavior(
2701 transformedHandoffState.mVelocity);
2702 }
2703
2704 residualVelocity += current->AttemptFling(transformedHandoffState);
2705
2706 // If there's no residual velocity, there's nothing more to hand off.
2707 if (IsZero(residualVelocity)) {
2708 return ParentLayerPoint();
2709 }
2710
2711 // If any of the velocity available to be handed off was consumed,
2712 // subtract the proportion of consumed velocity from finalResidualVelocity.
2713 // Note: it's important to compare |residualVelocity| to |availableVelocity|
2714 // here and not to |transformedHandoffState.mVelocity|, since the latter
2715 // may have been modified by AdjustHandoffVelocityForOverscrollBehavior().
2716 if (!FuzzyEqualsAdditive(availableVelocity.x, residualVelocity.x,
2717 COORDINATE_EPSILON)) {
2718 finalResidualVelocity.x *= (residualVelocity.x / availableVelocity.x);
2719 }
2720 if (!FuzzyEqualsAdditive(availableVelocity.y, residualVelocity.y,
2721 COORDINATE_EPSILON)) {
2722 finalResidualVelocity.y *= (residualVelocity.y / availableVelocity.y);
2723 }
2724
2725 currentVelocity = residualVelocity;
2726 }
2727
2728 // Return any residual velocity left over after the entire handoff process.
2729 return finalResidualVelocity;
2730 }
2731
GetTargetAPZC(const ScrollableLayerGuid & aGuid)2732 already_AddRefed<AsyncPanZoomController> APZCTreeManager::GetTargetAPZC(
2733 const ScrollableLayerGuid& aGuid) {
2734 RecursiveMutexAutoLock lock(mTreeLock);
2735 RefPtr<HitTestingTreeNode> node = GetTargetNode(aGuid, nullptr);
2736 MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC
2737 RefPtr<AsyncPanZoomController> apzc = node ? node->GetApzc() : nullptr;
2738 return apzc.forget();
2739 }
2740
GuidComparatorIgnoringPresShell(const ScrollableLayerGuid & aOne,const ScrollableLayerGuid & aTwo)2741 static bool GuidComparatorIgnoringPresShell(const ScrollableLayerGuid& aOne,
2742 const ScrollableLayerGuid& aTwo) {
2743 return aOne.mLayersId == aTwo.mLayersId && aOne.mScrollId == aTwo.mScrollId;
2744 }
2745
GetTargetAPZC(const LayersId & aLayersId,const ScrollableLayerGuid::ViewID & aScrollId) const2746 already_AddRefed<AsyncPanZoomController> APZCTreeManager::GetTargetAPZC(
2747 const LayersId& aLayersId,
2748 const ScrollableLayerGuid::ViewID& aScrollId) const {
2749 MutexAutoLock lock(mMapLock);
2750 ScrollableLayerGuid guid(aLayersId, 0, aScrollId);
2751 auto it = mApzcMap.find(guid);
2752 RefPtr<AsyncPanZoomController> apzc =
2753 (it != mApzcMap.end() ? it->second.apzc : nullptr);
2754 return apzc.forget();
2755 }
2756
GetTargetNode(const ScrollableLayerGuid & aGuid,GuidComparator aComparator) const2757 already_AddRefed<HitTestingTreeNode> APZCTreeManager::GetTargetNode(
2758 const ScrollableLayerGuid& aGuid, GuidComparator aComparator) const {
2759 mTreeLock.AssertCurrentThreadIn();
2760 RefPtr<HitTestingTreeNode> target =
2761 DepthFirstSearchPostOrder<ReverseIterator>(
2762 mRootNode.get(), [&aGuid, &aComparator](HitTestingTreeNode* node) {
2763 bool matches = false;
2764 if (node->GetApzc()) {
2765 if (aComparator) {
2766 matches = aComparator(aGuid, node->GetApzc()->GetGuid());
2767 } else {
2768 matches = node->GetApzc()->Matches(aGuid);
2769 }
2770 }
2771 return matches;
2772 });
2773 return target.forget();
2774 }
2775
GetTargetAPZC(const ScreenPoint & aPoint)2776 APZCTreeManager::HitTestResult APZCTreeManager::GetTargetAPZC(
2777 const ScreenPoint& aPoint) {
2778 RecursiveMutexAutoLock lock(mTreeLock);
2779
2780 if (gfx::gfxVars::UseWebRender()) {
2781 return GetAPZCAtPointWR(aPoint, lock);
2782 }
2783 return GetAPZCAtPoint(aPoint, lock);
2784 }
2785
GetAPZCAtPointWR(const ScreenPoint & aHitTestPoint,const RecursiveMutexAutoLock & aProofOfTreeLock)2786 APZCTreeManager::HitTestResult APZCTreeManager::GetAPZCAtPointWR(
2787 const ScreenPoint& aHitTestPoint,
2788 const RecursiveMutexAutoLock& aProofOfTreeLock) {
2789 HitTestResult hit;
2790 RefPtr<wr::WebRenderAPI> wr = GetWebRenderAPI();
2791 if (!wr) {
2792 // If WebRender isn't running, fall back to the root APZC.
2793 // This is mostly for the benefit of GTests which do not
2794 // run a WebRender instance, but gracefully falling back
2795 // here allows those tests which are not specifically
2796 // testing the hit-test algorithm to still work.
2797 hit.mTargetApzc = FindRootApzcForLayersId(mRootLayersId);
2798 hit.mHitResult = CompositorHitTestFlags::eVisibleToHitTest;
2799 return hit;
2800 }
2801
2802 APZCTM_LOG("Hit-testing point %s with WR\n",
2803 Stringify(aHitTestPoint).c_str());
2804 std::vector<wr::WrHitResult> results =
2805 wr->HitTest(wr::ToWorldPoint(aHitTestPoint));
2806
2807 Maybe<wr::WrHitResult> chosenResult;
2808 for (const wr::WrHitResult& result : results) {
2809 ScrollableLayerGuid guid{result.mLayersId, 0, result.mScrollId};
2810 APZCTM_LOG("Examining result with guid %s hit info 0x%x... ",
2811 Stringify(guid).c_str(), result.mHitInfo.serialize());
2812 if (result.mHitInfo == CompositorHitTestInvisibleToHit) {
2813 APZCTM_LOG("skipping due to invisibility.\n");
2814 continue;
2815 }
2816 RefPtr<HitTestingTreeNode> node =
2817 GetTargetNode(guid, &GuidComparatorIgnoringPresShell);
2818 if (!node) {
2819 APZCTM_LOG("no corresponding node found, falling back to root.\n");
2820 // We can enter here during normal codepaths for cases where the
2821 // nsDisplayCompositorHitTestInfo item emitted a scrollId of
2822 // NULL_SCROLL_ID to the webrender display list. The semantics of that
2823 // is to fall back to the root APZC for the layers id, so that's what
2824 // we do here.
2825 // If we enter this codepath and scrollId is not NULL_SCROLL_ID, then
2826 // that's more likely to be due to a race condition between rebuilding
2827 // the APZ tree and updating the WR scene/hit-test information, resulting
2828 // in WR giving us a hit result for a scene that is not active in APZ.
2829 // Such a scenario would need debugging and fixing.
2830 MOZ_ASSERT(result.mScrollId == ScrollableLayerGuid::NULL_SCROLL_ID);
2831 node = FindRootNodeForLayersId(result.mLayersId);
2832 if (!node) {
2833 // Should never happen, but handle gracefully in release builds just
2834 // in case.
2835 MOZ_ASSERT(false);
2836 chosenResult = Some(result);
2837 break;
2838 }
2839 }
2840 MOZ_ASSERT(node->GetApzc()); // any node returned must have an APZC
2841 EventRegionsOverride flags = node->GetEventRegionsOverride();
2842 if (flags & EventRegionsOverride::ForceEmptyHitRegion) {
2843 // This result is inside a subtree that is invisible to hit-testing.
2844 APZCTM_LOG("skipping due to FEHR subtree.\n");
2845 continue;
2846 }
2847
2848 APZCTM_LOG("selecting as chosen result.\n");
2849 chosenResult = Some(result);
2850 hit.mTargetApzc = node->GetApzc();
2851 if (flags & EventRegionsOverride::ForceDispatchToContent) {
2852 chosenResult->mHitInfo += CompositorHitTestFlags::eApzAwareListeners;
2853 }
2854 break;
2855 }
2856 if (!chosenResult) {
2857 return hit;
2858 }
2859
2860 MOZ_ASSERT(hit.mTargetApzc);
2861 hit.mLayersId = chosenResult->mLayersId;
2862 ScrollableLayerGuid::ViewID scrollId = chosenResult->mScrollId;
2863 gfx::CompositorHitTestInfo hitInfo = chosenResult->mHitInfo;
2864 SideBits sideBits = chosenResult->mSideBits;
2865
2866 APZCTM_LOG("Successfully matched APZC %p (hit result 0x%x)\n",
2867 hit.mTargetApzc.get(), hitInfo.serialize());
2868
2869 const bool isScrollbar =
2870 hitInfo.contains(gfx::CompositorHitTestFlags::eScrollbar);
2871 const bool isScrollbarThumb =
2872 hitInfo.contains(gfx::CompositorHitTestFlags::eScrollbarThumb);
2873 const ScrollDirection direction =
2874 hitInfo.contains(gfx::CompositorHitTestFlags::eScrollbarVertical)
2875 ? ScrollDirection::eVertical
2876 : ScrollDirection::eHorizontal;
2877 HitTestingTreeNode* scrollbarNode = nullptr;
2878 if (isScrollbar || isScrollbarThumb) {
2879 scrollbarNode = BreadthFirstSearch<ReverseIterator>(
2880 mRootNode.get(), [&](HitTestingTreeNode* aNode) {
2881 return (aNode->GetLayersId() == hit.mLayersId) &&
2882 (aNode->IsScrollbarNode() == isScrollbar) &&
2883 (aNode->IsScrollThumbNode() == isScrollbarThumb) &&
2884 (aNode->GetScrollbarDirection() == direction) &&
2885 (aNode->GetScrollTargetId() == scrollId);
2886 });
2887 }
2888
2889 hit.mHitResult = hitInfo;
2890
2891 if (scrollbarNode) {
2892 RefPtr<HitTestingTreeNode> scrollbarRef = scrollbarNode;
2893 hit.mScrollbarNode.Initialize(aProofOfTreeLock, scrollbarRef.forget(),
2894 mTreeLock);
2895 }
2896
2897 hit.mFixedPosSides = sideBits;
2898
2899 return hit;
2900 }
2901
2902 RefPtr<const OverscrollHandoffChain>
BuildOverscrollHandoffChain(const RefPtr<AsyncPanZoomController> & aInitialTarget)2903 APZCTreeManager::BuildOverscrollHandoffChain(
2904 const RefPtr<AsyncPanZoomController>& aInitialTarget) {
2905 // Scroll grabbing is a mechanism that allows content to specify that
2906 // the initial target of a pan should be not the innermost scrollable
2907 // frame at the touch point (which is what GetTargetAPZC finds), but
2908 // something higher up in the tree.
2909 // It's not sufficient to just find the initial target, however, as
2910 // overscroll can be handed off to another APZC. Without scroll grabbing,
2911 // handoff just occurs from child to parent. With scroll grabbing, the
2912 // handoff order can be different, so we build a chain of APZCs in the
2913 // order in which scroll will be handed off to them.
2914
2915 // Grab tree lock since we'll be walking the APZC tree.
2916 RecursiveMutexAutoLock lock(mTreeLock);
2917
2918 // Build the chain. If there is a scroll parent link, we use that. This is
2919 // needed to deal with scroll info layers, because they participate in handoff
2920 // but do not follow the expected layer tree structure. If there are no
2921 // scroll parent links we just walk up the tree to find the scroll parent.
2922 OverscrollHandoffChain* result = new OverscrollHandoffChain;
2923 AsyncPanZoomController* apzc = aInitialTarget;
2924 while (apzc != nullptr) {
2925 result->Add(apzc);
2926
2927 if (apzc->GetScrollHandoffParentId() ==
2928 ScrollableLayerGuid::NULL_SCROLL_ID) {
2929 if (!apzc->IsRootForLayersId()) {
2930 // This probably indicates a bug or missed case in layout code
2931 NS_WARNING("Found a non-root APZ with no handoff parent");
2932 }
2933 apzc = apzc->GetParent();
2934 continue;
2935 }
2936
2937 // Guard against a possible infinite-loop condition. If we hit this, the
2938 // layout code that generates the handoff parents did something wrong.
2939 MOZ_ASSERT(apzc->GetScrollHandoffParentId() != apzc->GetGuid().mScrollId);
2940 RefPtr<AsyncPanZoomController> scrollParent = GetTargetAPZC(
2941 apzc->GetGuid().mLayersId, apzc->GetScrollHandoffParentId());
2942 apzc = scrollParent.get();
2943 }
2944
2945 // Now adjust the chain to account for scroll grabbing. Sorting is a bit
2946 // of an overkill here, but scroll grabbing will likely be generalized
2947 // to scroll priorities, so we might as well do it this way.
2948 result->SortByScrollPriority();
2949
2950 // Print the overscroll chain for debugging.
2951 for (uint32_t i = 0; i < result->Length(); ++i) {
2952 APZCTM_LOG("OverscrollHandoffChain[%d] = %p\n", i,
2953 result->GetApzcAtIndex(i).get());
2954 }
2955
2956 return result;
2957 }
2958
SetLongTapEnabled(bool aLongTapEnabled)2959 void APZCTreeManager::SetLongTapEnabled(bool aLongTapEnabled) {
2960 APZThreadUtils::AssertOnControllerThread();
2961 GestureEventListener::SetLongTapEnabled(aLongTapEnabled);
2962 }
2963
FindScrollThumbNode(const AsyncDragMetrics & aDragMetrics,HitTestingTreeNodeAutoLock & aOutThumbNode)2964 void APZCTreeManager::FindScrollThumbNode(
2965 const AsyncDragMetrics& aDragMetrics,
2966 HitTestingTreeNodeAutoLock& aOutThumbNode) {
2967 if (!aDragMetrics.mDirection) {
2968 // The AsyncDragMetrics has not been initialized yet - there will be
2969 // no matching node, so don't bother searching the tree.
2970 return;
2971 }
2972
2973 RecursiveMutexAutoLock lock(mTreeLock);
2974
2975 RefPtr<HitTestingTreeNode> result = DepthFirstSearch<ReverseIterator>(
2976 mRootNode.get(), [&aDragMetrics](HitTestingTreeNode* aNode) {
2977 return aNode->MatchesScrollDragMetrics(aDragMetrics);
2978 });
2979 if (result) {
2980 aOutThumbNode.Initialize(lock, result.forget(), mTreeLock);
2981 }
2982 }
2983
GetTargetApzcForNode(HitTestingTreeNode * aNode)2984 AsyncPanZoomController* APZCTreeManager::GetTargetApzcForNode(
2985 HitTestingTreeNode* aNode) {
2986 for (const HitTestingTreeNode* n = aNode;
2987 n && n->GetLayersId() == aNode->GetLayersId(); n = n->GetParent()) {
2988 if (n->GetApzc()) {
2989 APZCTM_LOG("Found target %p using ancestor lookup\n", n->GetApzc());
2990 return n->GetApzc();
2991 }
2992 if (n->GetFixedPosTarget() != ScrollableLayerGuid::NULL_SCROLL_ID) {
2993 RefPtr<AsyncPanZoomController> fpTarget =
2994 GetTargetAPZC(n->GetLayersId(), n->GetFixedPosTarget());
2995 APZCTM_LOG("Found target APZC %p using fixed-pos lookup on %" PRIu64 "\n",
2996 fpTarget.get(), n->GetFixedPosTarget());
2997 return fpTarget.get();
2998 }
2999 }
3000 return nullptr;
3001 }
3002
GetAPZCAtPoint(const ScreenPoint & aHitTestPoint,const RecursiveMutexAutoLock & aProofOfTreeLock)3003 APZCTreeManager::HitTestResult APZCTreeManager::GetAPZCAtPoint(
3004 const ScreenPoint& aHitTestPoint,
3005 const RecursiveMutexAutoLock& aProofOfTreeLock) {
3006 HitTestResult hit;
3007 // This walks the tree in depth-first, reverse order, so that it encounters
3008 // APZCs front-to-back on the screen.
3009 HitTestingTreeNode* resultNode;
3010 HitTestingTreeNode* root = mRootNode;
3011 HitTestingTreeNode* scrollbarNode = nullptr;
3012 std::stack<LayerPoint> hitTestPoints;
3013 ParentLayerPoint point = ViewAs<ParentLayerPixel>(
3014 aHitTestPoint, PixelCastJustification::ScreenIsParentLayerForRoot);
3015 hitTestPoints.push(
3016 ViewAs<LayerPixel>(point, PixelCastJustification::MovingDownToChildren));
3017
3018 ForEachNode<ReverseIterator>(
3019 root,
3020 [&hitTestPoints, this](HitTestingTreeNode* aNode) {
3021 ParentLayerPoint hitTestPointForParent = ViewAs<ParentLayerPixel>(
3022 hitTestPoints.top(), PixelCastJustification::MovingDownToChildren);
3023 if (aNode->IsOutsideClip(hitTestPointForParent)) {
3024 // If the point being tested is outside the clip region for this node
3025 // then we don't need to test against this node or any of its
3026 // children. Just skip it and move on.
3027 APZCTM_LOG("Point %f %f outside clip for node %p\n",
3028 hitTestPoints.top().x, hitTestPoints.top().y, aNode);
3029 return TraversalFlag::Skip;
3030 }
3031 // First check the subtree rooted at this node, because deeper nodes
3032 // are more "in front".
3033 Maybe<LayerPoint> hitTestPoint = aNode->Untransform(
3034 hitTestPointForParent, ComputeTransformForNode(aNode));
3035 APZCTM_LOG(
3036 "Transformed ParentLayer point %s to layer %s\n",
3037 Stringify(hitTestPointForParent).c_str(),
3038 hitTestPoint ? Stringify(hitTestPoint.ref()).c_str() : "nil");
3039 if (!hitTestPoint) {
3040 return TraversalFlag::Skip;
3041 }
3042 hitTestPoints.push(hitTestPoint.ref());
3043 return TraversalFlag::Continue;
3044 },
3045 [&resultNode, &hitTestPoints, &hit](HitTestingTreeNode* aNode) {
3046 CompositorHitTestInfo hitResult = aNode->HitTest(hitTestPoints.top());
3047 hitTestPoints.pop();
3048 APZCTM_LOG("Testing Layer point %s against node %p\n",
3049 Stringify(hitTestPoints.top()).c_str(), aNode);
3050 if (hitResult != CompositorHitTestInvisibleToHit) {
3051 resultNode = aNode;
3052 hit.mHitResult = hitResult;
3053 return TraversalFlag::Abort;
3054 }
3055 return TraversalFlag::Continue;
3056 });
3057
3058 if (hit.mHitResult != CompositorHitTestInvisibleToHit) {
3059 MOZ_ASSERT(resultNode);
3060 for (HitTestingTreeNode* n = resultNode; n; n = n->GetParent()) {
3061 if (n->IsScrollbarNode()) {
3062 scrollbarNode = n;
3063 hit.mHitResult += CompositorHitTestFlags::eScrollbar;
3064 if (n->IsScrollThumbNode()) {
3065 hit.mHitResult += CompositorHitTestFlags::eScrollbarThumb;
3066 }
3067 if (n->GetScrollbarDirection() == ScrollDirection::eVertical) {
3068 hit.mHitResult += CompositorHitTestFlags::eScrollbarVertical;
3069 }
3070
3071 // If we hit a scrollbar, target the APZC for the content scrolled
3072 // by the scrollbar. (The scrollbar itself doesn't scroll with the
3073 // scrolled content, so it doesn't carry the scrolled content's
3074 // scroll metadata).
3075 RefPtr<AsyncPanZoomController> scrollTarget =
3076 GetTargetAPZC(n->GetLayersId(), n->GetScrollTargetId());
3077 if (scrollTarget) {
3078 hit.mLayersId = n->GetLayersId();
3079 hit.mTargetApzc = std::move(scrollTarget);
3080 RefPtr<HitTestingTreeNode> scrollbarRef = scrollbarNode;
3081 hit.mScrollbarNode.Initialize(aProofOfTreeLock, scrollbarRef.forget(),
3082 mTreeLock);
3083 return hit;
3084 }
3085 } else if (IsFixedToRootContent(n)) {
3086 hit.mFixedPosSides = n->GetFixedPosSides();
3087 }
3088 }
3089
3090 hit.mTargetApzc = GetTargetApzcForNode(resultNode);
3091 if (!hit.mTargetApzc) {
3092 hit.mTargetApzc = FindRootApzcForLayersId(resultNode->GetLayersId());
3093 MOZ_ASSERT(hit.mTargetApzc);
3094 APZCTM_LOG("Found target %p using root lookup\n", hit.mTargetApzc.get());
3095 }
3096 APZCTM_LOG("Successfully matched APZC %p via node %p (hit result 0x%x)\n",
3097 hit.mTargetApzc.get(), resultNode, hit.mHitResult.serialize());
3098 hit.mLayersId = resultNode->GetLayersId();
3099
3100 return hit;
3101 }
3102
3103 return hit;
3104 }
3105
FindRootNodeForLayersId(LayersId aLayersId) const3106 HitTestingTreeNode* APZCTreeManager::FindRootNodeForLayersId(
3107 LayersId aLayersId) const {
3108 mTreeLock.AssertCurrentThreadIn();
3109
3110 HitTestingTreeNode* resultNode = BreadthFirstSearch<ReverseIterator>(
3111 mRootNode.get(), [aLayersId](HitTestingTreeNode* aNode) {
3112 AsyncPanZoomController* apzc = aNode->GetApzc();
3113 return apzc && apzc->GetLayersId() == aLayersId &&
3114 apzc->IsRootForLayersId();
3115 });
3116 return resultNode;
3117 }
3118
FindRootApzcForLayersId(LayersId aLayersId) const3119 AsyncPanZoomController* APZCTreeManager::FindRootApzcForLayersId(
3120 LayersId aLayersId) const {
3121 mTreeLock.AssertCurrentThreadIn();
3122
3123 HitTestingTreeNode* resultNode = FindRootNodeForLayersId(aLayersId);
3124 return resultNode ? resultNode->GetApzc() : nullptr;
3125 }
3126
FindZoomableApzc(AsyncPanZoomController * aStart) const3127 already_AddRefed<AsyncPanZoomController> APZCTreeManager::FindZoomableApzc(
3128 AsyncPanZoomController* aStart) const {
3129 return GetZoomableTarget(aStart, aStart);
3130 }
3131
GetGeckoFixedLayerMargins() const3132 ScreenMargin APZCTreeManager::GetGeckoFixedLayerMargins() const {
3133 RecursiveMutexAutoLock lock(mTreeLock);
3134 return mGeckoFixedLayerMargins;
3135 }
3136
FindRootContentApzcForLayersId(LayersId aLayersId) const3137 AsyncPanZoomController* APZCTreeManager::FindRootContentApzcForLayersId(
3138 LayersId aLayersId) const {
3139 mTreeLock.AssertCurrentThreadIn();
3140
3141 HitTestingTreeNode* resultNode = BreadthFirstSearch<ReverseIterator>(
3142 mRootNode.get(), [aLayersId](HitTestingTreeNode* aNode) {
3143 AsyncPanZoomController* apzc = aNode->GetApzc();
3144 return apzc && apzc->GetLayersId() == aLayersId &&
3145 apzc->IsRootContent();
3146 });
3147 return resultNode ? resultNode->GetApzc() : nullptr;
3148 }
3149
3150 // clang-format off
3151 /* The methods GetScreenToApzcTransform() and GetApzcToGeckoTransform() return
3152 some useful transformations that input events may need applied. This is best
3153 illustrated with an example. Consider a chain of layers, L, M, N, O, P, Q, R. Layer L
3154 is the layer that corresponds to the argument |aApzc|, and layer R is the root
3155 of the layer tree. Layer M is the parent of L, N is the parent of M, and so on.
3156 When layer L is displayed to the screen by the compositor, the set of transforms that
3157 are applied to L are (in order from top to bottom):
3158
3159 L's CSS transform (hereafter referred to as transform matrix LC)
3160 L's nontransient async transform (hereafter referred to as transform matrix LN)
3161 L's transient async transform (hereafter referred to as transform matrix LT)
3162 M's CSS transform (hereafter referred to as transform matrix MC)
3163 M's nontransient async transform (hereafter referred to as transform matrix MN)
3164 M's transient async transform (hereafter referred to as transform matrix MT)
3165 ...
3166 R's CSS transform (hereafter referred to as transform matrix RC)
3167 R's nontransient async transform (hereafter referred to as transform matrix RN)
3168 R's transient async transform (hereafter referred to as transform matrix RT)
3169
3170 Also, for any layer, the async transform is the combination of its transient and non-transient
3171 parts. That is, for any layer L:
3172 LA === LN * LT
3173 LA.Inverse() === LT.Inverse() * LN.Inverse()
3174
3175 If we want user input to modify L's transient async transform, we have to first convert
3176 user input from screen space to the coordinate space of L's transient async transform. Doing
3177 this involves applying the following transforms (in order from top to bottom):
3178 RT.Inverse()
3179 RN.Inverse()
3180 RC.Inverse()
3181 ...
3182 MT.Inverse()
3183 MN.Inverse()
3184 MC.Inverse()
3185 This combined transformation is returned by GetScreenToApzcTransform().
3186
3187 Next, if we want user inputs sent to gecko for event-dispatching, we will need to strip
3188 out all of the async transforms that are involved in this chain. This is because async
3189 transforms are stored only in the compositor and gecko does not account for them when
3190 doing display-list-based hit-testing for event dispatching.
3191 Furthermore, because these input events are processed by Gecko in a FIFO queue that
3192 includes other things (specifically paint requests), it is possible that by time the
3193 input event reaches gecko, it will have painted something else. Therefore, we need to
3194 apply another transform to the input events to account for the possible disparity between
3195 what we know gecko last painted and the last paint request we sent to gecko. Let this
3196 transform be represented by LD, MD, ... RD.
3197 Therefore, given a user input in screen space, the following transforms need to be applied
3198 (in order from top to bottom):
3199 RT.Inverse()
3200 RN.Inverse()
3201 RC.Inverse()
3202 ...
3203 MT.Inverse()
3204 MN.Inverse()
3205 MC.Inverse()
3206 LT.Inverse()
3207 LN.Inverse()
3208 LC.Inverse()
3209 LC
3210 LD
3211 MC
3212 MD
3213 ...
3214 RC
3215 RD
3216 This sequence can be simplified and refactored to the following:
3217 GetScreenToApzcTransform()
3218 LA.Inverse()
3219 LD
3220 MC
3221 MD
3222 ...
3223 RC
3224 RD
3225 Since GetScreenToApzcTransform() can be obtained by calling that function, GetApzcToGeckoTransform()
3226 returns the remaining transforms (LA.Inverse() * LD * ... * RD), so that the caller code can
3227 combine it with GetScreenToApzcTransform() to get the final transform required in this case.
3228
3229 Note that for many of these layers, there will be no AsyncPanZoomController attached, and
3230 so the async transform will be the identity transform. So, in the example above, if layers
3231 L and P have APZC instances attached, MT, MN, MD, NT, NN, ND, OT, ON, OD, QT, QN, QD, RT,
3232 RN and RD will be identity transforms.
3233 Additionally, for space-saving purposes, each APZC instance stores its layer's individual
3234 CSS transform and the accumulation of CSS transforms to its parent APZC. So the APZC for
3235 layer L would store LC and (MC * NC * OC), and the layer P would store PC and (QC * RC).
3236 The APZC instances track the last dispatched paint request and so are able to calculate LD and
3237 PD using those internally stored values.
3238 The APZCs also obviously have LT, LN, PT, and PN, so all of the above transformation combinations
3239 required can be generated.
3240 */
3241 // clang-format on
3242
3243 /*
3244 * See the long comment above for a detailed explanation of this function.
3245 */
GetScreenToApzcTransform(const AsyncPanZoomController * aApzc) const3246 ScreenToParentLayerMatrix4x4 APZCTreeManager::GetScreenToApzcTransform(
3247 const AsyncPanZoomController* aApzc) const {
3248 Matrix4x4 result;
3249 RecursiveMutexAutoLock lock(mTreeLock);
3250
3251 // The comments below assume there is a chain of layers L..R with L and P
3252 // having APZC instances as explained in the comment above. This function is
3253 // called with aApzc at L, and the loop below performs one iteration, where
3254 // parent is at P. The comments explain what values are stored in the
3255 // variables at these two levels. All the comments use standard matrix
3256 // notation where the leftmost matrix in a multiplication is applied first.
3257
3258 // ancestorUntransform is PC.Inverse() * OC.Inverse() * NC.Inverse() *
3259 // MC.Inverse()
3260 Matrix4x4 ancestorUntransform = aApzc->GetAncestorTransform().Inverse();
3261
3262 // result is initialized to PC.Inverse() * OC.Inverse() * NC.Inverse() *
3263 // MC.Inverse()
3264 result = ancestorUntransform;
3265
3266 for (AsyncPanZoomController* parent = aApzc->GetParent(); parent;
3267 parent = parent->GetParent()) {
3268 // ancestorUntransform is updated to RC.Inverse() * QC.Inverse() when parent
3269 // == P
3270 ancestorUntransform = parent->GetAncestorTransform().Inverse();
3271 // asyncUntransform is updated to PA.Inverse() when parent == P
3272 Matrix4x4 asyncUntransform = parent
3273 ->GetCurrentAsyncTransformWithOverscroll(
3274 AsyncPanZoomController::eForHitTesting)
3275 .Inverse()
3276 .ToUnknownMatrix();
3277 // untransformSinceLastApzc is RC.Inverse() * QC.Inverse() * PA.Inverse()
3278 Matrix4x4 untransformSinceLastApzc = ancestorUntransform * asyncUntransform;
3279
3280 // result is RC.Inverse() * QC.Inverse() * PA.Inverse() * PC.Inverse() *
3281 // OC.Inverse() * NC.Inverse() * MC.Inverse()
3282 result = untransformSinceLastApzc * result;
3283
3284 // The above value for result when parent == P matches the required output
3285 // as explained in the comment above this method. Note that any missing
3286 // terms are guaranteed to be identity transforms.
3287 }
3288
3289 return ViewAs<ScreenToParentLayerMatrix4x4>(result);
3290 }
3291
3292 /*
3293 * See the long comment above GetScreenToApzcTransform() for a detailed
3294 * explanation of this function.
3295 */
GetApzcToGeckoTransform(const AsyncPanZoomController * aApzc) const3296 ParentLayerToScreenMatrix4x4 APZCTreeManager::GetApzcToGeckoTransform(
3297 const AsyncPanZoomController* aApzc) const {
3298 Matrix4x4 result;
3299 RecursiveMutexAutoLock lock(mTreeLock);
3300
3301 // The comments below assume there is a chain of layers L..R with L and P
3302 // having APZC instances as explained in the comment above. This function is
3303 // called with aApzc at L, and the loop below performs one iteration, where
3304 // parent is at P. The comments explain what values are stored in the
3305 // variables at these two levels. All the comments use standard matrix
3306 // notation where the leftmost matrix in a multiplication is applied first.
3307
3308 // asyncUntransform is LA.Inverse()
3309 Matrix4x4 asyncUntransform = aApzc
3310 ->GetCurrentAsyncTransformWithOverscroll(
3311 AsyncPanZoomController::eForHitTesting)
3312 .Inverse()
3313 .ToUnknownMatrix();
3314
3315 // aTransformToGeckoOut is initialized to LA.Inverse() * LD * MC * NC * OC *
3316 // PC
3317 result = asyncUntransform * aApzc->GetTransformToLastDispatchedPaint() *
3318 aApzc->GetAncestorTransform();
3319
3320 for (AsyncPanZoomController* parent = aApzc->GetParent(); parent;
3321 parent = parent->GetParent()) {
3322 // aTransformToGeckoOut is LA.Inverse() * LD * MC * NC * OC * PC * PD * QC *
3323 // RC
3324 result = result * parent->GetTransformToLastDispatchedPaint() *
3325 parent->GetAncestorTransform();
3326
3327 // The above value for result when parent == P matches the required output
3328 // as explained in the comment above this method. Note that any missing
3329 // terms are guaranteed to be identity transforms.
3330 }
3331
3332 return ViewAs<ParentLayerToScreenMatrix4x4>(result);
3333 }
3334
GetCurrentMousePosition() const3335 ScreenPoint APZCTreeManager::GetCurrentMousePosition() const {
3336 return mCurrentMousePosition;
3337 }
3338
GetZoomableTarget(AsyncPanZoomController * aApzc1,AsyncPanZoomController * aApzc2) const3339 already_AddRefed<AsyncPanZoomController> APZCTreeManager::GetZoomableTarget(
3340 AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const {
3341 RecursiveMutexAutoLock lock(mTreeLock);
3342 RefPtr<AsyncPanZoomController> apzc;
3343 // For now, we only ever want to do pinching on the root-content APZC for
3344 // a given layers id.
3345 if (aApzc1 && aApzc2 && aApzc1->GetLayersId() == aApzc2->GetLayersId()) {
3346 // If the two APZCs have the same layers id, find the root-content APZC
3347 // for that layers id. Don't call CommonAncestor() because there may not
3348 // be a common ancestor for the layers id (e.g. if one APZCs is inside a
3349 // fixed-position element).
3350 apzc = FindRootContentApzcForLayersId(aApzc1->GetLayersId());
3351 } else {
3352 // Otherwise, find the common ancestor (to reach a common layers id), and
3353 // get the root-content APZC for that layers id.
3354 apzc = CommonAncestor(aApzc1, aApzc2);
3355 if (apzc) {
3356 apzc = FindRootContentApzcForLayersId(apzc->GetLayersId());
3357 }
3358 }
3359 return apzc.forget();
3360 }
3361
ConvertToGecko(const ScreenIntPoint & aPoint,AsyncPanZoomController * aApzc)3362 Maybe<ScreenIntPoint> APZCTreeManager::ConvertToGecko(
3363 const ScreenIntPoint& aPoint, AsyncPanZoomController* aApzc) {
3364 RecursiveMutexAutoLock lock(mTreeLock);
3365 ScreenToScreenMatrix4x4 transformScreenToGecko =
3366 GetScreenToApzcTransform(aApzc) * GetApzcToGeckoTransform(aApzc);
3367 Maybe<ScreenIntPoint> geckoPoint =
3368 UntransformBy(transformScreenToGecko, aPoint);
3369 if (geckoPoint) {
3370 if (mTouchBlockHitResult.mFixedPosSides != SideBits::eNone) {
3371 MutexAutoLock mapLock(mMapLock);
3372 *geckoPoint -=
3373 RoundedToInt(AsyncCompositionManager::ComputeFixedMarginsOffset(
3374 GetCompositorFixedLayerMargins(mapLock),
3375 mTouchBlockHitResult.mFixedPosSides, mGeckoFixedLayerMargins));
3376 }
3377 }
3378 return geckoPoint;
3379 }
3380
CommonAncestor(AsyncPanZoomController * aApzc1,AsyncPanZoomController * aApzc2) const3381 already_AddRefed<AsyncPanZoomController> APZCTreeManager::CommonAncestor(
3382 AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const {
3383 mTreeLock.AssertCurrentThreadIn();
3384 RefPtr<AsyncPanZoomController> ancestor;
3385
3386 // If either aApzc1 or aApzc2 is null, min(depth1, depth2) will be 0 and this
3387 // function will return null.
3388
3389 // Calculate depth of the APZCs in the tree
3390 int depth1 = 0, depth2 = 0;
3391 for (AsyncPanZoomController* parent = aApzc1; parent;
3392 parent = parent->GetParent()) {
3393 depth1++;
3394 }
3395 for (AsyncPanZoomController* parent = aApzc2; parent;
3396 parent = parent->GetParent()) {
3397 depth2++;
3398 }
3399
3400 // At most one of the following two loops will be executed; the deeper APZC
3401 // pointer will get walked up to the depth of the shallower one.
3402 int minDepth = depth1 < depth2 ? depth1 : depth2;
3403 while (depth1 > minDepth) {
3404 depth1--;
3405 aApzc1 = aApzc1->GetParent();
3406 }
3407 while (depth2 > minDepth) {
3408 depth2--;
3409 aApzc2 = aApzc2->GetParent();
3410 }
3411
3412 // Walk up the ancestor chains of both APZCs, always staying at the same depth
3413 // for either APZC, and return the the first common ancestor encountered.
3414 while (true) {
3415 if (aApzc1 == aApzc2) {
3416 ancestor = aApzc1;
3417 break;
3418 }
3419 if (depth1 <= 0) {
3420 break;
3421 }
3422 aApzc1 = aApzc1->GetParent();
3423 aApzc2 = aApzc2->GetParent();
3424 }
3425 return ancestor.forget();
3426 }
3427
IsFixedToRootContent(const HitTestingTreeNode * aNode) const3428 bool APZCTreeManager::IsFixedToRootContent(
3429 const HitTestingTreeNode* aNode) const {
3430 MutexAutoLock lock(mMapLock);
3431 return IsFixedToRootContent(FixedPositionInfo(aNode), lock);
3432 }
3433
IsFixedToRootContent(const FixedPositionInfo & aFixedInfo,const MutexAutoLock & aProofOfMapLock) const3434 bool APZCTreeManager::IsFixedToRootContent(
3435 const FixedPositionInfo& aFixedInfo,
3436 const MutexAutoLock& aProofOfMapLock) const {
3437 ScrollableLayerGuid::ViewID fixedTarget = aFixedInfo.mFixedPosTarget;
3438 if (fixedTarget == ScrollableLayerGuid::NULL_SCROLL_ID) {
3439 return false;
3440 }
3441 auto it =
3442 mApzcMap.find(ScrollableLayerGuid(aFixedInfo.mLayersId, 0, fixedTarget));
3443 if (it == mApzcMap.end()) {
3444 return false;
3445 }
3446 RefPtr<AsyncPanZoomController> targetApzc = it->second.apzc;
3447 return targetApzc && targetApzc->IsRootContent();
3448 }
3449
SidesStuckToRootContent(const HitTestingTreeNode * aNode) const3450 SideBits APZCTreeManager::SidesStuckToRootContent(
3451 const HitTestingTreeNode* aNode) const {
3452 MutexAutoLock lock(mMapLock);
3453 return SidesStuckToRootContent(StickyPositionInfo(aNode), lock);
3454 }
3455
SidesStuckToRootContent(const StickyPositionInfo & aStickyInfo,const MutexAutoLock & aProofOfMapLock) const3456 SideBits APZCTreeManager::SidesStuckToRootContent(
3457 const StickyPositionInfo& aStickyInfo,
3458 const MutexAutoLock& aProofOfMapLock) const {
3459 SideBits result = SideBits::eNone;
3460
3461 ScrollableLayerGuid::ViewID stickyTarget = aStickyInfo.mStickyPosTarget;
3462 if (stickyTarget == ScrollableLayerGuid::NULL_SCROLL_ID) {
3463 return result;
3464 }
3465
3466 // We support the dynamic toolbar at top and bottom.
3467 if ((aStickyInfo.mFixedPosSides & SideBits::eTopBottom) == SideBits::eNone) {
3468 return result;
3469 }
3470
3471 auto it = mApzcMap.find(
3472 ScrollableLayerGuid(aStickyInfo.mLayersId, 0, stickyTarget));
3473 if (it == mApzcMap.end()) {
3474 return result;
3475 }
3476 RefPtr<AsyncPanZoomController> stickyTargetApzc = it->second.apzc;
3477 if (!stickyTargetApzc || !stickyTargetApzc->IsRootContent()) {
3478 return result;
3479 }
3480
3481 ParentLayerPoint translation =
3482 stickyTargetApzc
3483 ->GetCurrentAsyncTransform(
3484 AsyncPanZoomController::eForHitTesting,
3485 AsyncTransformComponents{AsyncTransformComponent::eLayout})
3486 .mTranslation;
3487
3488 if (apz::IsStuckAtTop(translation.y, aStickyInfo.mStickyScrollRangeInner,
3489 aStickyInfo.mStickyScrollRangeOuter)) {
3490 result |= SideBits::eTop;
3491 }
3492 if (apz::IsStuckAtBottom(translation.y, aStickyInfo.mStickyScrollRangeInner,
3493 aStickyInfo.mStickyScrollRangeOuter)) {
3494 result |= SideBits::eBottom;
3495 }
3496 return result;
3497 }
3498
ComputeTransformForNode(const HitTestingTreeNode * aNode) const3499 LayerToParentLayerMatrix4x4 APZCTreeManager::ComputeTransformForNode(
3500 const HitTestingTreeNode* aNode) const {
3501 mTreeLock.AssertCurrentThreadIn();
3502 // The async transforms applied here for hit-testing purposes, are intended
3503 // to match the ones AsyncCompositionManager (or equivalent WebRender code)
3504 // applies for rendering purposes.
3505 // Note that with containerless scrolling, the layer structure looks like
3506 // this:
3507 //
3508 // root container layer
3509 // async zoom container layer
3510 // scrollable content layers (with scroll metadata)
3511 // fixed content layers (no scroll metadta, annotated isFixedPosition)
3512 // scrollbar layers
3513 //
3514 // The intended async transforms in this case are:
3515 // * On the async zoom container layer, the "visual" portion of the root
3516 // content APZC's async transform (which includes the zoom, and async
3517 // scrolling of the visual viewport relative to the layout viewport).
3518 // * On the scrollable layers bearing the root content APZC's scroll
3519 // metadata, the "layout" portion of the root content APZC's async
3520 // transform (which includes async scrolling of the layout viewport
3521 // relative to the visual viewport).
3522 if (AsyncPanZoomController* apzc = aNode->GetApzc()) {
3523 // If the node represents scrollable content, apply the async transform
3524 // from its APZC.
3525 bool visualTransformIsInheritedFromAncestor =
3526 apzc->IsRootContent() && /* we're the APZC whose visual transform
3527 might be on the async zoom container */
3528 mUsingAsyncZoomContainer && /* there is an async zoom container */
3529 !aNode->IsAsyncZoomContainer(); /* it's not us */
3530 AsyncTransformComponents components =
3531 visualTransformIsInheritedFromAncestor
3532 ? AsyncTransformComponents{AsyncTransformComponent::eLayout}
3533 : LayoutAndVisual;
3534 return aNode->GetTransform() *
3535 CompleteAsyncTransform(apzc->GetCurrentAsyncTransformWithOverscroll(
3536 AsyncPanZoomController::eForHitTesting, components));
3537 } else if (aNode->IsAsyncZoomContainer()) {
3538 if (AsyncPanZoomController* rootContent =
3539 FindRootContentApzcForLayersId(aNode->GetLayersId())) {
3540 return aNode->GetTransform() *
3541 CompleteAsyncTransform(
3542 rootContent->GetCurrentAsyncTransformWithOverscroll(
3543 AsyncPanZoomController::eForHitTesting,
3544 {AsyncTransformComponent::eVisual}));
3545 }
3546 } else if (aNode->IsScrollThumbNode()) {
3547 // If the node represents a scrollbar thumb, compute and apply the
3548 // transformation that will be applied to the thumb in
3549 // AsyncCompositionManager.
3550 ScrollableLayerGuid guid{aNode->GetLayersId(), 0,
3551 aNode->GetScrollTargetId()};
3552 if (RefPtr<HitTestingTreeNode> scrollTargetNode =
3553 GetTargetNode(guid, &GuidComparatorIgnoringPresShell)) {
3554 AsyncPanZoomController* scrollTargetApzc = scrollTargetNode->GetApzc();
3555 MOZ_ASSERT(scrollTargetApzc);
3556 return scrollTargetApzc->CallWithLastContentPaintMetrics(
3557 [&](const FrameMetrics& aMetrics) {
3558 return ComputeTransformForScrollThumb(
3559 aNode->GetTransform() * AsyncTransformMatrix(),
3560 scrollTargetNode->GetTransform().ToUnknownMatrix(),
3561 scrollTargetApzc, aMetrics, aNode->GetScrollbarData(),
3562 scrollTargetNode->IsAncestorOf(aNode), nullptr);
3563 });
3564 }
3565 } else if (IsFixedToRootContent(aNode)) {
3566 ParentLayerPoint translation;
3567 {
3568 MutexAutoLock mapLock(mMapLock);
3569 translation = ViewAs<ParentLayerPixel>(
3570 AsyncCompositionManager::ComputeFixedMarginsOffset(
3571 GetCompositorFixedLayerMargins(mapLock),
3572 aNode->GetFixedPosSides(), mGeckoFixedLayerMargins),
3573 PixelCastJustification::ScreenIsParentLayerForRoot);
3574 }
3575 return aNode->GetTransform() *
3576 CompleteAsyncTransform(
3577 AsyncTransformComponentMatrix::Translation(translation));
3578 }
3579 SideBits sides = SidesStuckToRootContent(aNode);
3580 if (sides != SideBits::eNone) {
3581 ParentLayerPoint translation;
3582 {
3583 MutexAutoLock mapLock(mMapLock);
3584 translation = ViewAs<ParentLayerPixel>(
3585 AsyncCompositionManager::ComputeFixedMarginsOffset(
3586 GetCompositorFixedLayerMargins(mapLock), sides,
3587 // For sticky layers, we don't need to factor
3588 // mGeckoFixedLayerMargins because Gecko doesn't shift the
3589 // position of sticky elements for dynamic toolbar movements.
3590 ScreenMargin()),
3591 PixelCastJustification::ScreenIsParentLayerForRoot);
3592 }
3593 return aNode->GetTransform() *
3594 CompleteAsyncTransform(
3595 AsyncTransformComponentMatrix::Translation(translation));
3596 }
3597 // Otherwise, the node does not have an async transform.
3598 return aNode->GetTransform() * AsyncTransformMatrix();
3599 }
3600
GetWebRenderAPI() const3601 already_AddRefed<wr::WebRenderAPI> APZCTreeManager::GetWebRenderAPI() const {
3602 RefPtr<wr::WebRenderAPI> api;
3603 CompositorBridgeParent::CallWithIndirectShadowTree(
3604 mRootLayersId, [&](LayerTreeState& aState) -> void {
3605 if (aState.mWrBridge) {
3606 api = aState.mWrBridge->GetWebRenderAPI();
3607 }
3608 });
3609 return api.forget();
3610 }
3611
3612 /*static*/
GetContentController(LayersId aLayersId)3613 already_AddRefed<GeckoContentController> APZCTreeManager::GetContentController(
3614 LayersId aLayersId) {
3615 RefPtr<GeckoContentController> controller;
3616 CompositorBridgeParent::CallWithIndirectShadowTree(
3617 aLayersId,
3618 [&](LayerTreeState& aState) -> void { controller = aState.mController; });
3619 return controller.forget();
3620 }
3621
GetCompositorFixedLayerMargins(const MutexAutoLock & aProofOfMapLock) const3622 ScreenMargin APZCTreeManager::GetCompositorFixedLayerMargins(
3623 const MutexAutoLock& aProofOfMapLock) const {
3624 ScreenMargin result = mCompositorFixedLayerMargins;
3625 if (StaticPrefs::apz_fixed_margin_override_enabled()) {
3626 result.top = StaticPrefs::apz_fixed_margin_override_top();
3627 result.bottom = StaticPrefs::apz_fixed_margin_override_bottom();
3628 }
3629 return result;
3630 }
3631
GetAPZTestData(LayersId aLayersId,APZTestData * aOutData)3632 bool APZCTreeManager::GetAPZTestData(LayersId aLayersId,
3633 APZTestData* aOutData) {
3634 AssertOnUpdaterThread();
3635
3636 { // copy the relevant test data into aOutData while holding the
3637 // mTestDataLock
3638 MutexAutoLock lock(mTestDataLock);
3639 auto it = mTestData.find(aLayersId);
3640 if (it == mTestData.end()) {
3641 return false;
3642 }
3643 *aOutData = *(it->second);
3644 }
3645
3646 { // add some additional "current state" into the returned APZTestData
3647 MutexAutoLock mapLock(mMapLock);
3648
3649 ClippedCompositionBoundsMap clippedCompBounds;
3650 for (const auto& mapping : mApzcMap) {
3651 if (mapping.first.mLayersId != aLayersId) {
3652 continue;
3653 }
3654
3655 ParentLayerRect clippedBounds = ComputeClippedCompositionBounds(
3656 mapLock, clippedCompBounds, mapping.first);
3657 AsyncPanZoomController* apzc = mapping.second.apzc;
3658 std::string viewId = std::to_string(mapping.first.mScrollId);
3659 std::string apzcState;
3660 if (apzc->GetCheckerboardMagnitude(clippedBounds)) {
3661 apzcState += "checkerboarding,";
3662 }
3663 aOutData->RecordAdditionalData(viewId, apzcState);
3664 }
3665 }
3666 return true;
3667 }
3668
SendSubtreeTransformsToChromeMainThread(const AsyncPanZoomController * aAncestor)3669 void APZCTreeManager::SendSubtreeTransformsToChromeMainThread(
3670 const AsyncPanZoomController* aAncestor) {
3671 RefPtr<GeckoContentController> controller =
3672 GetContentController(mRootLayersId);
3673 if (!controller) {
3674 return;
3675 }
3676 nsTArray<MatrixMessage> messages;
3677 bool underAncestor = (aAncestor == nullptr);
3678 {
3679 RecursiveMutexAutoLock lock(mTreeLock);
3680 if (!mRootNode) {
3681 // Event dispatched during shutdown, after ClearTree().
3682 // Note, mRootNode needs to be checked with mTreeLock held.
3683 return;
3684 }
3685 // This formulation duplicates matrix multiplications closer
3686 // to the root of the tree. For now, aiming for separation
3687 // of concerns rather than minimum number of multiplications.
3688 ForEachNode<ReverseIterator>(
3689 mRootNode.get(),
3690 [&](HitTestingTreeNode* aNode) {
3691 bool atAncestor = (aAncestor && aNode->GetApzc() == aAncestor);
3692 MOZ_ASSERT(!(underAncestor && atAncestor));
3693 underAncestor |= atAncestor;
3694 if (!underAncestor) {
3695 return;
3696 }
3697 LayersId layersId = aNode->GetLayersId();
3698 HitTestingTreeNode* parent = aNode->GetParent();
3699 if (!parent) {
3700 messages.AppendElement(MatrixMessage(Some(LayerToScreenMatrix4x4()),
3701 ScreenRect(), layersId));
3702 } else if (layersId != parent->GetLayersId()) {
3703 if (mDetachedLayersIds.find(layersId) != mDetachedLayersIds.end()) {
3704 messages.AppendElement(
3705 MatrixMessage(Nothing(), ScreenRect(), layersId));
3706 } else {
3707 messages.AppendElement(MatrixMessage(
3708 Some(parent->GetTransformToGecko()),
3709 parent->GetRemoteDocumentScreenRect(), layersId));
3710 }
3711 }
3712 },
3713 [&](HitTestingTreeNode* aNode) {
3714 bool atAncestor = (aAncestor && aNode->GetApzc() == aAncestor);
3715 if (atAncestor) {
3716 MOZ_ASSERT(underAncestor);
3717 underAncestor = false;
3718 }
3719 });
3720 }
3721 controller->NotifyLayerTransforms(messages);
3722 }
3723
SetFixedLayerMargins(ScreenIntCoord aTop,ScreenIntCoord aBottom)3724 void APZCTreeManager::SetFixedLayerMargins(ScreenIntCoord aTop,
3725 ScreenIntCoord aBottom) {
3726 MutexAutoLock lock(mMapLock);
3727 mCompositorFixedLayerMargins.top = aTop;
3728 mCompositorFixedLayerMargins.bottom = aBottom;
3729 }
3730
3731 /*static*/
ComputeTransformForScrollThumb(const LayerToParentLayerMatrix4x4 & aCurrentTransform,const Matrix4x4 & aScrollableContentTransform,AsyncPanZoomController * aApzc,const FrameMetrics & aMetrics,const ScrollbarData & aScrollbarData,bool aScrollbarIsDescendant,AsyncTransformComponentMatrix * aOutClipTransform)3732 LayerToParentLayerMatrix4x4 APZCTreeManager::ComputeTransformForScrollThumb(
3733 const LayerToParentLayerMatrix4x4& aCurrentTransform,
3734 const Matrix4x4& aScrollableContentTransform, AsyncPanZoomController* aApzc,
3735 const FrameMetrics& aMetrics, const ScrollbarData& aScrollbarData,
3736 bool aScrollbarIsDescendant,
3737 AsyncTransformComponentMatrix* aOutClipTransform) {
3738 // We only apply the transform if the scroll-target layer has non-container
3739 // children (i.e. when it has some possibly-visible content). This is to
3740 // avoid moving scroll-bars in the situation that only a scroll information
3741 // layer has been built for a scroll frame, as this would result in a
3742 // disparity between scrollbars and visible content.
3743 if (aMetrics.IsScrollInfoLayer()) {
3744 return LayerToParentLayerMatrix4x4{};
3745 }
3746
3747 MOZ_RELEASE_ASSERT(aApzc);
3748
3749 AsyncTransformComponentMatrix asyncTransform =
3750 aApzc->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing);
3751
3752 // |asyncTransform| represents the amount by which we have scrolled and
3753 // zoomed since the last paint. Because the scrollbar was sized and positioned
3754 // based on the painted content, we need to adjust it based on asyncTransform
3755 // so that it reflects what the user is actually seeing now.
3756 AsyncTransformComponentMatrix scrollbarTransform;
3757 if (*aScrollbarData.mDirection == ScrollDirection::eVertical) {
3758 const ParentLayerCoord asyncScrollY = asyncTransform._42;
3759 const float asyncZoomY = asyncTransform._22;
3760
3761 // The scroll thumb needs to be scaled in the direction of scrolling by the
3762 // inverse of the async zoom. This is because zooming in decreases the
3763 // fraction of the whole srollable rect that is in view.
3764 const float yScale = 1.f / asyncZoomY;
3765
3766 // Note: |metrics.GetZoom()| doesn't yet include the async zoom.
3767 const CSSToParentLayerScale effectiveZoom(aMetrics.GetZoom().yScale *
3768 asyncZoomY);
3769
3770 // Here we convert the scrollbar thumb ratio into a true unitless ratio by
3771 // dividing out the conversion factor from the scrollframe's parent's space
3772 // to the scrollframe's space.
3773 const float ratio = aScrollbarData.mThumbRatio /
3774 (aMetrics.GetPresShellResolution() * asyncZoomY);
3775 // The scroll thumb needs to be translated in opposite direction of the
3776 // async scroll. This is because scrolling down, which translates the layer
3777 // content up, should result in moving the scroll thumb down.
3778 ParentLayerCoord yTranslation = -asyncScrollY * ratio;
3779
3780 // The scroll thumb additionally needs to be translated to compensate for
3781 // the scale applied above. The origin with respect to which the scale is
3782 // applied is the origin of the entire scrollbar, rather than the origin of
3783 // the scroll thumb (meaning, for a vertical scrollbar it's at the top of
3784 // the composition bounds). This means that empty space above the thumb
3785 // is scaled too, effectively translating the thumb. We undo that
3786 // translation here.
3787 // (One can think of the adjustment being done to the translation here as
3788 // a change of basis. We have a method to help with that,
3789 // Matrix4x4::ChangeBasis(), but it wouldn't necessarily make the code
3790 // cleaner in this case).
3791 const CSSCoord thumbOrigin = (aMetrics.GetScrollOffset().y * ratio);
3792 const CSSCoord thumbOriginScaled = thumbOrigin * yScale;
3793 const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin;
3794 const ParentLayerCoord thumbOriginDeltaPL =
3795 thumbOriginDelta * effectiveZoom;
3796 yTranslation -= thumbOriginDeltaPL;
3797
3798 scrollbarTransform.PostScale(1.f, yScale, 1.f);
3799 scrollbarTransform.PostTranslate(0, yTranslation, 0);
3800 }
3801 if (*aScrollbarData.mDirection == ScrollDirection::eHorizontal) {
3802 // See detailed comments under the eVertical case.
3803
3804 const ParentLayerCoord asyncScrollX = asyncTransform._41;
3805 const float asyncZoomX = asyncTransform._11;
3806
3807 const float xScale = 1.f / asyncZoomX;
3808
3809 const CSSToParentLayerScale effectiveZoom(aMetrics.GetZoom().xScale *
3810 asyncZoomX);
3811
3812 const float ratio = aScrollbarData.mThumbRatio /
3813 (aMetrics.GetPresShellResolution() * asyncZoomX);
3814 ParentLayerCoord xTranslation = -asyncScrollX * ratio;
3815
3816 const CSSCoord thumbOrigin = (aMetrics.GetScrollOffset().x * ratio);
3817 const CSSCoord thumbOriginScaled = thumbOrigin * xScale;
3818 const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin;
3819 const ParentLayerCoord thumbOriginDeltaPL =
3820 thumbOriginDelta * effectiveZoom;
3821 xTranslation -= thumbOriginDeltaPL;
3822
3823 scrollbarTransform.PostScale(xScale, 1.f, 1.f);
3824 scrollbarTransform.PostTranslate(xTranslation, 0, 0);
3825 }
3826
3827 LayerToParentLayerMatrix4x4 transform =
3828 aCurrentTransform * scrollbarTransform;
3829
3830 AsyncTransformComponentMatrix compensation;
3831 // If the scrollbar layer is a child of the content it is a scrollbar for,
3832 // then we need to adjust for any async transform (including an overscroll
3833 // transform) on the content. This needs to be cancelled out because layout
3834 // positions and sizes the scrollbar on the assumption that there is no async
3835 // transform, and without this adjustment the scrollbar will end up in the
3836 // wrong place.
3837 //
3838 // Note that since the async transform is applied on top of the content's
3839 // regular transform, we need to make sure to unapply the async transform in
3840 // the same coordinate space. This requires applying the content transform
3841 // and then unapplying it after unapplying the async transform.
3842 if (aScrollbarIsDescendant) {
3843 AsyncTransformComponentMatrix overscroll =
3844 aApzc->GetOverscrollTransform(AsyncPanZoomController::eForCompositing);
3845 Matrix4x4 asyncUntransform =
3846 (asyncTransform * overscroll).Inverse().ToUnknownMatrix();
3847 const Matrix4x4& contentTransform = aScrollableContentTransform;
3848 Matrix4x4 contentUntransform = contentTransform.Inverse();
3849
3850 compensation *= ViewAs<AsyncTransformComponentMatrix>(
3851 contentTransform * asyncUntransform * contentUntransform);
3852
3853 // Pass the total compensation out to the caller so that it can use it
3854 // to transform clip transforms as needed.
3855 if (aOutClipTransform) {
3856 *aOutClipTransform = compensation;
3857 }
3858 }
3859 transform = transform * compensation;
3860
3861 return transform;
3862 }
3863
GetSampler() const3864 APZSampler* APZCTreeManager::GetSampler() const {
3865 // We should always have a sampler here, since in practice the sampler
3866 // is destroyed at the same time that this APZCTreeMAnager instance is.
3867 MOZ_ASSERT(mSampler);
3868 return mSampler;
3869 }
3870
AssertOnSamplerThread()3871 void APZCTreeManager::AssertOnSamplerThread() {
3872 GetSampler()->AssertOnSamplerThread();
3873 }
3874
GetUpdater() const3875 APZUpdater* APZCTreeManager::GetUpdater() const {
3876 // We should always have an updater here, since in practice the updater
3877 // is destroyed at the same time that this APZCTreeManager instance is.
3878 MOZ_ASSERT(mUpdater);
3879 return mUpdater;
3880 }
3881
AssertOnUpdaterThread()3882 void APZCTreeManager::AssertOnUpdaterThread() {
3883 GetUpdater()->AssertOnUpdaterThread();
3884 }
3885
LockTree()3886 void APZCTreeManager::LockTree() {
3887 AssertOnUpdaterThread();
3888 mTreeLock.Lock();
3889 }
3890
UnlockTree()3891 void APZCTreeManager::UnlockTree() {
3892 AssertOnUpdaterThread();
3893 mTreeLock.Unlock();
3894 }
3895
SetDPI(float aDpiValue)3896 void APZCTreeManager::SetDPI(float aDpiValue) {
3897 APZThreadUtils::AssertOnControllerThread();
3898 mDPI = aDpiValue;
3899 }
3900
GetDPI() const3901 float APZCTreeManager::GetDPI() const {
3902 APZThreadUtils::AssertOnControllerThread();
3903 return mDPI;
3904 }
3905
FixedPositionInfo(const HitTestingTreeNode * aNode)3906 APZCTreeManager::FixedPositionInfo::FixedPositionInfo(
3907 const HitTestingTreeNode* aNode) {
3908 mFixedPositionAnimationId = aNode->GetFixedPositionAnimationId();
3909 mFixedPosSides = aNode->GetFixedPosSides();
3910 mFixedPosTarget = aNode->GetFixedPosTarget();
3911 mLayersId = aNode->GetLayersId();
3912 }
3913
StickyPositionInfo(const HitTestingTreeNode * aNode)3914 APZCTreeManager::StickyPositionInfo::StickyPositionInfo(
3915 const HitTestingTreeNode* aNode) {
3916 mStickyPositionAnimationId = aNode->GetStickyPositionAnimationId();
3917 mFixedPosSides = aNode->GetFixedPosSides();
3918 mStickyPosTarget = aNode->GetStickyPosTarget();
3919 mLayersId = aNode->GetLayersId();
3920 mStickyScrollRangeInner = aNode->GetStickyScrollRangeInner();
3921 mStickyScrollRangeOuter = aNode->GetStickyScrollRangeOuter();
3922 }
3923
TargetIsConfirmedRoot() const3924 bool APZCTreeManager::HitTestResult::TargetIsConfirmedRoot() const {
3925 CompositorHitTestInfo impreciseHitAreaFlags(
3926 CompositorHitTestFlags::eIrregularArea,
3927 CompositorHitTestFlags::eInactiveScrollframe);
3928 return (mHitResult & impreciseHitAreaFlags).isEmpty() &&
3929 mTargetApzc->IsRootContent();
3930 }
3931
3932 } // namespace layers
3933 } // namespace mozilla
3934