Bug 1715179 - Allow double-tap-to-zoom to zoom in contents inside OOP iframes. r...
[gecko.git] / gfx / layers / apz / src / APZCTreeManager.cpp
blob55604cfda2a6aa557d2b81e6c85298f65c16e516
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/. */
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 "WRHitTester.h" // for WRHitTester
18 #include "apz/src/APZUtils.h"
19 #include "mozilla/RecursiveMutex.h"
20 #include "mozilla/dom/MouseEventBinding.h" // for MouseEvent constants
21 #include "mozilla/dom/BrowserParent.h" // for AreRecordReplayTabsActive
22 #include "mozilla/dom/Touch.h" // for Touch
23 #include "mozilla/gfx/CompositorHitTestInfo.h"
24 #include "mozilla/gfx/LoggingConstants.h"
25 #include "mozilla/gfx/gfxVars.h" // for gfxVars
26 #include "mozilla/gfx/GPUParent.h" // for GPUParent
27 #include "mozilla/gfx/Logging.h" // for gfx::TreeLog
28 #include "mozilla/gfx/Point.h" // for Point
29 #include "mozilla/layers/APZSampler.h" // for APZSampler
30 #include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread, etc
31 #include "mozilla/layers/APZUpdater.h" // for APZUpdater
32 #include "mozilla/layers/APZUtils.h" // for AsyncTransform
33 #include "mozilla/layers/AsyncDragMetrics.h" // for AsyncDragMetrics
34 #include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc
35 #include "mozilla/layers/DoubleTapToZoom.h" // for ZoomTarget
36 #include "mozilla/layers/MatrixMessage.h"
37 #include "mozilla/layers/UiCompositorControllerParent.h"
38 #include "mozilla/layers/WebRenderScrollDataWrapper.h"
39 #include "mozilla/MouseEvents.h"
40 #include "mozilla/mozalloc.h" // for operator new
41 #include "mozilla/Preferences.h" // for Preferences
42 #include "mozilla/StaticPrefs_accessibility.h"
43 #include "mozilla/StaticPrefs_apz.h"
44 #include "mozilla/StaticPrefs_layout.h"
45 #include "mozilla/ToString.h"
46 #include "mozilla/TouchEvents.h"
47 #include "mozilla/EventStateManager.h" // for WheelPrefs
48 #include "mozilla/webrender/WebRenderAPI.h"
49 #include "nsDebug.h" // for NS_WARNING
50 #include "nsPoint.h" // for nsIntPoint
51 #include "nsThreadUtils.h" // for NS_IsMainThread
52 #include "ScrollThumbUtils.h" // for ComputeTransformForScrollThumb
53 #include "OverscrollHandoffState.h" // for OverscrollHandoffState
54 #include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch, etc
55 #include "Units.h" // for ParentlayerPixel
56 #include "GestureEventListener.h" // for GestureEventListener::setLongTapEnabled
57 #include "UnitTransforms.h" // for ViewAs
59 mozilla::LazyLogModule mozilla::layers::APZCTreeManager::sLog("apz.manager");
60 #define APZCTM_LOG(...) \
61 MOZ_LOG(APZCTreeManager::sLog, LogLevel::Debug, (__VA_ARGS__))
63 static mozilla::LazyLogModule sApzKeyLog("apz.key");
64 #define APZ_KEY_LOG(...) MOZ_LOG(sApzKeyLog, LogLevel::Debug, (__VA_ARGS__))
66 namespace mozilla {
67 namespace layers {
69 using mozilla::gfx::CompositorHitTestFlags;
70 using mozilla::gfx::CompositorHitTestInfo;
71 using mozilla::gfx::CompositorHitTestInvisibleToHit;
72 using mozilla::gfx::LOG_DEFAULT;
74 typedef mozilla::gfx::Point Point;
75 typedef mozilla::gfx::Point4D Point4D;
76 typedef mozilla::gfx::Matrix4x4 Matrix4x4;
78 typedef CompositorBridgeParent::LayerTreeState LayerTreeState;
80 struct APZCTreeManager::TreeBuildingState {
81 TreeBuildingState(LayersId aRootLayersId, bool aIsFirstPaint,
82 LayersId aOriginatingLayersId, APZTestData* aTestData,
83 uint32_t aPaintSequence)
84 : mIsFirstPaint(aIsFirstPaint),
85 mOriginatingLayersId(aOriginatingLayersId),
86 mPaintLogger(aTestData, aPaintSequence) {
87 CompositorBridgeParent::CallWithIndirectShadowTree(
88 aRootLayersId, [this](LayerTreeState& aState) -> void {
89 mCompositorController = aState.GetCompositorController();
90 });
93 typedef std::unordered_map<AsyncPanZoomController*, gfx::Matrix4x4>
94 DeferredTransformMap;
96 // State that doesn't change as we recurse in the tree building
97 RefPtr<CompositorController> mCompositorController;
98 const bool mIsFirstPaint;
99 const LayersId mOriginatingLayersId;
100 const APZPaintLogHelper mPaintLogger;
102 // State that is updated as we perform the tree build
104 // A list of nodes that need to be destroyed at the end of the tree building.
105 // This is initialized with all nodes in the old tree, and nodes are removed
106 // from it as we reuse them in the new tree.
107 nsTArray<RefPtr<HitTestingTreeNode>> mNodesToDestroy;
109 // This map is populated as we place APZCs into the new tree. Its purpose is
110 // to facilitate re-using the same APZC for different layers that scroll
111 // together (and thus have the same ScrollableLayerGuid). The presShellId
112 // doesn't matter for this purpose, and we move the map to the APZCTreeManager
113 // after we're done building, so it's useful to have the presshell-ignoring
114 // map for that.
115 std::unordered_map<ScrollableLayerGuid, ApzcMapData,
116 ScrollableLayerGuid::HashIgnoringPresShellFn,
117 ScrollableLayerGuid::EqualIgnoringPresShellFn>
118 mApzcMap;
120 // This is populated with all the HitTestingTreeNodes that are scroll thumbs
121 // and have a scrollthumb animation id (which indicates that they need to be
122 // sampled for WebRender on the sampler thread).
123 std::vector<HitTestingTreeNode*> mScrollThumbs;
124 // This is populated with all the scroll target nodes. We use in conjunction
125 // with mScrollThumbs to build APZCTreeManager::mScrollThumbInfo.
126 std::unordered_map<ScrollableLayerGuid, HitTestingTreeNode*,
127 ScrollableLayerGuid::HashIgnoringPresShellFn,
128 ScrollableLayerGuid::EqualIgnoringPresShellFn>
129 mScrollTargets;
131 // During the tree building process, the perspective transform component
132 // of the ancestor transforms of some APZCs can be "deferred" to their
133 // children, meaning they are added to the children's ancestor transforms
134 // instead. Those deferred transforms are tracked here.
135 DeferredTransformMap mPerspectiveTransformsDeferredToChildren;
137 // As we recurse down through the tree, this picks up the zoom animation id
138 // from a node in the layer tree, and propagates it downwards to the nearest
139 // APZC instance that is for an RCD node. Generally it will be set on the
140 // root node of the layers (sub-)tree, which may not be same as the RCD node
141 // for the subtree, and so we need this mechanism to ensure it gets propagated
142 // to the RCD's APZC instance. Once it is set on the APZC instance, the value
143 // is cleared back to Nothing(). Note that this is only used in the WebRender
144 // codepath.
145 Maybe<uint64_t> mZoomAnimationId;
147 // See corresponding members of APZCTreeManager. These are the same thing, but
148 // on the tree-walking state. They are populated while walking the tree in
149 // a layers update, and then moved into APZCTreeManager.
150 std::vector<FixedPositionInfo> mFixedPositionInfo;
151 std::vector<RootScrollbarInfo> mRootScrollbarInfo;
152 std::vector<StickyPositionInfo> mStickyPositionInfo;
154 // As we recurse down through reflayers in the tree, this picks up the
155 // cumulative EventRegionsOverride flags from the reflayers, and is used to
156 // apply them to descendant layers.
157 std::stack<EventRegionsOverride> mOverrideFlags;
160 class APZCTreeManager::CheckerboardFlushObserver : public nsIObserver {
161 public:
162 NS_DECL_ISUPPORTS
163 NS_DECL_NSIOBSERVER
165 explicit CheckerboardFlushObserver(APZCTreeManager* aTreeManager)
166 : mTreeManager(aTreeManager) {
167 MOZ_ASSERT(NS_IsMainThread());
168 nsCOMPtr<nsIObserverService> obsSvc =
169 mozilla::services::GetObserverService();
170 MOZ_ASSERT(obsSvc);
171 if (obsSvc) {
172 obsSvc->AddObserver(this, "APZ:FlushActiveCheckerboard", false);
176 void Unregister() {
177 MOZ_ASSERT(NS_IsMainThread());
178 nsCOMPtr<nsIObserverService> obsSvc =
179 mozilla::services::GetObserverService();
180 if (obsSvc) {
181 obsSvc->RemoveObserver(this, "APZ:FlushActiveCheckerboard");
183 mTreeManager = nullptr;
186 protected:
187 virtual ~CheckerboardFlushObserver() = default;
189 private:
190 RefPtr<APZCTreeManager> mTreeManager;
193 NS_IMPL_ISUPPORTS(APZCTreeManager::CheckerboardFlushObserver, nsIObserver)
195 NS_IMETHODIMP
196 APZCTreeManager::CheckerboardFlushObserver::Observe(nsISupports* aSubject,
197 const char* aTopic,
198 const char16_t*) {
199 MOZ_ASSERT(NS_IsMainThread());
200 MOZ_ASSERT(mTreeManager.get());
202 RecursiveMutexAutoLock lock(mTreeManager->mTreeLock);
203 if (mTreeManager->mRootNode) {
204 ForEachNode<ReverseIterator>(
205 mTreeManager->mRootNode.get(), [](HitTestingTreeNode* aNode) {
206 if (aNode->IsPrimaryHolder()) {
207 MOZ_ASSERT(aNode->GetApzc());
208 aNode->GetApzc()->FlushActiveCheckerboardReport();
212 if (XRE_IsGPUProcess()) {
213 if (gfx::GPUParent* gpu = gfx::GPUParent::GetSingleton()) {
214 nsCString topic("APZ:FlushActiveCheckerboard:Done");
215 Unused << gpu->SendNotifyUiObservers(topic);
217 } else {
218 MOZ_ASSERT(XRE_IsParentProcess());
219 nsCOMPtr<nsIObserverService> obsSvc =
220 mozilla::services::GetObserverService();
221 if (obsSvc) {
222 obsSvc->NotifyObservers(nullptr, "APZ:FlushActiveCheckerboard:Done",
223 nullptr);
226 return NS_OK;
230 * A RAII class used for setting the focus sequence number on input events
231 * as they are being processed. Any input event is assumed to be potentially
232 * focus changing unless explicitly marked otherwise.
234 class MOZ_RAII AutoFocusSequenceNumberSetter {
235 public:
236 AutoFocusSequenceNumberSetter(FocusState& aFocusState, InputData& aEvent)
237 : mFocusState(aFocusState), mEvent(aEvent), mMayChangeFocus(true) {}
239 void MarkAsNonFocusChanging() { mMayChangeFocus = false; }
241 ~AutoFocusSequenceNumberSetter() {
242 if (mMayChangeFocus) {
243 mFocusState.ReceiveFocusChangingEvent();
245 APZ_KEY_LOG(
246 "Marking input with type=%d as focus changing with seq=%" PRIu64 "\n",
247 static_cast<int>(mEvent.mInputType),
248 mFocusState.LastAPZProcessedEvent());
249 } else {
250 APZ_KEY_LOG(
251 "Marking input with type=%d as non focus changing with seq=%" PRIu64
252 "\n",
253 static_cast<int>(mEvent.mInputType),
254 mFocusState.LastAPZProcessedEvent());
257 mEvent.mFocusSequenceNumber = mFocusState.LastAPZProcessedEvent();
260 private:
261 FocusState& mFocusState;
262 InputData& mEvent;
263 bool mMayChangeFocus;
266 APZCTreeManager::APZCTreeManager(LayersId aRootLayersId,
267 UniquePtr<IAPZHitTester> aHitTester)
268 : mTestSampleTime(Nothing(), "APZCTreeManager::mTestSampleTime"),
269 mInputQueue(new InputQueue()),
270 mMapLock("APZCMapLock"),
271 mRootLayersId(aRootLayersId),
272 mSampler(nullptr),
273 mUpdater(nullptr),
274 mTreeLock("APZCTreeLock"),
275 mRetainedTouchIdentifier(-1),
276 mInScrollbarTouchDrag(false),
277 mCurrentMousePosition(ScreenPoint(),
278 "APZCTreeManager::mCurrentMousePosition"),
279 mApzcTreeLog("apzctree"),
280 mTestDataLock("APZTestDataLock"),
281 mDPI(160.0),
282 mHitTester(std::move(aHitTester)),
283 mScrollGenerationLock("APZScrollGenerationLock") {
284 AsyncPanZoomController::InitializeGlobalState();
285 mApzcTreeLog.ConditionOnPrefFunction(StaticPrefs::apz_printtree);
287 if (!mHitTester) {
288 mHitTester = MakeUnique<WRHitTester>();
290 mHitTester->Initialize(this);
293 APZCTreeManager::~APZCTreeManager() = default;
295 void APZCTreeManager::Init() {
296 RefPtr<APZCTreeManager> self(this);
297 NS_DispatchToMainThread(NS_NewRunnableFunction(
298 "layers::APZCTreeManager::Init",
299 [self] { self->mFlushObserver = new CheckerboardFlushObserver(self); }));
302 already_AddRefed<APZCTreeManager> APZCTreeManager::Create(
303 LayersId aRootLayersId, UniquePtr<IAPZHitTester> aHitTester) {
304 RefPtr<APZCTreeManager> manager =
305 new APZCTreeManager(aRootLayersId, std::move(aHitTester));
306 manager->Init();
307 return manager.forget();
310 void APZCTreeManager::SetSampler(APZSampler* aSampler) {
311 // We're either setting the sampler or clearing it
312 MOZ_ASSERT((mSampler == nullptr) != (aSampler == nullptr));
313 mSampler = aSampler;
316 void APZCTreeManager::SetUpdater(APZUpdater* aUpdater) {
317 // We're either setting the updater or clearing it
318 MOZ_ASSERT((mUpdater == nullptr) != (aUpdater == nullptr));
319 mUpdater = aUpdater;
322 void APZCTreeManager::NotifyLayerTreeAdopted(
323 LayersId aLayersId, const RefPtr<APZCTreeManager>& aOldApzcTreeManager) {
324 AssertOnUpdaterThread();
326 if (aOldApzcTreeManager) {
327 aOldApzcTreeManager->mFocusState.RemoveFocusTarget(aLayersId);
328 // While we could move the focus target information from the old APZC tree
329 // manager into this one, it's safer to not do that, as we'll probably have
330 // that information repopulated soon anyway (on the next layers update).
333 // There may be focus updates from the tab's content process in flight
334 // triggered by events that were processed by the old tree manager,
335 // which this tree manager does not expect.
336 // Resetting the focus state avoids this (the state will sync with content
337 // on the next focus update).
338 mFocusState.Reset();
340 UniquePtr<APZTestData> adoptedData;
341 if (aOldApzcTreeManager) {
342 MutexAutoLock lock(aOldApzcTreeManager->mTestDataLock);
343 auto it = aOldApzcTreeManager->mTestData.find(aLayersId);
344 if (it != aOldApzcTreeManager->mTestData.end()) {
345 adoptedData = std::move(it->second);
346 aOldApzcTreeManager->mTestData.erase(it);
349 if (adoptedData) {
350 MutexAutoLock lock(mTestDataLock);
351 mTestData[aLayersId] = std::move(adoptedData);
355 void APZCTreeManager::NotifyLayerTreeRemoved(LayersId aLayersId) {
356 AssertOnUpdaterThread();
358 mFocusState.RemoveFocusTarget(aLayersId);
360 { // scope lock
361 MutexAutoLock lock(mTestDataLock);
362 mTestData.erase(aLayersId);
366 already_AddRefed<AsyncPanZoomController> APZCTreeManager::NewAPZCInstance(
367 LayersId aLayersId, GeckoContentController* aController) {
368 return MakeRefPtr<AsyncPanZoomController>(
369 aLayersId, this, mInputQueue, aController,
370 AsyncPanZoomController::USE_GESTURE_DETECTOR)
371 .forget();
374 void APZCTreeManager::SetTestSampleTime(const Maybe<TimeStamp>& aTime) {
375 auto testSampleTime = mTestSampleTime.Lock();
376 testSampleTime.ref() = aTime;
379 SampleTime APZCTreeManager::GetFrameTime() {
380 auto testSampleTime = mTestSampleTime.Lock();
381 if (testSampleTime.ref()) {
382 return SampleTime::FromTest(*testSampleTime.ref());
384 return SampleTime::FromNow();
387 void APZCTreeManager::SetAllowedTouchBehavior(
388 uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aValues) {
389 if (!APZThreadUtils::IsControllerThread()) {
390 APZThreadUtils::RunOnControllerThread(
391 NewRunnableMethod<uint64_t,
392 StoreCopyPassByLRef<nsTArray<TouchBehaviorFlags>>>(
393 "layers::APZCTreeManager::SetAllowedTouchBehavior", this,
394 &APZCTreeManager::SetAllowedTouchBehavior, aInputBlockId,
395 aValues.Clone()));
396 return;
399 APZThreadUtils::AssertOnControllerThread();
401 mInputQueue->SetAllowedTouchBehavior(aInputBlockId, aValues);
404 void APZCTreeManager::SetBrowserGestureResponse(
405 uint64_t aInputBlockId, BrowserGestureResponse aResponse) {
406 if (!APZThreadUtils::IsControllerThread()) {
407 APZThreadUtils::RunOnControllerThread(
408 NewRunnableMethod<uint64_t, BrowserGestureResponse>(
409 "layers::APZCTreeManager::SetBrowserGestureResponse", this,
410 &APZCTreeManager::SetBrowserGestureResponse, aInputBlockId,
411 aResponse));
412 return;
415 APZThreadUtils::AssertOnControllerThread();
417 mInputQueue->SetBrowserGestureResponse(aInputBlockId, aResponse);
420 void APZCTreeManager::UpdateHitTestingTree(
421 const WebRenderScrollDataWrapper& aRoot, bool aIsFirstPaint,
422 LayersId aOriginatingLayersId, uint32_t aPaintSequenceNumber) {
423 AssertOnUpdaterThread();
425 RecursiveMutexAutoLock lock(mTreeLock);
427 // For testing purposes, we log some data to the APZTestData associated with
428 // the layers id that originated this update.
429 APZTestData* testData = nullptr;
430 if (StaticPrefs::apz_test_logging_enabled()) {
431 MutexAutoLock lock(mTestDataLock);
432 UniquePtr<APZTestData> ptr = MakeUnique<APZTestData>();
433 auto result =
434 mTestData.insert(std::make_pair(aOriginatingLayersId, std::move(ptr)));
435 testData = result.first->second.get();
436 testData->StartNewPaint(aPaintSequenceNumber);
439 TreeBuildingState state(mRootLayersId, aIsFirstPaint, aOriginatingLayersId,
440 testData, aPaintSequenceNumber);
442 // We do this business with collecting the entire tree into an array because
443 // otherwise it's very hard to determine which APZC instances need to be
444 // destroyed. In the worst case, there are two scenarios: (a) a layer with an
445 // APZC is removed from the layer tree and (b) a layer with an APZC is moved
446 // in the layer tree from one place to a completely different place. In
447 // scenario (a) we would want to destroy the APZC while walking the layer tree
448 // and noticing that the layer/APZC is no longer there. But if we do that then
449 // we run into a problem in scenario (b) because we might encounter that layer
450 // later during the walk. To handle both of these we have to 'remember' that
451 // the layer was not found, and then do the destroy only at the end of the
452 // tree walk after we are sure that the layer was removed and not just
453 // transplanted elsewhere. Doing that as part of a recursive tree walk is hard
454 // and so maintaining a list and removing APZCs that are still alive is much
455 // simpler.
456 ForEachNode<ReverseIterator>(mRootNode.get(),
457 [&state](HitTestingTreeNode* aNode) {
458 state.mNodesToDestroy.AppendElement(aNode);
460 mRootNode = nullptr;
461 Maybe<LayersId> asyncZoomContainerSubtree = Nothing();
462 int asyncZoomContainerNestingDepth = 0;
463 bool haveNestedAsyncZoomContainers = false;
464 nsTArray<LayersId> subtreesWithRootContentOutsideAsyncZoomContainer;
466 if (aRoot) {
467 std::unordered_set<LayersId, LayersId::HashFn> seenLayersIds;
468 std::stack<gfx::TreeAutoIndent<gfx::LOG_CRITICAL>> indents;
469 std::stack<AncestorTransform> ancestorTransforms;
470 HitTestingTreeNode* parent = nullptr;
471 HitTestingTreeNode* next = nullptr;
472 LayersId layersId = mRootLayersId;
473 seenLayersIds.insert(mRootLayersId);
474 ancestorTransforms.push(AncestorTransform());
475 state.mOverrideFlags.push(EventRegionsOverride::NoOverride);
476 nsTArray<Maybe<ZoomConstraints>> zoomConstraintsStack;
478 // push a nothing to be used for anything outside an async zoom container
479 zoomConstraintsStack.AppendElement(Nothing());
481 mApzcTreeLog << "[start]\n";
482 mTreeLock.AssertCurrentThreadIn();
484 ForEachNode<ReverseIterator>(
485 aRoot,
486 [&](ScrollNode aLayerMetrics) {
487 if (auto asyncZoomContainerId =
488 aLayerMetrics.GetAsyncZoomContainerId()) {
489 if (asyncZoomContainerNestingDepth > 0) {
490 haveNestedAsyncZoomContainers = true;
492 asyncZoomContainerSubtree = Some(layersId);
493 ++asyncZoomContainerNestingDepth;
495 auto it = mZoomConstraints.find(
496 ScrollableLayerGuid(layersId, 0, *asyncZoomContainerId));
497 if (it != mZoomConstraints.end()) {
498 zoomConstraintsStack.AppendElement(Some(it->second));
499 } else {
500 zoomConstraintsStack.AppendElement(Nothing());
504 if (aLayerMetrics.Metrics().IsRootContent()) {
505 MutexAutoLock lock(mMapLock);
506 mGeckoFixedLayerMargins =
507 aLayerMetrics.Metrics().GetFixedLayerMargins();
508 } else {
509 MOZ_ASSERT(aLayerMetrics.Metrics().GetFixedLayerMargins() ==
510 ScreenMargin(),
511 "fixed-layer-margins should be 0 on non-root layer");
514 // Note that this check happens after the potential increment of
515 // asyncZoomContainerNestingDepth, to allow the root content
516 // metadata to be on the same node as the async zoom container.
517 if (aLayerMetrics.Metrics().IsRootContent() &&
518 asyncZoomContainerNestingDepth == 0) {
519 subtreesWithRootContentOutsideAsyncZoomContainer.AppendElement(
520 layersId);
523 HitTestingTreeNode* node = PrepareNodeForLayer(
524 lock, aLayerMetrics, aLayerMetrics.Metrics(), layersId,
525 zoomConstraintsStack.LastElement(), ancestorTransforms.top(),
526 parent, next, state);
527 MOZ_ASSERT(node);
528 AsyncPanZoomController* apzc = node->GetApzc();
529 aLayerMetrics.SetApzc(apzc);
531 // GetScrollbarAnimationId is only set when webrender is enabled,
532 // which limits the extra thumb mapping work to the webrender-enabled
533 // case where it is needed.
534 // Note also that when webrender is enabled, a "valid" animation id
535 // is always nonzero, so we don't need to worry about handling the
536 // case where WR is enabled and the animation id is zero.
537 if (node->GetScrollbarAnimationId()) {
538 if (node->IsScrollThumbNode()) {
539 state.mScrollThumbs.push_back(node);
540 } else if (node->IsScrollbarContainerNode()) {
541 // Only scrollbar containers for the root have an animation id.
542 state.mRootScrollbarInfo.emplace_back(
543 *(node->GetScrollbarAnimationId()),
544 node->GetScrollbarDirection());
548 // GetFixedPositionAnimationId is only set when webrender is enabled.
549 if (node->GetFixedPositionAnimationId().isSome()) {
550 state.mFixedPositionInfo.emplace_back(node);
552 // GetStickyPositionAnimationId is only set when webrender is enabled.
553 if (node->GetStickyPositionAnimationId().isSome()) {
554 state.mStickyPositionInfo.emplace_back(node);
556 if (apzc && node->IsPrimaryHolder()) {
557 state.mScrollTargets[apzc->GetGuid()] = node;
560 // Accumulate the CSS transform between layers that have an APZC.
561 // In the terminology of the big comment above
562 // APZCTreeManager::GetScreenToApzcTransform, if we are at layer M,
563 // then aAncestorTransform is NC * OC * PC, and we left-multiply MC
564 // and compute ancestorTransform to be MC * NC * OC * PC. This gets
565 // passed down as the ancestor transform to layer L when we recurse
566 // into the children below. If we are at a layer with an APZC, such as
567 // P, then we reset the ancestorTransform to just PC, to start the new
568 // accumulation as we go down.
569 AncestorTransform currentTransform{
570 aLayerMetrics.GetTransform(),
571 aLayerMetrics.TransformIsPerspective()};
572 if (!apzc) {
573 currentTransform = currentTransform * ancestorTransforms.top();
575 ancestorTransforms.push(currentTransform);
577 // Note that |node| at this point will not have any children,
578 // otherwise we we would have to set next to node->GetFirstChild().
579 MOZ_ASSERT(!node->GetFirstChild());
580 parent = node;
581 next = nullptr;
583 // Update the layersId if we have a new one
584 if (Maybe<LayersId> newLayersId = aLayerMetrics.GetReferentId()) {
585 layersId = *newLayersId;
586 seenLayersIds.insert(layersId);
588 // Propagate any event region override flags down into all
589 // descendant nodes from the reflayer that has the flag. This is an
590 // optimization to avoid having to walk up the tree to check the
591 // override flags. Note that we don't keep the flags on the reflayer
592 // itself, because the semantics of the flags are that they apply
593 // to all content in the layer subtree being referenced. This
594 // matters with the WR hit-test codepath, because this reflayer may
595 // be just one of many nodes associated with a particular APZC, and
596 // calling GetTargetNode with a guid may return any one of the
597 // nodes. If different nodes have different flags on them that can
598 // make the WR hit-test result incorrect, but being strict about
599 // only putting the flags on descendant layers avoids this problem.
600 state.mOverrideFlags.push(state.mOverrideFlags.top() |
601 aLayerMetrics.GetEventRegionsOverride());
604 indents.push(gfx::TreeAutoIndent<gfx::LOG_CRITICAL>(mApzcTreeLog));
606 [&](ScrollNode aLayerMetrics) {
607 if (aLayerMetrics.GetAsyncZoomContainerId()) {
608 --asyncZoomContainerNestingDepth;
609 zoomConstraintsStack.RemoveLastElement();
611 if (aLayerMetrics.GetReferentId()) {
612 state.mOverrideFlags.pop();
615 next = parent;
616 parent = parent->GetParent();
617 layersId = next->GetLayersId();
618 ancestorTransforms.pop();
619 indents.pop();
622 mApzcTreeLog << "[end]\n";
624 MOZ_ASSERT(
625 !asyncZoomContainerSubtree ||
626 !subtreesWithRootContentOutsideAsyncZoomContainer.Contains(
627 *asyncZoomContainerSubtree),
628 "If there is an async zoom container, all scroll nodes with root "
629 "content scroll metadata should be inside it");
630 MOZ_ASSERT(!haveNestedAsyncZoomContainers,
631 "Should not have nested async zoom container");
633 // If we have perspective transforms deferred to children, do another
634 // walk of the tree and actually apply them to the children.
635 // We can't do this "as we go" in the previous traversal, because by the
636 // time we realize we need to defer a perspective transform for an APZC,
637 // we may already have processed a previous layer (including children
638 // found in its subtree) that shares that APZC.
639 if (!state.mPerspectiveTransformsDeferredToChildren.empty()) {
640 ForEachNode<ReverseIterator>(
641 mRootNode.get(), [&state](HitTestingTreeNode* aNode) {
642 AsyncPanZoomController* apzc = aNode->GetApzc();
643 if (!apzc) {
644 return;
646 if (!aNode->IsPrimaryHolder()) {
647 return;
650 AsyncPanZoomController* parent = apzc->GetParent();
651 if (!parent) {
652 return;
655 auto it =
656 state.mPerspectiveTransformsDeferredToChildren.find(parent);
657 if (it != state.mPerspectiveTransformsDeferredToChildren.end()) {
658 apzc->SetAncestorTransform(AncestorTransform{
659 it->second * apzc->GetAncestorTransform(), false});
664 // Remove any layers ids for which we no longer have content from
665 // mDetachedLayersIds.
666 for (auto iter = mDetachedLayersIds.begin();
667 iter != mDetachedLayersIds.end();) {
668 // unordered_set::erase() invalidates the iterator pointing to the
669 // element being erased, but returns an iterator to the next element.
670 if (seenLayersIds.find(*iter) == seenLayersIds.end()) {
671 iter = mDetachedLayersIds.erase(iter);
672 } else {
673 ++iter;
678 // We do not support tree structures where the root node has siblings.
679 MOZ_ASSERT(!(mRootNode && mRootNode->GetPrevSibling()));
681 { // scope lock and update our mApzcMap before we destroy all the unused
682 // APZC instances
683 MutexAutoLock lock(mMapLock);
684 mApzcMap = std::move(state.mApzcMap);
686 for (auto& mapping : mApzcMap) {
687 AsyncPanZoomController* parent = mapping.second.apzc->GetParent();
688 mapping.second.parent = parent ? Some(parent->GetGuid()) : Nothing();
691 mScrollThumbInfo.clear();
692 // For non-webrender, state.mScrollThumbs will be empty so this will be a
693 // no-op.
694 for (HitTestingTreeNode* thumb : state.mScrollThumbs) {
695 MOZ_ASSERT(thumb->IsScrollThumbNode());
696 ScrollableLayerGuid targetGuid(thumb->GetLayersId(), 0,
697 thumb->GetScrollTargetId());
698 auto it = state.mScrollTargets.find(targetGuid);
699 if (it == state.mScrollTargets.end()) {
700 // It could be that |thumb| is a scrollthumb for content which didn't
701 // have an APZC, for example if the content isn't layerized. Regardless,
702 // we can't async-scroll it so we don't need to worry about putting it
703 // in mScrollThumbInfo.
704 continue;
706 HitTestingTreeNode* target = it->second;
707 mScrollThumbInfo.emplace_back(
708 *(thumb->GetScrollbarAnimationId()), thumb->GetTransform(),
709 thumb->GetScrollbarData(), targetGuid, target->GetTransform(),
710 target->IsAncestorOf(thumb));
713 mRootScrollbarInfo = std::move(state.mRootScrollbarInfo);
714 mFixedPositionInfo = std::move(state.mFixedPositionInfo);
715 mStickyPositionInfo = std::move(state.mStickyPositionInfo);
718 for (size_t i = 0; i < state.mNodesToDestroy.Length(); i++) {
719 APZCTM_LOG("Destroying node at %p with APZC %p\n",
720 state.mNodesToDestroy[i].get(),
721 state.mNodesToDestroy[i]->GetApzc());
722 state.mNodesToDestroy[i]->Destroy();
725 APZCTM_LOG("APZCTreeManager (%p)\n", this);
726 if (mRootNode && MOZ_LOG_TEST(sLog, LogLevel::Debug)) {
727 mRootNode->Dump(" ");
729 SendSubtreeTransformsToChromeMainThread(nullptr);
732 void APZCTreeManager::UpdateFocusState(LayersId aRootLayerTreeId,
733 LayersId aOriginatingLayersId,
734 const FocusTarget& aFocusTarget) {
735 AssertOnUpdaterThread();
737 if (!StaticPrefs::apz_keyboard_enabled_AtStartup()) {
738 return;
741 mFocusState.Update(aRootLayerTreeId, aOriginatingLayersId, aFocusTarget);
744 void APZCTreeManager::SampleForWebRender(const Maybe<VsyncId>& aVsyncId,
745 wr::TransactionWrapper& aTxn,
746 const SampleTime& aSampleTime) {
747 AssertOnSamplerThread();
748 MutexAutoLock lock(mMapLock);
750 RefPtr<WebRenderBridgeParent> wrBridgeParent;
751 RefPtr<CompositorController> controller;
752 CompositorBridgeParent::CallWithIndirectShadowTree(
753 mRootLayersId, [&](LayerTreeState& aState) -> void {
754 controller = aState.GetCompositorController();
755 wrBridgeParent = aState.mWrBridge;
758 const bool activeAnimations = AdvanceAnimationsInternal(lock, aSampleTime);
759 if (activeAnimations && controller) {
760 controller->ScheduleRenderOnCompositorThread(
761 wr::RenderReasons::ANIMATED_PROPERTY);
764 nsTArray<wr::WrTransformProperty> transforms;
766 // Sample async transforms on scrollable layers.
767 for (const auto& mapping : mApzcMap) {
768 AsyncPanZoomController* apzc = mapping.second.apzc;
770 if (Maybe<CompositionPayload> payload = apzc->NotifyScrollSampling()) {
771 if (wrBridgeParent && aVsyncId) {
772 wrBridgeParent->AddPendingScrollPayload(*payload, *aVsyncId);
776 if (StaticPrefs::apz_test_logging_enabled()) {
777 MutexAutoLock lock(mTestDataLock);
779 ScrollableLayerGuid guid = apzc->GetGuid();
780 auto it = mTestData.find(guid.mLayersId);
781 if (it != mTestData.end()) {
782 it->second->RecordSampledResult(
783 apzc->GetCurrentAsyncVisualViewport(
784 AsyncPanZoomController::eForCompositing)
785 .TopLeft(),
786 (aSampleTime.Time() - TimeStamp::ProcessCreation())
787 .ToMicroseconds(),
788 guid.mLayersId, guid.mScrollId);
792 if (Maybe<uint64_t> zoomAnimationId = apzc->GetZoomAnimationId()) {
793 // for now we only support zooming on root content APZCs
794 MOZ_ASSERT(apzc->IsRootContent());
796 LayoutDeviceToParentLayerScale zoom = apzc->GetCurrentPinchZoomScale(
797 AsyncPanZoomController::eForCompositing);
799 AsyncTransform asyncVisualTransform = apzc->GetCurrentAsyncTransform(
800 AsyncPanZoomController::eForCompositing,
801 AsyncTransformComponents{AsyncTransformComponent::eVisual});
803 transforms.AppendElement(wr::ToWrTransformProperty(
804 *zoomAnimationId, LayoutDeviceToParentLayerMatrix4x4::Scaling(
805 zoom.scale, zoom.scale, 1.0f) *
806 AsyncTransformComponentMatrix::Translation(
807 asyncVisualTransform.mTranslation)));
809 aTxn.UpdateIsTransformAsyncZooming(*zoomAnimationId,
810 apzc->IsAsyncZooming());
813 nsTArray<wr::SampledScrollOffset> sampledOffsets =
814 apzc->GetSampledScrollOffsets();
815 aTxn.UpdateScrollPosition(wr::AsPipelineId(apzc->GetGuid().mLayersId),
816 apzc->GetGuid().mScrollId, sampledOffsets);
818 #if defined(MOZ_WIDGET_ANDROID)
819 // Send the root frame metrics to java through the UIController
820 RefPtr<UiCompositorControllerParent> uiController =
821 UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayersId);
822 if (uiController &&
823 apzc->UpdateRootFrameMetricsIfChanged(mLastRootMetrics)) {
824 uiController->NotifyUpdateScreenMetrics(mLastRootMetrics);
826 #endif
829 // Now collect all the async transforms needed for the scrollthumbs.
830 for (const ScrollThumbInfo& info : mScrollThumbInfo) {
831 auto it = mApzcMap.find(info.mTargetGuid);
832 if (it == mApzcMap.end()) {
833 // It could be that |info| is a scrollthumb for content which didn't
834 // have an APZC, for example if the content isn't layerized. Regardless,
835 // we can't async-scroll it so we don't need to worry about putting it
836 // in mScrollThumbInfo.
837 continue;
839 AsyncPanZoomController* scrollTargetApzc = it->second.apzc;
840 MOZ_ASSERT(scrollTargetApzc);
841 LayerToParentLayerMatrix4x4 transform =
842 scrollTargetApzc->CallWithLastContentPaintMetrics(
843 [&](const FrameMetrics& aMetrics) {
844 return ComputeTransformForScrollThumb(
845 info.mThumbTransform * AsyncTransformMatrix(),
846 info.mTargetTransform.ToUnknownMatrix(), scrollTargetApzc,
847 aMetrics, info.mThumbData, info.mTargetIsAncestor);
849 transforms.AppendElement(
850 wr::ToWrTransformProperty(info.mThumbAnimationId, transform));
853 // Move the root scrollbar in response to the dynamic toolbar transition.
854 for (const RootScrollbarInfo& info : mRootScrollbarInfo) {
855 // We only care about the horizontal scrollbar.
856 if (info.mScrollDirection == ScrollDirection::eHorizontal) {
857 ScreenPoint translation =
858 apz::ComputeFixedMarginsOffset(GetCompositorFixedLayerMargins(lock),
859 SideBits::eBottom, ScreenMargin());
861 LayerToParentLayerMatrix4x4 transform =
862 LayerToParentLayerMatrix4x4::Translation(ViewAs<ParentLayerPixel>(
863 translation, PixelCastJustification::ScreenIsParentLayerForRoot));
865 transforms.AppendElement(
866 wr::ToWrTransformProperty(info.mScrollbarAnimationId, transform));
870 for (const FixedPositionInfo& info : mFixedPositionInfo) {
871 MOZ_ASSERT(info.mFixedPositionAnimationId.isSome());
872 if (!IsFixedToRootContent(info, lock)) {
873 continue;
876 ScreenPoint translation = apz::ComputeFixedMarginsOffset(
877 GetCompositorFixedLayerMargins(lock), info.mFixedPosSides,
878 mGeckoFixedLayerMargins);
880 LayerToParentLayerMatrix4x4 transform =
881 LayerToParentLayerMatrix4x4::Translation(ViewAs<ParentLayerPixel>(
882 translation, PixelCastJustification::ScreenIsParentLayerForRoot));
884 transforms.AppendElement(
885 wr::ToWrTransformProperty(*info.mFixedPositionAnimationId, transform));
888 for (const StickyPositionInfo& info : mStickyPositionInfo) {
889 MOZ_ASSERT(info.mStickyPositionAnimationId.isSome());
890 SideBits sides = SidesStuckToRootContent(
891 info, AsyncTransformConsumer::eForCompositing, lock);
892 if (sides == SideBits::eNone) {
893 continue;
896 ScreenPoint translation = apz::ComputeFixedMarginsOffset(
897 GetCompositorFixedLayerMargins(lock), sides,
898 // For sticky layers, we don't need to factor
899 // mGeckoFixedLayerMargins because Gecko doesn't shift the
900 // position of sticky elements for dynamic toolbar movements.
901 ScreenMargin());
903 LayerToParentLayerMatrix4x4 transform =
904 LayerToParentLayerMatrix4x4::Translation(ViewAs<ParentLayerPixel>(
905 translation, PixelCastJustification::ScreenIsParentLayerForRoot));
907 transforms.AppendElement(
908 wr::ToWrTransformProperty(*info.mStickyPositionAnimationId, transform));
911 aTxn.AppendTransformProperties(transforms);
914 ParentLayerRect APZCTreeManager::ComputeClippedCompositionBounds(
915 const MutexAutoLock& aProofOfMapLock, ClippedCompositionBoundsMap& aDestMap,
916 ScrollableLayerGuid aGuid) {
917 if (auto iter = aDestMap.find(aGuid); iter != aDestMap.end()) {
918 // We already computed it for this one, early-exit. This might happen
919 // because on a later iteration of mApzcMap we might encounter an ancestor
920 // of an APZC that we processed on an earlier iteration. In this case we
921 // would have computed the ancestor's clipped composition bounds when
922 // recursing up on the earlier iteration.
923 return iter->second;
926 ParentLayerRect bounds = mApzcMap[aGuid].apzc->GetCompositionBounds();
927 const auto& mapEntry = mApzcMap.find(aGuid);
928 MOZ_ASSERT(mapEntry != mApzcMap.end());
929 if (mapEntry->second.parent.isNothing()) {
930 // Recursion base case, where the APZC with guid `aGuid` has no parent.
931 // In this case, we don't need to clip `bounds` any further and can just
932 // early exit.
933 aDestMap.emplace(aGuid, bounds);
934 return bounds;
937 ScrollableLayerGuid parentGuid = mapEntry->second.parent.value();
938 auto parentBoundsEntry = aDestMap.find(parentGuid);
939 // If aDestMap doesn't contain the parent entry yet, we recurse to compute
940 // that one first.
941 ParentLayerRect parentClippedBounds =
942 (parentBoundsEntry == aDestMap.end())
943 ? ComputeClippedCompositionBounds(aProofOfMapLock, aDestMap,
944 parentGuid)
945 : parentBoundsEntry->second;
947 // The parent layer's async transform applies to the current layer to take
948 // `bounds` into the same coordinate space as `parentClippedBounds`. However,
949 // we're going to do the inverse operation and unapply this transform to
950 // `parentClippedBounds` to bring it into the same coordinate space as
951 // `bounds`.
952 AsyncTransform appliesToLayer =
953 mApzcMap[parentGuid].apzc->GetCurrentAsyncTransform(
954 AsyncPanZoomController::eForCompositing);
956 // Do the unapplication
957 LayerRect parentClippedBoundsInParentLayerSpace =
958 (parentClippedBounds - appliesToLayer.mTranslation) /
959 appliesToLayer.mScale;
961 // And then clip `bounds` by the parent's comp bounds in the current space.
962 bounds = bounds.Intersect(
963 ViewAs<ParentLayerPixel>(parentClippedBoundsInParentLayerSpace,
964 PixelCastJustification::MovingDownToChildren));
966 // Done!
967 aDestMap.emplace(aGuid, bounds);
968 return bounds;
971 bool APZCTreeManager::AdvanceAnimationsInternal(
972 const MutexAutoLock& aProofOfMapLock, const SampleTime& aSampleTime) {
973 ClippedCompositionBoundsMap clippedCompBounds;
974 bool activeAnimations = false;
975 for (const auto& mapping : mApzcMap) {
976 AsyncPanZoomController* apzc = mapping.second.apzc;
977 // Note that this call is recursive, but it early-exits if called again
978 // with the same guid. So this loop is still amortized O(n) with respect to
979 // the number of APZCs.
980 ParentLayerRect clippedBounds = ComputeClippedCompositionBounds(
981 aProofOfMapLock, clippedCompBounds, mapping.first);
983 apzc->ReportCheckerboard(aSampleTime, clippedBounds);
984 activeAnimations |= apzc->AdvanceAnimations(aSampleTime);
986 return activeAnimations;
989 void APZCTreeManager::PrintLayerInfo(const ScrollNode& aLayer) {
990 if (StaticPrefs::apz_printtree() && aLayer.Dump(mApzcTreeLog) > 0) {
991 mApzcTreeLog << "\n";
995 // mTreeLock is held, and checked with static analysis
996 void APZCTreeManager::AttachNodeToTree(HitTestingTreeNode* aNode,
997 HitTestingTreeNode* aParent,
998 HitTestingTreeNode* aNextSibling) {
999 if (aNextSibling) {
1000 aNextSibling->SetPrevSibling(aNode);
1001 } else if (aParent) {
1002 aParent->SetLastChild(aNode);
1003 } else {
1004 MOZ_ASSERT(!mRootNode);
1005 mRootNode = aNode;
1006 aNode->MakeRoot();
1010 already_AddRefed<HitTestingTreeNode> APZCTreeManager::RecycleOrCreateNode(
1011 const RecursiveMutexAutoLock& aProofOfTreeLock, TreeBuildingState& aState,
1012 AsyncPanZoomController* aApzc, LayersId aLayersId) {
1013 // Find a node without an APZC and return it. Note that unless the layer tree
1014 // actually changes, this loop should generally do an early-return on the
1015 // first iteration, so it should be cheap in the common case.
1016 for (int32_t i = aState.mNodesToDestroy.Length() - 1; i >= 0; i--) {
1017 RefPtr<HitTestingTreeNode> node = aState.mNodesToDestroy[i];
1018 if (node->IsRecyclable(aProofOfTreeLock)) {
1019 aState.mNodesToDestroy.RemoveElementAt(i);
1020 node->RecycleWith(aProofOfTreeLock, aApzc, aLayersId);
1021 return node.forget();
1024 RefPtr<HitTestingTreeNode> node =
1025 new HitTestingTreeNode(aApzc, false, aLayersId);
1026 return node.forget();
1029 void APZCTreeManager::StartScrollbarDrag(const ScrollableLayerGuid& aGuid,
1030 const AsyncDragMetrics& aDragMetrics) {
1031 if (!APZThreadUtils::IsControllerThread()) {
1032 APZThreadUtils::RunOnControllerThread(
1033 NewRunnableMethod<ScrollableLayerGuid, AsyncDragMetrics>(
1034 "layers::APZCTreeManager::StartScrollbarDrag", this,
1035 &APZCTreeManager::StartScrollbarDrag, aGuid, aDragMetrics));
1036 return;
1039 APZThreadUtils::AssertOnControllerThread();
1041 RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
1042 if (!apzc) {
1043 NotifyScrollbarDragRejected(aGuid);
1044 return;
1047 uint64_t inputBlockId = aDragMetrics.mDragStartSequenceNumber;
1048 mInputQueue->ConfirmDragBlock(inputBlockId, apzc, aDragMetrics);
1051 bool APZCTreeManager::StartAutoscroll(const ScrollableLayerGuid& aGuid,
1052 const ScreenPoint& aAnchorLocation) {
1053 APZThreadUtils::AssertOnControllerThread();
1055 RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
1056 if (!apzc) {
1057 if (XRE_IsGPUProcess()) {
1058 // If we're in the compositor process, the "return false" will be
1059 // ignored because the query comes over the PAPZCTreeManager protocol
1060 // via an async message. In this case, send an explicit rejection
1061 // message to content.
1062 NotifyAutoscrollRejected(aGuid);
1064 return false;
1067 apzc->StartAutoscroll(aAnchorLocation);
1068 return true;
1071 void APZCTreeManager::StopAutoscroll(const ScrollableLayerGuid& aGuid) {
1072 APZThreadUtils::AssertOnControllerThread();
1074 if (RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid)) {
1075 apzc->StopAutoscroll();
1079 void APZCTreeManager::NotifyScrollbarDragInitiated(
1080 uint64_t aDragBlockId, const ScrollableLayerGuid& aGuid,
1081 ScrollDirection aDirection) const {
1082 RefPtr<GeckoContentController> controller =
1083 GetContentController(aGuid.mLayersId);
1084 if (controller) {
1085 controller->NotifyAsyncScrollbarDragInitiated(aDragBlockId, aGuid.mScrollId,
1086 aDirection);
1090 void APZCTreeManager::NotifyScrollbarDragRejected(
1091 const ScrollableLayerGuid& aGuid) const {
1092 RefPtr<GeckoContentController> controller =
1093 GetContentController(aGuid.mLayersId);
1094 if (controller) {
1095 controller->NotifyAsyncScrollbarDragRejected(aGuid.mScrollId);
1099 void APZCTreeManager::NotifyAutoscrollRejected(
1100 const ScrollableLayerGuid& aGuid) const {
1101 RefPtr<GeckoContentController> controller =
1102 GetContentController(aGuid.mLayersId);
1103 MOZ_ASSERT(controller);
1104 controller->NotifyAsyncAutoscrollRejected(aGuid.mScrollId);
1107 void SetHitTestData(HitTestingTreeNode* aNode,
1108 const WebRenderScrollDataWrapper& aLayer,
1109 const EventRegionsOverride& aOverrideFlags) {
1110 aNode->SetHitTestData(aLayer.GetVisibleRegion(),
1111 aLayer.GetRemoteDocumentSize(),
1112 aLayer.GetTransformTyped(), aOverrideFlags,
1113 aLayer.GetAsyncZoomContainerId());
1116 HitTestingTreeNode* APZCTreeManager::PrepareNodeForLayer(
1117 const RecursiveMutexAutoLock& aProofOfTreeLock, const ScrollNode& aLayer,
1118 const FrameMetrics& aMetrics, LayersId aLayersId,
1119 const Maybe<ZoomConstraints>& aZoomConstraints,
1120 const AncestorTransform& aAncestorTransform, HitTestingTreeNode* aParent,
1121 HitTestingTreeNode* aNextSibling, TreeBuildingState& aState) {
1122 mTreeLock.AssertCurrentThreadIn(); // for static analysis
1123 bool needsApzc = true;
1124 if (!aMetrics.IsScrollable()) {
1125 needsApzc = false;
1128 // XXX: As a future optimization we can probably stick these things on the
1129 // TreeBuildingState, and update them as we change layers id during the
1130 // traversal
1131 RefPtr<GeckoContentController> geckoContentController;
1132 CompositorBridgeParent::CallWithIndirectShadowTree(
1133 aLayersId, [&](LayerTreeState& lts) -> void {
1134 geckoContentController = lts.mController;
1137 if (!geckoContentController) {
1138 needsApzc = false;
1141 if (Maybe<uint64_t> zoomAnimationId = aLayer.GetZoomAnimationId()) {
1142 aState.mZoomAnimationId = zoomAnimationId;
1145 RefPtr<HitTestingTreeNode> node = nullptr;
1146 if (!needsApzc) {
1147 // Note: if layer properties must be propagated to nodes, RecvUpdate in
1148 // LayerTransactionParent.cpp must ensure that APZ will be notified
1149 // when those properties change.
1150 node = RecycleOrCreateNode(aProofOfTreeLock, aState, nullptr, aLayersId);
1151 AttachNodeToTree(node, aParent, aNextSibling);
1152 SetHitTestData(node, aLayer, aState.mOverrideFlags.top());
1153 node->SetScrollbarData(aLayer.GetScrollbarAnimationId(),
1154 aLayer.GetScrollbarData());
1155 node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId(),
1156 aLayer.GetFixedPositionSides(),
1157 aLayer.GetFixedPositionAnimationId());
1158 node->SetStickyPosData(aLayer.GetStickyScrollContainerId(),
1159 aLayer.GetStickyScrollRangeOuter(),
1160 aLayer.GetStickyScrollRangeInner(),
1161 aLayer.GetStickyPositionAnimationId());
1162 PrintLayerInfo(aLayer);
1163 return node;
1166 RefPtr<AsyncPanZoomController> apzc;
1167 // If we get here, aLayer is a scrollable layer and somebody
1168 // has registered a GeckoContentController for it, so we need to ensure
1169 // it has an APZC instance to manage its scrolling.
1171 // aState.mApzcMap allows reusing the exact same APZC instance for different
1172 // layers with the same FrameMetrics data. This is needed because in some
1173 // cases content that is supposed to scroll together is split into multiple
1174 // layers because of e.g. non-scrolling content interleaved in z-index order.
1175 ScrollableLayerGuid guid(aLayersId, aMetrics.GetPresShellId(),
1176 aMetrics.GetScrollId());
1177 auto insertResult = aState.mApzcMap.insert(std::make_pair(
1178 guid,
1179 ApzcMapData{static_cast<AsyncPanZoomController*>(nullptr), Nothing()}));
1180 if (!insertResult.second) {
1181 apzc = insertResult.first->second.apzc;
1182 PrintLayerInfo(aLayer);
1184 APZCTM_LOG(
1185 "Found APZC %p for layer %p with identifiers %" PRIx64 " %" PRId64 "\n",
1186 apzc.get(), aLayer.GetLayer(), uint64_t(guid.mLayersId), guid.mScrollId);
1188 // If we haven't encountered a layer already with the same metrics, then we
1189 // need to do the full reuse-or-make-an-APZC algorithm, which is contained
1190 // inside the block below.
1191 if (apzc == nullptr) {
1192 apzc = aLayer.GetApzc();
1194 // If the content represented by the scrollable layer has changed (which may
1195 // be possible because of DLBI heuristics) then we don't want to keep using
1196 // the same old APZC for the new content. Also, when reparenting a tab into
1197 // a new window a layer might get moved to a different layer tree with a
1198 // different APZCTreeManager. In these cases we don't want to reuse the same
1199 // APZC, so null it out so we run through the code to find another one or
1200 // create one.
1201 if (apzc && (!apzc->Matches(guid) || !apzc->HasTreeManager(this))) {
1202 apzc = nullptr;
1205 // See if we can find an APZC from the previous tree that matches the
1206 // ScrollableLayerGuid from this layer. If there is one, then we know that
1207 // the layout of the page changed causing the layer tree to be rebuilt, but
1208 // the underlying content for the APZC is still there somewhere. Therefore,
1209 // we want to find the APZC instance and continue using it here.
1211 // We particularly want to find the primary-holder node from the previous
1212 // tree that matches, because we don't want that node to get destroyed. If
1213 // it does get destroyed, then the APZC will get destroyed along with it by
1214 // definition, but we want to keep that APZC around in the new tree.
1215 // We leave non-primary-holder nodes in the destroy list because we don't
1216 // care about those nodes getting destroyed.
1217 for (size_t i = 0; i < aState.mNodesToDestroy.Length(); i++) {
1218 RefPtr<HitTestingTreeNode> n = aState.mNodesToDestroy[i];
1219 if (n->IsPrimaryHolder() && n->GetApzc() && n->GetApzc()->Matches(guid)) {
1220 node = n;
1221 if (apzc != nullptr) {
1222 // If there is an APZC already then it should match the one from the
1223 // old primary-holder node
1224 MOZ_ASSERT(apzc == node->GetApzc());
1226 apzc = node->GetApzc();
1227 break;
1231 // The APZC we get off the layer may have been destroyed previously if the
1232 // layer was inactive or omitted from the layer tree for whatever reason
1233 // from a layers update. If it later comes back it will have a reference to
1234 // a destroyed APZC and so we need to throw that out and make a new one.
1235 bool newApzc = (apzc == nullptr || apzc->IsDestroyed());
1236 if (newApzc) {
1237 apzc = NewAPZCInstance(aLayersId, geckoContentController);
1238 apzc->SetCompositorController(aState.mCompositorController.get());
1239 MOZ_ASSERT(node == nullptr);
1240 node = new HitTestingTreeNode(apzc, true, aLayersId);
1241 } else {
1242 // If we are re-using a node for this layer clear the tree pointers
1243 // so that it doesn't continue pointing to nodes that might no longer
1244 // be in the tree. These pointers will get reset properly as we continue
1245 // building the tree. Also remove it from the set of nodes that are going
1246 // to be destroyed, because it's going to remain active.
1247 aState.mNodesToDestroy.RemoveElement(node);
1248 node->SetPrevSibling(nullptr);
1249 node->SetLastChild(nullptr);
1252 if (aMetrics.IsRootContent()) {
1253 apzc->SetZoomAnimationId(aState.mZoomAnimationId);
1254 aState.mZoomAnimationId = Nothing();
1257 APZCTM_LOG("Using APZC %p for layer %p with identifiers %" PRIx64
1258 " %" PRId64 "\n",
1259 apzc.get(), aLayer.GetLayer(), uint64_t(aLayersId),
1260 aMetrics.GetScrollId());
1262 apzc->NotifyLayersUpdated(aLayer.Metadata(), aState.mIsFirstPaint,
1263 aLayersId == aState.mOriginatingLayersId);
1265 // Since this is the first time we are encountering an APZC with this guid,
1266 // the node holding it must be the primary holder. It may be newly-created
1267 // or not, depending on whether it went through the newApzc branch above.
1268 MOZ_ASSERT(node->IsPrimaryHolder() && node->GetApzc() &&
1269 node->GetApzc()->Matches(guid));
1271 SetHitTestData(node, aLayer, aState.mOverrideFlags.top());
1272 apzc->SetAncestorTransform(aAncestorTransform);
1274 PrintLayerInfo(aLayer);
1276 // Bind the APZC instance into the tree of APZCs
1277 AttachNodeToTree(node, aParent, aNextSibling);
1279 // For testing, log the parent scroll id of every APZC that has a
1280 // parent. This allows test code to reconstruct the APZC tree.
1281 // Note that we currently only do this for APZCs in the layer tree
1282 // that originated the update, because the only identifying information
1283 // we are logging about APZCs is the scroll id, and otherwise we could
1284 // confuse APZCs from different layer trees with the same scroll id.
1285 if (aLayersId == aState.mOriginatingLayersId) {
1286 if (apzc->HasNoParentWithSameLayersId()) {
1287 aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
1288 "hasNoParentWithSameLayersId", true);
1289 } else {
1290 MOZ_ASSERT(apzc->GetParent());
1291 aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
1292 "parentScrollId",
1293 apzc->GetParent()->GetGuid().mScrollId);
1295 if (aMetrics.IsRootContent()) {
1296 aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(), "isRootContent",
1297 true);
1299 // Note that the async scroll offset is in ParentLayer pixels
1300 aState.mPaintLogger.LogTestData(
1301 aMetrics.GetScrollId(), "asyncScrollOffset",
1302 apzc->GetCurrentAsyncScrollOffset(
1303 AsyncPanZoomController::eForEventHandling));
1304 aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
1305 "hasAsyncKeyScrolled",
1306 apzc->TestHasAsyncKeyScrolled());
1309 // We must update the zoom constraints even if the apzc isn't new because it
1310 // might have moved.
1311 if (node->IsPrimaryHolder()) {
1312 if (aZoomConstraints) {
1313 apzc->UpdateZoomConstraints(*aZoomConstraints);
1315 #ifdef DEBUG
1316 auto it = mZoomConstraints.find(guid);
1317 if (it != mZoomConstraints.end()) {
1318 MOZ_ASSERT(it->second == *aZoomConstraints);
1320 } else {
1321 // We'd like to assert these things (if the first doesn't hold then at
1322 // least the second) but neither are not true because xul root content
1323 // gets zoomable zoom constraints, but which is not zoomable because it
1324 // doesn't have a root scroll frame.
1325 // clang-format off
1326 // MOZ_ASSERT(mZoomConstraints.find(guid) == mZoomConstraints.end());
1327 // auto it = mZoomConstraints.find(guid);
1328 // if (it != mZoomConstraints.end()) {
1329 // MOZ_ASSERT(!it->second.mAllowZoom && !it->second.mAllowDoubleTapZoom);
1330 // }
1331 // clang-format on
1332 #endif
1336 // Add a guid -> APZC mapping for the newly created APZC.
1337 insertResult.first->second.apzc = apzc;
1338 } else {
1339 // We already built an APZC earlier in this tree walk, but we have another
1340 // layer now that will also be using that APZC. The hit-test region on the
1341 // APZC needs to be updated to deal with the new layer's hit region.
1343 node = RecycleOrCreateNode(aProofOfTreeLock, aState, apzc, aLayersId);
1344 AttachNodeToTree(node, aParent, aNextSibling);
1346 // Even though different layers associated with a given APZC may be at
1347 // different levels in the layer tree (e.g. one being an uncle of another),
1348 // we require from Layout that the CSS transforms up to their common
1349 // ancestor be roughly the same. There are cases in which the transforms
1350 // are not exactly the same, for example if the parent is container layer
1351 // for an opacity, and this container layer has a resolution-induced scale
1352 // as its base transform and a prescale that is supposed to undo that scale.
1353 // Due to floating point inaccuracies those transforms can end up not quite
1354 // canceling each other. That's why we're using a fuzzy comparison here
1355 // instead of an exact one.
1356 // In addition, two ancestor transforms are allowed to differ if one of
1357 // them contains a perspective transform component and the other does not.
1358 // This represents situations where some content in a scrollable frame
1359 // is subject to a perspective transform and other content does not.
1360 // In such cases, go with the one that does not include the perspective
1361 // component; the perspective transform is remembered and applied to the
1362 // children instead.
1363 auto ancestorTransform = aAncestorTransform.CombinedTransform();
1364 auto existingAncestorTransform = apzc->GetAncestorTransform();
1365 if (!ancestorTransform.FuzzyEqualsMultiplicative(
1366 existingAncestorTransform)) {
1367 typedef TreeBuildingState::DeferredTransformMap::value_type PairType;
1368 if (!aAncestorTransform.ContainsPerspectiveTransform() &&
1369 !apzc->AncestorTransformContainsPerspective()) {
1370 // If this content is being presented in a paginated fashion (e.g.
1371 // print preview), the multiple layers may reflect multiple instances
1372 // of the same display item being rendered on different pages. In such
1373 // cases, it's expected that different instances can have different
1374 // transforms, since each page renders a different part of the item.
1375 if (!aLayer.Metadata().IsPaginatedPresentation()) {
1376 if (ancestorTransform.IsFinite() &&
1377 existingAncestorTransform.IsFinite()) {
1378 // Log separately from the assert because assert doesn't allow
1379 // printf-style arguments, but it's important for debugging that the
1380 // log identifies *which* scroll frame violated the condition.
1381 MOZ_LOG(sLog, LogLevel::Error,
1382 ("Two layers that scroll together have different ancestor "
1383 "transforms (guid=%s)",
1384 ToString(apzc->GetGuid()).c_str()));
1385 MOZ_ASSERT(false,
1386 "Two layers that scroll together have different "
1387 "ancestor transforms");
1388 } else {
1389 MOZ_ASSERT(ancestorTransform.IsFinite() ==
1390 existingAncestorTransform.IsFinite());
1393 } else if (!aAncestorTransform.ContainsPerspectiveTransform()) {
1394 aState.mPerspectiveTransformsDeferredToChildren.insert(
1395 PairType{apzc, apzc->GetAncestorTransformPerspective()});
1396 apzc->SetAncestorTransform(aAncestorTransform);
1397 } else {
1398 aState.mPerspectiveTransformsDeferredToChildren.insert(
1399 PairType{apzc, aAncestorTransform.GetPerspectiveTransform()});
1403 SetHitTestData(node, aLayer, aState.mOverrideFlags.top());
1406 // Note: if layer properties must be propagated to nodes, RecvUpdate in
1407 // LayerTransactionParent.cpp must ensure that APZ will be notified
1408 // when those properties change.
1409 node->SetScrollbarData(aLayer.GetScrollbarAnimationId(),
1410 aLayer.GetScrollbarData());
1411 node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId(),
1412 aLayer.GetFixedPositionSides(),
1413 aLayer.GetFixedPositionAnimationId());
1414 node->SetStickyPosData(aLayer.GetStickyScrollContainerId(),
1415 aLayer.GetStickyScrollRangeOuter(),
1416 aLayer.GetStickyScrollRangeInner(),
1417 aLayer.GetStickyPositionAnimationId());
1418 return node;
1421 template <typename PanGestureOrScrollWheelInput>
1422 static bool WillHandleInput(const PanGestureOrScrollWheelInput& aPanInput) {
1423 if (!XRE_IsParentProcess() || !NS_IsMainThread()) {
1424 return true;
1427 WidgetWheelEvent wheelEvent = aPanInput.ToWidgetEvent(nullptr);
1428 return APZInputBridge::ActionForWheelEvent(&wheelEvent).isSome();
1431 /*static*/
1432 void APZCTreeManager::FlushApzRepaints(LayersId aLayersId) {
1433 // Previously, paints were throttled and therefore this method was used to
1434 // ensure any pending paints were flushed. Now, paints are flushed
1435 // immediately, so it is safe to simply send a notification now.
1436 APZCTM_LOG("Flushing repaints for layers id 0x%" PRIx64 "\n",
1437 uint64_t(aLayersId));
1438 RefPtr<GeckoContentController> controller = GetContentController(aLayersId);
1439 #ifndef MOZ_WIDGET_ANDROID
1440 // On Android, this code is run in production and may actually get a nullptr
1441 // controller here. On other platforms this code is test-only and should never
1442 // get a nullptr.
1443 MOZ_ASSERT(controller);
1444 #endif
1445 if (controller) {
1446 controller->DispatchToRepaintThread(NewRunnableMethod(
1447 "layers::GeckoContentController::NotifyFlushComplete", controller,
1448 &GeckoContentController::NotifyFlushComplete));
1452 void APZCTreeManager::MarkAsDetached(LayersId aLayersId) {
1453 RecursiveMutexAutoLock lock(mTreeLock);
1454 mDetachedLayersIds.insert(aLayersId);
1457 static bool HasNonLockModifier(Modifiers aModifiers) {
1458 return (aModifiers &
1459 (MODIFIER_ALT | MODIFIER_ALTGRAPH | MODIFIER_CONTROL | MODIFIER_FN |
1460 MODIFIER_META | MODIFIER_SHIFT | MODIFIER_SYMBOL)) != 0;
1463 APZEventResult APZCTreeManager::ReceiveInputEvent(
1464 InputData& aEvent, InputBlockCallback&& aCallback) {
1465 APZThreadUtils::AssertOnControllerThread();
1466 InputHandlingState state{aEvent};
1468 // Use a RAII class for updating the focus sequence number of this event
1469 AutoFocusSequenceNumberSetter focusSetter(mFocusState, aEvent);
1471 switch (aEvent.mInputType) {
1472 case MULTITOUCH_INPUT: {
1473 MultiTouchInput& touchInput = aEvent.AsMultiTouchInput();
1474 ProcessTouchInput(state, touchInput);
1475 break;
1477 case MOUSE_INPUT: {
1478 MouseInput& mouseInput = aEvent.AsMouseInput();
1479 mouseInput.mHandledByAPZ = true;
1481 SetCurrentMousePosition(mouseInput.mOrigin);
1483 bool startsDrag = DragTracker::StartsDrag(mouseInput);
1484 if (startsDrag) {
1485 // If this is the start of a drag we need to unambiguously know if it's
1486 // going to land on a scrollbar or not. We can't apply an untransform
1487 // here without knowing that, so we need to ensure the untransform is
1488 // a no-op.
1489 FlushRepaintsToClearScreenToGeckoTransform();
1492 state.mHit = GetTargetAPZC(mouseInput.mOrigin);
1493 bool hitScrollbar = (bool)state.mHit.mScrollbarNode;
1495 // When the mouse is outside the window we still want to handle dragging
1496 // but we won't find an APZC. Fallback to root APZC then.
1497 { // scope lock
1498 RecursiveMutexAutoLock lock(mTreeLock);
1499 if (!state.mHit.mTargetApzc && mRootNode) {
1500 state.mHit.mTargetApzc = mRootNode->GetApzc();
1504 if (state.mHit.mTargetApzc) {
1505 if (StaticPrefs::apz_test_logging_enabled() &&
1506 mouseInput.mType == MouseInput::MOUSE_HITTEST) {
1507 ScrollableLayerGuid guid = state.mHit.mTargetApzc->GetGuid();
1509 MutexAutoLock lock(mTestDataLock);
1510 auto it = mTestData.find(guid.mLayersId);
1511 MOZ_ASSERT(it != mTestData.end());
1512 it->second->RecordHitResult(mouseInput.mOrigin, state.mHit.mHitResult,
1513 guid.mLayersId, guid.mScrollId);
1516 TargetConfirmationFlags confFlags{state.mHit.mHitResult};
1517 state.mResult = mInputQueue->ReceiveInputEvent(state.mHit.mTargetApzc,
1518 confFlags, mouseInput);
1520 // If we're starting an async scrollbar drag
1521 bool apzDragEnabled = StaticPrefs::apz_drag_enabled();
1522 if (apzDragEnabled && startsDrag && state.mHit.mScrollbarNode &&
1523 state.mHit.mScrollbarNode->IsScrollThumbNode() &&
1524 state.mHit.mScrollbarNode->GetScrollbarData()
1525 .mThumbIsAsyncDraggable) {
1526 SetupScrollbarDrag(mouseInput, state.mHit.mScrollbarNode,
1527 state.mHit.mTargetApzc.get());
1530 if (state.mResult.GetStatus() == nsEventStatus_eConsumeDoDefault) {
1531 // This input event is part of a drag block, so whether or not it is
1532 // directed at a scrollbar depends on whether the drag block started
1533 // on a scrollbar.
1534 hitScrollbar = mInputQueue->IsDragOnScrollbar(hitScrollbar);
1537 if (!hitScrollbar) {
1538 // The input was not targeted at a scrollbar, so we untransform it
1539 // like we do for other content. Scrollbars are "special" because they
1540 // have special handling in AsyncCompositionManager when resolution is
1541 // applied. TODO: we should find a better way to deal with this.
1542 ScreenToParentLayerMatrix4x4 transformToApzc =
1543 GetScreenToApzcTransform(state.mHit.mTargetApzc);
1544 ParentLayerToScreenMatrix4x4 transformToGecko =
1545 GetApzcToGeckoTransformForHit(state.mHit);
1546 ScreenToScreenMatrix4x4 outTransform =
1547 transformToApzc * transformToGecko;
1548 Maybe<ScreenPoint> untransformedRefPoint =
1549 UntransformBy(outTransform, mouseInput.mOrigin);
1550 if (untransformedRefPoint) {
1551 mouseInput.mOrigin = *untransformedRefPoint;
1553 } else {
1554 // Likewise, if the input was targeted at a scrollbar, we don't want
1555 // to apply the callback transform in the main thread, so we remove
1556 // the scrollid from the guid. We need to keep the layersId intact so
1557 // that the response from the child process doesn't get discarded.
1558 state.mResult.mTargetGuid.mScrollId =
1559 ScrollableLayerGuid::NULL_SCROLL_ID;
1562 break;
1564 case SCROLLWHEEL_INPUT: {
1565 FlushRepaintsToClearScreenToGeckoTransform();
1567 // Do this before early return for Fission hit testing.
1568 ScrollWheelInput& wheelInput = aEvent.AsScrollWheelInput();
1569 state.mHit = GetTargetAPZC(wheelInput.mOrigin);
1571 wheelInput.mHandledByAPZ = WillHandleInput(wheelInput);
1572 if (!wheelInput.mHandledByAPZ) {
1573 return state.Finish(*this, std::move(aCallback));
1576 mOvershootDetector.Update(wheelInput);
1578 if (state.mHit.mTargetApzc) {
1579 MOZ_ASSERT(state.mHit.mHitResult != CompositorHitTestInvisibleToHit);
1581 if (wheelInput.mAPZAction == APZWheelAction::PinchZoom) {
1582 // The mousewheel may have hit a subframe, but we want to send the
1583 // pinch-zoom events to the root-content APZC.
1585 RecursiveMutexAutoLock lock(mTreeLock);
1586 state.mHit.mTargetApzc = FindRootContentApzcForLayersId(
1587 state.mHit.mTargetApzc->GetLayersId());
1589 if (state.mHit.mTargetApzc) {
1590 SynthesizePinchGestureFromMouseWheel(wheelInput,
1591 state.mHit.mTargetApzc);
1593 state.mResult.SetStatusAsConsumeNoDefault();
1594 return state.Finish(*this, std::move(aCallback));
1597 MOZ_ASSERT(wheelInput.mAPZAction == APZWheelAction::Scroll);
1599 // For wheel events, the call to ReceiveInputEvent below may result in
1600 // scrolling, which changes the async transform. However, the event we
1601 // want to pass to gecko should be the pre-scroll event coordinates,
1602 // transformed into the gecko space. (pre-scroll because the mouse
1603 // cursor is stationary during wheel scrolling, unlike touchmove
1604 // events). Since we just flushed the pending repaints the transform to
1605 // gecko space should only consist of overscroll-cancelling transforms.
1606 ScreenToScreenMatrix4x4 transformToGecko =
1607 GetScreenToApzcTransform(state.mHit.mTargetApzc) *
1608 GetApzcToGeckoTransformForHit(state.mHit);
1609 Maybe<ScreenPoint> untransformedOrigin =
1610 UntransformBy(transformToGecko, wheelInput.mOrigin);
1612 if (!untransformedOrigin) {
1613 return state.Finish(*this, std::move(aCallback));
1616 state.mResult = mInputQueue->ReceiveInputEvent(
1617 state.mHit.mTargetApzc,
1618 TargetConfirmationFlags{state.mHit.mHitResult}, wheelInput);
1620 // Update the out-parameters so they are what the caller expects.
1621 wheelInput.mOrigin = *untransformedOrigin;
1623 break;
1625 case PANGESTURE_INPUT: {
1626 FlushRepaintsToClearScreenToGeckoTransform();
1628 // Do this before early return for Fission hit testing.
1629 PanGestureInput& panInput = aEvent.AsPanGestureInput();
1630 state.mHit = GetTargetAPZC(panInput.mPanStartPoint);
1632 panInput.mHandledByAPZ = WillHandleInput(panInput);
1633 if (!panInput.mHandledByAPZ) {
1634 if (mInputQueue->GetCurrentPanGestureBlock()) {
1635 if (state.mHit.mTargetApzc &&
1636 (panInput.mType == PanGestureInput::PANGESTURE_END ||
1637 panInput.mType == PanGestureInput::PANGESTURE_CANCELLED)) {
1638 // If we've already been processing a pan gesture in an APZC but
1639 // fall into this _if_ branch, which means this pan-end or
1640 // pan-cancelled event will not be proccessed in the APZC, send a
1641 // pan-interrupted event to stop any on-going work for the pan
1642 // gesture, otherwise we will get stuck at an intermidiate state
1643 // becasue we might no longer receive any events which will be
1644 // handled by the APZC.
1645 PanGestureInput panInterrupted(
1646 PanGestureInput::PANGESTURE_INTERRUPTED, panInput.mTimeStamp,
1647 panInput.mPanStartPoint, panInput.mPanDisplacement,
1648 panInput.modifiers);
1649 Unused << mInputQueue->ReceiveInputEvent(
1650 state.mHit.mTargetApzc,
1651 TargetConfirmationFlags{state.mHit.mHitResult}, panInterrupted);
1654 return state.Finish(*this, std::move(aCallback));
1657 // If/when we enable support for pan inputs off-main-thread, we'll need
1658 // to duplicate this EventStateManager code or something. See the call to
1659 // GetUserPrefsForWheelEvent in APZInputBridge.cpp for why these fields
1660 // are stored separately.
1661 MOZ_ASSERT(NS_IsMainThread());
1662 WidgetWheelEvent wheelEvent = panInput.ToWidgetEvent(nullptr);
1663 EventStateManager::GetUserPrefsForWheelEvent(
1664 &wheelEvent, &panInput.mUserDeltaMultiplierX,
1665 &panInput.mUserDeltaMultiplierY);
1667 if (state.mHit.mTargetApzc) {
1668 MOZ_ASSERT(state.mHit.mHitResult != CompositorHitTestInvisibleToHit);
1670 // For pan gesture events, the call to ReceiveInputEvent below may
1671 // result in scrolling, which changes the async transform. However, the
1672 // event we want to pass to gecko should be the pre-scroll event
1673 // coordinates, transformed into the gecko space. (pre-scroll because
1674 // the mouse cursor is stationary during pan gesture scrolling, unlike
1675 // touchmove events). Since we just flushed the pending repaints the
1676 // transform to gecko space should only consist of overscroll-cancelling
1677 // transforms.
1678 ScreenToScreenMatrix4x4 transformToGecko =
1679 GetScreenToApzcTransform(state.mHit.mTargetApzc) *
1680 GetApzcToGeckoTransformForHit(state.mHit);
1681 Maybe<ScreenPoint> untransformedStartPoint =
1682 UntransformBy(transformToGecko, panInput.mPanStartPoint);
1683 Maybe<ScreenPoint> untransformedDisplacement =
1684 UntransformVector(transformToGecko, panInput.mPanDisplacement,
1685 panInput.mPanStartPoint);
1687 if (!untransformedStartPoint || !untransformedDisplacement) {
1688 return state.Finish(*this, std::move(aCallback));
1691 panInput.mOverscrollBehaviorAllowsSwipe =
1692 state.mHit.mTargetApzc->OverscrollBehaviorAllowsSwipe();
1694 state.mResult = mInputQueue->ReceiveInputEvent(
1695 state.mHit.mTargetApzc,
1696 TargetConfirmationFlags{state.mHit.mHitResult}, panInput);
1698 // Update the out-parameters so they are what the caller expects.
1699 panInput.mPanStartPoint = *untransformedStartPoint;
1700 panInput.mPanDisplacement = *untransformedDisplacement;
1702 break;
1704 case PINCHGESTURE_INPUT: {
1705 PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
1706 if (HasNonLockModifier(pinchInput.modifiers)) {
1707 APZCTM_LOG("Discarding pinch input due to modifiers 0x%x\n",
1708 pinchInput.modifiers);
1709 return state.Finish(*this, std::move(aCallback));
1712 state.mHit = GetTargetAPZC(pinchInput.mFocusPoint);
1714 // We always handle pinch gestures as pinch zooms.
1715 pinchInput.mHandledByAPZ = true;
1717 if (state.mHit.mTargetApzc) {
1718 MOZ_ASSERT(state.mHit.mHitResult != CompositorHitTestInvisibleToHit);
1720 if (!state.mHit.mTargetApzc->IsRootContent()) {
1721 state.mHit.mTargetApzc = FindZoomableApzc(state.mHit.mTargetApzc);
1725 if (state.mHit.mTargetApzc) {
1726 ScreenToScreenMatrix4x4 outTransform =
1727 GetScreenToApzcTransform(state.mHit.mTargetApzc) *
1728 GetApzcToGeckoTransformForHit(state.mHit);
1729 Maybe<ScreenPoint> untransformedFocusPoint =
1730 UntransformBy(outTransform, pinchInput.mFocusPoint);
1732 if (!untransformedFocusPoint) {
1733 return state.Finish(*this, std::move(aCallback));
1736 state.mResult = mInputQueue->ReceiveInputEvent(
1737 state.mHit.mTargetApzc,
1738 TargetConfirmationFlags{state.mHit.mHitResult}, pinchInput);
1740 // Update the out-parameters so they are what the caller expects.
1741 pinchInput.mFocusPoint = *untransformedFocusPoint;
1743 break;
1745 case TAPGESTURE_INPUT: { // note: no one currently sends these
1746 TapGestureInput& tapInput = aEvent.AsTapGestureInput();
1747 state.mHit = GetTargetAPZC(tapInput.mPoint);
1749 if (state.mHit.mTargetApzc) {
1750 MOZ_ASSERT(state.mHit.mHitResult != CompositorHitTestInvisibleToHit);
1752 ScreenToScreenMatrix4x4 outTransform =
1753 GetScreenToApzcTransform(state.mHit.mTargetApzc) *
1754 GetApzcToGeckoTransformForHit(state.mHit);
1755 Maybe<ScreenIntPoint> untransformedPoint =
1756 UntransformBy(outTransform, tapInput.mPoint);
1758 if (!untransformedPoint) {
1759 return state.Finish(*this, std::move(aCallback));
1762 // Tap gesture events are not grouped into input blocks, and they're
1763 // never queued in InputQueue, but processed right away. So, we only
1764 // need to set |mTapGestureHitResult| for the duration of the
1765 // InputQueue::ReceiveInputEvent() call.
1767 RecursiveMutexAutoLock lock(mTreeLock);
1768 mTapGestureHitResult =
1769 mHitTester->CloneHitTestResult(lock, state.mHit);
1772 state.mResult = mInputQueue->ReceiveInputEvent(
1773 state.mHit.mTargetApzc,
1774 TargetConfirmationFlags{state.mHit.mHitResult}, tapInput);
1776 mTapGestureHitResult = HitTestResult();
1778 // Update the out-parameters so they are what the caller expects.
1779 tapInput.mPoint = *untransformedPoint;
1781 break;
1783 case KEYBOARD_INPUT: {
1784 // Disable async keyboard scrolling when accessibility.browsewithcaret is
1785 // enabled
1786 if (!StaticPrefs::apz_keyboard_enabled_AtStartup() ||
1787 StaticPrefs::accessibility_browsewithcaret()) {
1788 APZ_KEY_LOG("Skipping key input from invalid prefs\n");
1789 return state.Finish(*this, std::move(aCallback));
1792 KeyboardInput& keyInput = aEvent.AsKeyboardInput();
1794 // Try and find a matching shortcut for this keyboard input
1795 Maybe<KeyboardShortcut> shortcut = mKeyboardMap.FindMatch(keyInput);
1797 if (!shortcut) {
1798 APZ_KEY_LOG("Skipping key input with no shortcut\n");
1800 // If we don't have a shortcut for this key event, then we can keep our
1801 // focus only if we know there are no key event listeners for this
1802 // target
1803 if (mFocusState.CanIgnoreKeyboardShortcutMisses()) {
1804 focusSetter.MarkAsNonFocusChanging();
1806 return state.Finish(*this, std::move(aCallback));
1809 // Check if this shortcut needs to be dispatched to content. Anything
1810 // matching this is assumed to be able to change focus.
1811 if (shortcut->mDispatchToContent) {
1812 APZ_KEY_LOG("Skipping key input with dispatch-to-content shortcut\n");
1813 return state.Finish(*this, std::move(aCallback));
1816 // We know we have an action to execute on whatever is the current focus
1817 // target
1818 const KeyboardScrollAction& action = shortcut->mAction;
1820 // The current focus target depends on which direction the scroll is to
1821 // happen
1822 Maybe<ScrollableLayerGuid> targetGuid;
1823 switch (action.mType) {
1824 case KeyboardScrollAction::eScrollCharacter: {
1825 targetGuid = mFocusState.GetHorizontalTarget();
1826 break;
1828 case KeyboardScrollAction::eScrollLine:
1829 case KeyboardScrollAction::eScrollPage:
1830 case KeyboardScrollAction::eScrollComplete: {
1831 targetGuid = mFocusState.GetVerticalTarget();
1832 break;
1836 // If we don't have a scroll target then either we have a stale focus
1837 // target, the focused element has event listeners, or the focused element
1838 // doesn't have a layerized scroll frame. In any case we need to dispatch
1839 // to content.
1840 if (!targetGuid) {
1841 APZ_KEY_LOG("Skipping key input with no current focus target\n");
1842 return state.Finish(*this, std::move(aCallback));
1845 RefPtr<AsyncPanZoomController> targetApzc =
1846 GetTargetAPZC(targetGuid->mLayersId, targetGuid->mScrollId);
1848 if (!targetApzc) {
1849 APZ_KEY_LOG("Skipping key input with focus target but no APZC\n");
1850 return state.Finish(*this, std::move(aCallback));
1853 // Attach the keyboard scroll action to the input event for processing
1854 // by the input queue.
1855 keyInput.mAction = action;
1857 APZ_KEY_LOG("Dispatching key input with apzc=%p\n", targetApzc.get());
1859 // Dispatch the event to the input queue.
1860 state.mResult = mInputQueue->ReceiveInputEvent(
1861 targetApzc, TargetConfirmationFlags{true}, keyInput);
1863 // Any keyboard event that is dispatched to the input queue at this point
1864 // should have been consumed
1865 MOZ_ASSERT(state.mResult.GetStatus() == nsEventStatus_eConsumeDoDefault ||
1866 state.mResult.GetStatus() == nsEventStatus_eConsumeNoDefault);
1868 keyInput.mHandledByAPZ = true;
1869 focusSetter.MarkAsNonFocusChanging();
1871 break;
1874 return state.Finish(*this, std::move(aCallback));
1877 static TouchBehaviorFlags ConvertToTouchBehavior(
1878 const CompositorHitTestInfo& info) {
1879 TouchBehaviorFlags result = AllowedTouchBehavior::UNKNOWN;
1880 if (info == CompositorHitTestInvisibleToHit) {
1881 result = AllowedTouchBehavior::NONE;
1882 } else if (info.contains(CompositorHitTestFlags::eIrregularArea)) {
1883 // Note that eApzAwareListeners and eInactiveScrollframe are similar
1884 // to eIrregularArea in some respects, but are not relevant for the
1885 // purposes of this function, which deals specifically with touch-action.
1886 result = AllowedTouchBehavior::UNKNOWN;
1887 } else {
1888 result = AllowedTouchBehavior::VERTICAL_PAN |
1889 AllowedTouchBehavior::HORIZONTAL_PAN |
1890 AllowedTouchBehavior::PINCH_ZOOM |
1891 AllowedTouchBehavior::ANIMATING_ZOOM;
1892 if (info.contains(CompositorHitTestFlags::eTouchActionPanXDisabled)) {
1893 result &= ~AllowedTouchBehavior::HORIZONTAL_PAN;
1895 if (info.contains(CompositorHitTestFlags::eTouchActionPanYDisabled)) {
1896 result &= ~AllowedTouchBehavior::VERTICAL_PAN;
1898 if (info.contains(CompositorHitTestFlags::eTouchActionPinchZoomDisabled)) {
1899 result &= ~AllowedTouchBehavior::PINCH_ZOOM;
1901 if (info.contains(
1902 CompositorHitTestFlags::eTouchActionAnimatingZoomDisabled)) {
1903 result &= ~AllowedTouchBehavior::ANIMATING_ZOOM;
1906 return result;
1909 APZCTreeManager::HitTestResult APZCTreeManager::GetTouchInputBlockAPZC(
1910 const MultiTouchInput& aEvent,
1911 nsTArray<TouchBehaviorFlags>* aOutTouchBehaviors) {
1912 HitTestResult hit;
1913 if (aEvent.mTouches.Length() == 0) {
1914 return hit;
1917 FlushRepaintsToClearScreenToGeckoTransform();
1919 hit = GetTargetAPZC(aEvent.mTouches[0].mScreenPoint);
1920 // Don't set a layers id on multi-touch events.
1921 if (aEvent.mTouches.Length() != 1) {
1922 hit.mLayersId = LayersId{0};
1925 if (aOutTouchBehaviors) {
1926 aOutTouchBehaviors->AppendElement(ConvertToTouchBehavior(hit.mHitResult));
1928 for (size_t i = 1; i < aEvent.mTouches.Length(); i++) {
1929 HitTestResult hit2 = GetTargetAPZC(aEvent.mTouches[i].mScreenPoint);
1930 if (aOutTouchBehaviors) {
1931 aOutTouchBehaviors->AppendElement(
1932 ConvertToTouchBehavior(hit2.mHitResult));
1934 hit.mTargetApzc = GetZoomableTarget(hit.mTargetApzc, hit2.mTargetApzc);
1935 APZCTM_LOG("Using APZC %p as the root APZC for multi-touch\n",
1936 hit.mTargetApzc.get());
1937 // A multi-touch gesture will not be a scrollbar drag, even if the
1938 // first touch point happened to hit a scrollbar.
1939 hit.mScrollbarNode.Clear();
1941 // XXX we should probably be combining the hit results from the different
1942 // touch points somehow, instead of just using the last one.
1943 hit.mHitResult = hit2.mHitResult;
1946 return hit;
1949 APZEventResult APZCTreeManager::InputHandlingState::Finish(
1950 APZCTreeManager& aTreeManager, InputBlockCallback&& aCallback) {
1951 // The validity check here handles both the case where mHit was
1952 // never populated (because this event did not trigger a hit-test),
1953 // and the case where it was populated with an invalid LayersId
1954 // (which can happen e.g. for multi-touch events).
1955 if (mHit.mLayersId.IsValid()) {
1956 mEvent.mLayersId = mHit.mLayersId;
1959 // Absorb events that are in targetted at a position in the gutter,
1960 // unless they are fixed position elements.
1961 if (mHit.mHitOverscrollGutter && mHit.mFixedPosSides == SideBits::eNone) {
1962 mResult.SetStatusAsConsumeNoDefault();
1965 // If the event will have a delayed result then add the callback to the
1966 // APZCTreeManager.
1967 if (aCallback && mResult.WillHaveDelayedResult()) {
1968 aTreeManager.AddInputBlockCallback(
1969 mResult.mInputBlockId, {mResult.GetStatus(), std::move(aCallback)});
1972 return mResult;
1975 void APZCTreeManager::ProcessTouchInput(InputHandlingState& aState,
1976 MultiTouchInput& aInput) {
1977 aInput.mHandledByAPZ = true;
1978 nsTArray<TouchBehaviorFlags> touchBehaviors;
1979 HitTestingTreeNodeAutoLock hitScrollbarNode;
1980 if (aInput.mType == MultiTouchInput::MULTITOUCH_START) {
1981 // If we are panned into overscroll and a second finger goes down,
1982 // ignore that second touch point completely. The touch-start for it is
1983 // dropped completely; subsequent touch events until the touch-end for it
1984 // will have this touch point filtered out.
1985 // (By contrast, if we're in overscroll but not panning, such as after
1986 // putting two fingers down during an overscroll animation, we process the
1987 // second touch and proceed to pinch.)
1988 if (mTouchBlockHitResult.mTargetApzc &&
1989 mTouchBlockHitResult.mTargetApzc->IsInPanningState() &&
1990 BuildOverscrollHandoffChain(mTouchBlockHitResult.mTargetApzc)
1991 ->HasOverscrolledApzc()) {
1992 if (mRetainedTouchIdentifier == -1) {
1993 mRetainedTouchIdentifier =
1994 mTouchBlockHitResult.mTargetApzc->GetLastTouchIdentifier();
1997 aState.mResult.SetStatusAsConsumeNoDefault();
1998 return;
2001 aState.mHit = GetTouchInputBlockAPZC(aInput, &touchBehaviors);
2002 RecursiveMutexAutoLock lock(mTreeLock);
2003 // Repopulate mTouchBlockHitResult from the input state.
2004 mTouchBlockHitResult = mHitTester->CloneHitTestResult(lock, aState.mHit);
2005 hitScrollbarNode = std::move(aState.mHit.mScrollbarNode);
2007 // Check if this event starts a scrollbar touch-drag. The conditions
2008 // checked are similar to the ones we check for MOUSE_INPUT starting
2009 // a scrollbar mouse-drag.
2010 mInScrollbarTouchDrag =
2011 StaticPrefs::apz_drag_enabled() &&
2012 StaticPrefs::apz_drag_touch_enabled() && hitScrollbarNode &&
2013 hitScrollbarNode->IsScrollThumbNode() &&
2014 hitScrollbarNode->GetScrollbarData().mThumbIsAsyncDraggable;
2016 MOZ_ASSERT(touchBehaviors.Length() == aInput.mTouches.Length());
2017 for (size_t i = 0; i < touchBehaviors.Length(); i++) {
2018 APZCTM_LOG("Touch point has allowed behaviours 0x%02x\n",
2019 touchBehaviors[i]);
2020 if (touchBehaviors[i] == AllowedTouchBehavior::UNKNOWN) {
2021 // If there's any unknown items in the list, throw it out and we'll
2022 // wait for the main thread to send us a notification.
2023 touchBehaviors.Clear();
2024 break;
2027 } else if (mTouchBlockHitResult.mTargetApzc) {
2028 APZCTM_LOG("Re-using APZC %p as continuation of event block\n",
2029 mTouchBlockHitResult.mTargetApzc.get());
2030 RecursiveMutexAutoLock lock(mTreeLock);
2031 aState.mHit = mHitTester->CloneHitTestResult(lock, mTouchBlockHitResult);
2034 if (mInScrollbarTouchDrag) {
2035 aState.mResult = ProcessTouchInputForScrollbarDrag(
2036 aInput, hitScrollbarNode, mTouchBlockHitResult.mHitResult);
2037 } else {
2038 // If we receive a touch-cancel, it means all touches are finished, so we
2039 // can stop ignoring any that we were ignoring.
2040 if (aInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
2041 mRetainedTouchIdentifier = -1;
2044 // If we are currently ignoring any touch points, filter them out from the
2045 // set of touch points included in this event. Note that we modify aInput
2046 // itself, so that the touch points are also filtered out when the caller
2047 // passes the event on to content.
2048 if (mRetainedTouchIdentifier != -1) {
2049 for (size_t j = 0; j < aInput.mTouches.Length(); ++j) {
2050 if (aInput.mTouches[j].mIdentifier != mRetainedTouchIdentifier) {
2051 aInput.mTouches.RemoveElementAt(j);
2052 if (!touchBehaviors.IsEmpty()) {
2053 MOZ_ASSERT(touchBehaviors.Length() > j);
2054 touchBehaviors.RemoveElementAt(j);
2056 --j;
2059 if (aInput.mTouches.IsEmpty()) {
2060 aState.mResult.SetStatusAsConsumeNoDefault();
2061 return;
2065 if (mTouchBlockHitResult.mTargetApzc) {
2066 MOZ_ASSERT(mTouchBlockHitResult.mHitResult !=
2067 CompositorHitTestInvisibleToHit);
2069 aState.mResult = mInputQueue->ReceiveInputEvent(
2070 mTouchBlockHitResult.mTargetApzc,
2071 TargetConfirmationFlags{mTouchBlockHitResult.mHitResult}, aInput,
2072 touchBehaviors.IsEmpty() ? Nothing()
2073 : Some(std::move(touchBehaviors)));
2075 // For computing the event to pass back to Gecko, use up-to-date
2076 // transforms (i.e. not anything cached in an input block). This ensures
2077 // that transformToApzc and transformToGecko are in sync.
2078 // Note: we are not using ConvertToGecko() here, because we don't
2079 // want to multiply transformToApzc and transformToGecko once
2080 // for each touch point.
2081 ScreenToParentLayerMatrix4x4 transformToApzc =
2082 GetScreenToApzcTransform(mTouchBlockHitResult.mTargetApzc);
2083 ParentLayerToScreenMatrix4x4 transformToGecko =
2084 GetApzcToGeckoTransformForHit(mTouchBlockHitResult);
2085 ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko;
2087 for (size_t i = 0; i < aInput.mTouches.Length(); i++) {
2088 SingleTouchData& touchData = aInput.mTouches[i];
2089 Maybe<ScreenIntPoint> untransformedScreenPoint =
2090 UntransformBy(outTransform, touchData.mScreenPoint);
2091 if (!untransformedScreenPoint) {
2092 aState.mResult.SetStatusAsIgnore();
2093 return;
2095 touchData.mScreenPoint = *untransformedScreenPoint;
2096 AdjustEventPointForDynamicToolbar(touchData.mScreenPoint,
2097 mTouchBlockHitResult);
2102 mTouchCounter.Update(aInput);
2104 // If it's the end of the touch sequence then clear out variables so we
2105 // don't keep dangling references and leak things.
2106 if (mTouchCounter.GetActiveTouchCount() == 0) {
2107 mTouchBlockHitResult = HitTestResult();
2108 mRetainedTouchIdentifier = -1;
2109 mInScrollbarTouchDrag = false;
2113 void APZCTreeManager::AdjustEventPointForDynamicToolbar(
2114 ScreenIntPoint& aEventPoint, const HitTestResult& aHit) {
2115 if (aHit.mFixedPosSides != SideBits::eNone) {
2116 MutexAutoLock lock(mMapLock);
2117 aEventPoint -= RoundedToInt(apz::ComputeFixedMarginsOffset(
2118 GetCompositorFixedLayerMargins(lock), aHit.mFixedPosSides,
2119 mGeckoFixedLayerMargins));
2120 } else if (aHit.mNode && aHit.mNode->GetStickyPositionAnimationId()) {
2121 SideBits sideBits = SideBits::eNone;
2123 RecursiveMutexAutoLock lock(mTreeLock);
2124 sideBits = SidesStuckToRootContent(
2125 aHit.mNode.Get(lock), AsyncTransformConsumer::eForEventHandling);
2127 MutexAutoLock lock(mMapLock);
2128 aEventPoint -= RoundedToInt(apz::ComputeFixedMarginsOffset(
2129 GetCompositorFixedLayerMargins(lock), sideBits, ScreenMargin()));
2133 static MouseInput::MouseType MultiTouchTypeToMouseType(
2134 MultiTouchInput::MultiTouchType aType) {
2135 switch (aType) {
2136 case MultiTouchInput::MULTITOUCH_START:
2137 return MouseInput::MOUSE_DOWN;
2138 case MultiTouchInput::MULTITOUCH_MOVE:
2139 return MouseInput::MOUSE_MOVE;
2140 case MultiTouchInput::MULTITOUCH_END:
2141 case MultiTouchInput::MULTITOUCH_CANCEL:
2142 return MouseInput::MOUSE_UP;
2144 MOZ_ASSERT_UNREACHABLE("Invalid multi-touch type");
2145 return MouseInput::MOUSE_NONE;
2148 APZEventResult APZCTreeManager::ProcessTouchInputForScrollbarDrag(
2149 MultiTouchInput& aTouchInput,
2150 const HitTestingTreeNodeAutoLock& aScrollThumbNode,
2151 const gfx::CompositorHitTestInfo& aHitInfo) {
2152 MOZ_ASSERT(mRetainedTouchIdentifier == -1);
2153 MOZ_ASSERT(mTouchBlockHitResult.mTargetApzc);
2154 MOZ_ASSERT(aTouchInput.mTouches.Length() == 1);
2156 // Synthesize a mouse event based on the touch event, so that we can
2157 // reuse code in InputQueue and APZC for handling scrollbar mouse-drags.
2158 MouseInput mouseInput{MultiTouchTypeToMouseType(aTouchInput.mType),
2159 MouseInput::PRIMARY_BUTTON,
2160 dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH,
2161 MouseButtonsFlag::ePrimaryFlag,
2162 aTouchInput.mTouches[0].mScreenPoint,
2163 aTouchInput.mTimeStamp,
2164 aTouchInput.modifiers};
2165 mouseInput.mHandledByAPZ = true;
2167 TargetConfirmationFlags targetConfirmed{aHitInfo};
2168 APZEventResult result;
2169 result = mInputQueue->ReceiveInputEvent(mTouchBlockHitResult.mTargetApzc,
2170 targetConfirmed, mouseInput);
2172 // |aScrollThumbNode| is non-null iff. this is the event that starts the drag.
2173 // If so, set up the drag.
2174 if (aScrollThumbNode) {
2175 SetupScrollbarDrag(mouseInput, aScrollThumbNode,
2176 mTouchBlockHitResult.mTargetApzc.get());
2179 // Since the input was targeted at a scrollbar:
2180 // - The original touch event (which will be sent on to content) will
2181 // not be untransformed.
2182 // - We don't want to apply the callback transform in the main thread,
2183 // so we remove the scrollid from the guid.
2184 // Both of these match the behaviour of mouse events that target a scrollbar;
2185 // see the code for handling mouse events in ReceiveInputEvent() for
2186 // additional explanation.
2187 result.mTargetGuid.mScrollId = ScrollableLayerGuid::NULL_SCROLL_ID;
2189 return result;
2192 void APZCTreeManager::SetupScrollbarDrag(
2193 MouseInput& aMouseInput, const HitTestingTreeNodeAutoLock& aScrollThumbNode,
2194 AsyncPanZoomController* aApzc) {
2195 DragBlockState* dragBlock = mInputQueue->GetCurrentDragBlock();
2196 if (!dragBlock) {
2197 return;
2200 const ScrollbarData& thumbData = aScrollThumbNode->GetScrollbarData();
2201 MOZ_ASSERT(thumbData.mDirection.isSome());
2203 // Record the thumb's position at the start of the drag.
2204 // We snap back to this position if, during the drag, the mouse
2205 // gets sufficiently far away from the scrollbar.
2206 dragBlock->SetInitialThumbPos(thumbData.mThumbStart);
2208 // Under some conditions, we can confirm the drag block right away.
2209 // Otherwise, we have to wait for a main-thread confirmation.
2210 if (StaticPrefs::apz_drag_initial_enabled() &&
2211 // check that the scrollbar's target scroll frame is layerized
2212 aScrollThumbNode->GetScrollTargetId() == aApzc->GetGuid().mScrollId &&
2213 !aApzc->IsScrollInfoLayer()) {
2214 uint64_t dragBlockId = dragBlock->GetBlockId();
2215 // AsyncPanZoomController::HandleInputEvent() will call
2216 // TransformToLocal() on the event, but we need its mLocalOrigin now
2217 // to compute a drag start offset for the AsyncDragMetrics.
2218 aMouseInput.TransformToLocal(aApzc->GetTransformToThis());
2219 OuterCSSCoord dragStart =
2220 aApzc->ConvertScrollbarPoint(aMouseInput.mLocalOrigin, thumbData);
2221 // ConvertScrollbarPoint() got the drag start offset relative to
2222 // the scroll track. Now get it relative to the thumb.
2223 // ScrollThumbData::mThumbStart stores the offset of the thumb
2224 // relative to the scroll track at the time of the last paint.
2225 // Since that paint, the thumb may have acquired an async transform
2226 // due to async scrolling, so look that up and apply it.
2227 LayerToParentLayerMatrix4x4 thumbTransform;
2229 RecursiveMutexAutoLock lock(mTreeLock);
2230 thumbTransform =
2231 ComputeTransformForScrollThumbNode(aScrollThumbNode.Get(lock));
2233 // Only consider the translation, since we do not support both
2234 // zooming and scrollbar dragging on any platform.
2235 // FIXME: We do now.
2236 OuterCSSCoord thumbStart =
2237 thumbData.mThumbStart +
2238 ((*thumbData.mDirection == ScrollDirection::eHorizontal)
2239 ? thumbTransform._41
2240 : thumbTransform._42);
2241 dragStart -= thumbStart;
2243 // Content can't prevent scrollbar dragging with preventDefault(),
2244 // so we don't need to wait for a content response. It's important
2245 // to do this before calling ConfirmDragBlock() since that can
2246 // potentially process and consume the block.
2247 dragBlock->SetContentResponse(false);
2249 NotifyScrollbarDragInitiated(dragBlockId, aApzc->GetGuid(),
2250 *thumbData.mDirection);
2252 mInputQueue->ConfirmDragBlock(
2253 dragBlockId, aApzc,
2254 AsyncDragMetrics(aApzc->GetGuid().mScrollId,
2255 aApzc->GetGuid().mPresShellId, dragBlockId, dragStart,
2256 *thumbData.mDirection));
2260 void APZCTreeManager::SynthesizePinchGestureFromMouseWheel(
2261 const ScrollWheelInput& aWheelInput,
2262 const RefPtr<AsyncPanZoomController>& aTarget) {
2263 MOZ_ASSERT(aTarget);
2265 ScreenPoint focusPoint = aWheelInput.mOrigin;
2267 // Compute span values based on the wheel delta.
2268 ScreenCoord oldSpan = 100;
2269 ScreenCoord newSpan = oldSpan + aWheelInput.mDeltaY;
2271 // There's no ambiguity as to the target for pinch gesture events.
2272 TargetConfirmationFlags confFlags{true};
2274 PinchGestureInput pinchStart{PinchGestureInput::PINCHGESTURE_START,
2275 PinchGestureInput::MOUSEWHEEL,
2276 aWheelInput.mTimeStamp,
2277 ExternalPoint(0, 0),
2278 focusPoint,
2279 oldSpan,
2280 oldSpan,
2281 aWheelInput.modifiers};
2282 PinchGestureInput pinchScale1{PinchGestureInput::PINCHGESTURE_SCALE,
2283 PinchGestureInput::MOUSEWHEEL,
2284 aWheelInput.mTimeStamp,
2285 ExternalPoint(0, 0),
2286 focusPoint,
2287 oldSpan,
2288 oldSpan,
2289 aWheelInput.modifiers};
2290 PinchGestureInput pinchScale2{PinchGestureInput::PINCHGESTURE_SCALE,
2291 PinchGestureInput::MOUSEWHEEL,
2292 aWheelInput.mTimeStamp,
2293 ExternalPoint(0, 0),
2294 focusPoint,
2295 oldSpan,
2296 newSpan,
2297 aWheelInput.modifiers};
2298 PinchGestureInput pinchEnd{PinchGestureInput::PINCHGESTURE_END,
2299 PinchGestureInput::MOUSEWHEEL,
2300 aWheelInput.mTimeStamp,
2301 ExternalPoint(0, 0),
2302 focusPoint,
2303 newSpan,
2304 newSpan,
2305 aWheelInput.modifiers};
2307 mInputQueue->ReceiveInputEvent(aTarget, confFlags, pinchStart);
2308 mInputQueue->ReceiveInputEvent(aTarget, confFlags, pinchScale1);
2309 mInputQueue->ReceiveInputEvent(aTarget, confFlags, pinchScale2);
2310 mInputQueue->ReceiveInputEvent(aTarget, confFlags, pinchEnd);
2313 void APZCTreeManager::UpdateWheelTransaction(
2314 LayoutDeviceIntPoint aRefPoint, EventMessage aEventMessage,
2315 const Maybe<ScrollableLayerGuid>& aTargetGuid) {
2316 APZThreadUtils::AssertOnControllerThread();
2318 WheelBlockState* txn = mInputQueue->GetActiveWheelTransaction();
2319 if (!txn) {
2320 return;
2323 // If the transaction has simply timed out, we don't need to do anything
2324 // else.
2325 if (txn->MaybeTimeout(TimeStamp::Now())) {
2326 return;
2329 switch (aEventMessage) {
2330 case eMouseMove:
2331 case eDragOver: {
2332 ScreenIntPoint point = ViewAs<ScreenPixel>(
2333 aRefPoint,
2334 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
2336 txn->OnMouseMove(point, aTargetGuid);
2338 return;
2340 case eKeyPress:
2341 case eKeyUp:
2342 case eKeyDown:
2343 case eMouseUp:
2344 case eMouseDown:
2345 case eMouseDoubleClick:
2346 case eMouseAuxClick:
2347 case eMouseClick:
2348 case eContextMenu:
2349 case eDrop:
2350 txn->EndTransaction();
2351 return;
2352 default:
2353 break;
2357 void APZCTreeManager::ProcessUnhandledEvent(LayoutDeviceIntPoint* aRefPoint,
2358 ScrollableLayerGuid* aOutTargetGuid,
2359 uint64_t* aOutFocusSequenceNumber,
2360 LayersId* aOutLayersId) {
2361 APZThreadUtils::AssertOnControllerThread();
2363 // Transform the aRefPoint.
2364 // If the event hits an overscrolled APZC, instruct the caller to ignore it.
2365 PixelCastJustification LDIsScreen =
2366 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent;
2367 ScreenIntPoint refPointAsScreen = ViewAs<ScreenPixel>(*aRefPoint, LDIsScreen);
2368 HitTestResult hit = GetTargetAPZC(refPointAsScreen);
2369 if (aOutLayersId) {
2370 *aOutLayersId = hit.mLayersId;
2372 if (hit.mTargetApzc) {
2373 MOZ_ASSERT(hit.mHitResult != CompositorHitTestInvisibleToHit);
2374 hit.mTargetApzc->GetGuid(aOutTargetGuid);
2375 ScreenToParentLayerMatrix4x4 transformToApzc =
2376 GetScreenToApzcTransform(hit.mTargetApzc);
2377 ParentLayerToScreenMatrix4x4 transformToGecko =
2378 GetApzcToGeckoTransformForHit(hit);
2379 ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko;
2380 Maybe<ScreenIntPoint> untransformedRefPoint =
2381 UntransformBy(outTransform, refPointAsScreen);
2382 if (untransformedRefPoint) {
2383 *aRefPoint =
2384 ViewAs<LayoutDevicePixel>(*untransformedRefPoint, LDIsScreen);
2388 // Update the focus sequence number and attach it to the event
2389 mFocusState.ReceiveFocusChangingEvent();
2390 *aOutFocusSequenceNumber = mFocusState.LastAPZProcessedEvent();
2393 void APZCTreeManager::SetKeyboardMap(const KeyboardMap& aKeyboardMap) {
2394 if (!APZThreadUtils::IsControllerThread()) {
2395 APZThreadUtils::RunOnControllerThread(NewRunnableMethod<KeyboardMap>(
2396 "layers::APZCTreeManager::SetKeyboardMap", this,
2397 &APZCTreeManager::SetKeyboardMap, aKeyboardMap));
2398 return;
2401 APZThreadUtils::AssertOnControllerThread();
2403 mKeyboardMap = aKeyboardMap;
2406 void APZCTreeManager::ZoomToRect(const ScrollableLayerGuid& aGuid,
2407 const ZoomTarget& aZoomTarget,
2408 const uint32_t aFlags) {
2409 if (!APZThreadUtils::IsControllerThread()) {
2410 APZThreadUtils::RunOnControllerThread(
2411 NewRunnableMethod<ScrollableLayerGuid, ZoomTarget, uint32_t>(
2412 "layers::APZCTreeManager::ZoomToRect", this,
2413 &APZCTreeManager::ZoomToRect, aGuid, aZoomTarget, aFlags));
2414 return;
2417 // We could probably move this to run on the updater thread if needed, but
2418 // either way we should restrict it to a single thread. For now let's use the
2419 // controller thread.
2420 APZThreadUtils::AssertOnControllerThread();
2422 RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
2423 if (aFlags & ZOOM_TO_FOCUSED_INPUT) {
2424 // In the case of ZoomToFocusInput the targetRect is in the in-process root
2425 // content coordinates. We need to convert it to the top level content
2426 // coordiates.
2427 if (apzc) {
2428 CSSRect transformedRect =
2429 ConvertRectInApzcToRoot(apzc, aZoomTarget.targetRect);
2431 transformedRect.Inflate(15.0f, 0.0f);
2432 // Note that ZoomToFocusedInput doesn't use the other fields of
2433 // ZoomTarget.
2434 ZoomTarget zoomTarget{transformedRect};
2436 apzc = FindZoomableApzc(apzc);
2437 if (apzc) {
2438 apzc->ZoomToRect(zoomTarget, aFlags);
2441 return;
2444 if (apzc) {
2445 apzc = FindZoomableApzc(apzc);
2446 if (apzc) {
2447 apzc->ZoomToRect(aZoomTarget, aFlags);
2452 void APZCTreeManager::ContentReceivedInputBlock(uint64_t aInputBlockId,
2453 bool aPreventDefault) {
2454 if (!APZThreadUtils::IsControllerThread()) {
2455 APZThreadUtils::RunOnControllerThread(NewRunnableMethod<uint64_t, bool>(
2456 "layers::APZCTreeManager::ContentReceivedInputBlock", this,
2457 &APZCTreeManager::ContentReceivedInputBlock, aInputBlockId,
2458 aPreventDefault));
2459 return;
2462 APZThreadUtils::AssertOnControllerThread();
2464 mInputQueue->ContentReceivedInputBlock(aInputBlockId, aPreventDefault);
2467 void APZCTreeManager::SetTargetAPZC(
2468 uint64_t aInputBlockId, const nsTArray<ScrollableLayerGuid>& aTargets) {
2469 if (!APZThreadUtils::IsControllerThread()) {
2470 APZThreadUtils::RunOnControllerThread(
2471 NewRunnableMethod<uint64_t,
2472 StoreCopyPassByRRef<nsTArray<ScrollableLayerGuid>>>(
2473 "layers::APZCTreeManager::SetTargetAPZC", this,
2474 &layers::APZCTreeManager::SetTargetAPZC, aInputBlockId,
2475 aTargets.Clone()));
2476 return;
2479 RefPtr<AsyncPanZoomController> target = nullptr;
2480 if (aTargets.Length() > 0) {
2481 target = GetTargetAPZC(aTargets[0]);
2483 for (size_t i = 1; i < aTargets.Length(); i++) {
2484 RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aTargets[i]);
2485 target = GetZoomableTarget(target, apzc);
2487 if (InputBlockState* block = mInputQueue->GetBlockForId(aInputBlockId)) {
2488 if (block->AsPinchGestureBlock() && aTargets.Length() == 1) {
2489 target = FindZoomableApzc(target);
2492 mInputQueue->SetConfirmedTargetApzc(aInputBlockId, target);
2495 void APZCTreeManager::UpdateZoomConstraints(
2496 const ScrollableLayerGuid& aGuid,
2497 const Maybe<ZoomConstraints>& aConstraints) {
2498 if (!GetUpdater()->IsUpdaterThread()) {
2499 // This can happen if we're in the UI process and got a call directly from
2500 // nsBaseWidget or from a content process over PAPZCTreeManager. In that
2501 // case we get this call on the compositor thread, which may be different
2502 // from the updater thread. It can also happen in the GPU process if that is
2503 // enabled, since the call will go over PAPZCTreeManager and arrive on the
2504 // compositor thread in the GPU process.
2505 GetUpdater()->RunOnUpdaterThread(
2506 aGuid.mLayersId,
2507 NewRunnableMethod<ScrollableLayerGuid, Maybe<ZoomConstraints>>(
2508 "APZCTreeManager::UpdateZoomConstraints", this,
2509 &APZCTreeManager::UpdateZoomConstraints, aGuid, aConstraints));
2510 return;
2513 AssertOnUpdaterThread();
2515 // Propagate the zoom constraints down to the subtree, stopping at APZCs
2516 // which have their own zoom constraints or are in a different layers id.
2517 if (aConstraints) {
2518 APZCTM_LOG("Recording constraints %s for guid %s\n",
2519 ToString(aConstraints.value()).c_str(), ToString(aGuid).c_str());
2520 mZoomConstraints[aGuid] = aConstraints.ref();
2521 } else {
2522 APZCTM_LOG("Removing constraints for guid %s\n", ToString(aGuid).c_str());
2523 mZoomConstraints.erase(aGuid);
2526 RecursiveMutexAutoLock lock(mTreeLock);
2527 RefPtr<HitTestingTreeNode> node = DepthFirstSearchPostOrder<ReverseIterator>(
2528 mRootNode.get(), [&aGuid](HitTestingTreeNode* aNode) {
2529 bool matches = false;
2530 if (auto zoomId = aNode->GetAsyncZoomContainerId()) {
2531 matches = ScrollableLayerGuid::EqualsIgnoringPresShell(
2532 aGuid, ScrollableLayerGuid(aNode->GetLayersId(), 0, *zoomId));
2534 return matches;
2537 // This does not hold because we can get zoom constraints updates before the
2538 // layer tree update with the async zoom container (I assume).
2539 // clang-format off
2540 // MOZ_ASSERT(node || aConstraints.isNothing() ||
2541 // (!aConstraints->mAllowZoom && !aConstraints->mAllowDoubleTapZoom));
2542 // clang-format on
2544 // If there is no async zoom container then the zoom constraints should not
2545 // allow zooming and building the HTT should have handled clearing the zoom
2546 // constraints from all nodes so we don't have to handle doing anything in
2547 // case there is no async zoom container.
2549 if (node && aConstraints) {
2550 ForEachNode<ReverseIterator>(node.get(), [&aConstraints, &node, &aGuid,
2551 this](HitTestingTreeNode* aNode) {
2552 if (aNode != node) {
2553 // don't go into other async zoom containers
2554 if (auto zoomId = aNode->GetAsyncZoomContainerId()) {
2555 MOZ_ASSERT(!ScrollableLayerGuid::EqualsIgnoringPresShell(
2556 aGuid, ScrollableLayerGuid(aNode->GetLayersId(), 0, *zoomId)));
2557 return TraversalFlag::Skip;
2559 if (AsyncPanZoomController* childApzc = aNode->GetApzc()) {
2560 if (!ScrollableLayerGuid::EqualsIgnoringPresShell(
2561 aGuid, childApzc->GetGuid())) {
2562 // We can have subtrees with their own zoom constraints - leave
2563 // these alone.
2564 if (this->mZoomConstraints.find(childApzc->GetGuid()) !=
2565 this->mZoomConstraints.end()) {
2566 return TraversalFlag::Skip;
2571 if (aNode->IsPrimaryHolder()) {
2572 MOZ_ASSERT(aNode->GetApzc());
2573 aNode->GetApzc()->UpdateZoomConstraints(aConstraints.ref());
2575 return TraversalFlag::Continue;
2580 void APZCTreeManager::FlushRepaintsToClearScreenToGeckoTransform() {
2581 // As the name implies, we flush repaint requests for the entire APZ tree in
2582 // order to clear the screen-to-gecko transform (aka the "untransform" applied
2583 // to incoming input events before they can be passed on to Gecko).
2585 // The primary reason we do this is to avoid the problem where input events,
2586 // after being untransformed, end up hit-testing differently in Gecko. This
2587 // might happen in cases where the input event lands on content that is async-
2588 // scrolled into view, but Gecko still thinks it is out of view given the
2589 // visible area of a scrollframe.
2591 // Another reason we want to clear the untransform is that if our APZ hit-test
2592 // hits a dispatch-to-content region then that's an ambiguous result and we
2593 // need to ask Gecko what actually got hit. In order to do this we need to
2594 // untransform the input event into Gecko space - but to do that we need to
2595 // know which APZC got hit! This leads to a circular dependency; the only way
2596 // to get out of it is to make sure that the untransform for all the possible
2597 // matched APZCs is the same. It is simplest to ensure that by flushing the
2598 // pending repaint requests, which makes all of the untransforms empty (and
2599 // therefore equal).
2600 RecursiveMutexAutoLock lock(mTreeLock);
2602 ForEachNode<ReverseIterator>(mRootNode.get(), [](HitTestingTreeNode* aNode) {
2603 if (aNode->IsPrimaryHolder()) {
2604 MOZ_ASSERT(aNode->GetApzc());
2605 aNode->GetApzc()->FlushRepaintForNewInputBlock();
2610 void APZCTreeManager::ClearTree() {
2611 AssertOnUpdaterThread();
2613 // Ensure that no references to APZCs are alive in any lingering input
2614 // blocks. This breaks cycles from InputBlockState::mTargetApzc back to
2615 // the InputQueue.
2616 APZThreadUtils::RunOnControllerThread(NewRunnableMethod(
2617 "layers::InputQueue::Clear", mInputQueue, &InputQueue::Clear));
2619 RecursiveMutexAutoLock lock(mTreeLock);
2621 // Collect the nodes into a list, and then destroy each one.
2622 // We can't destroy them as we collect them, because ForEachNode()
2623 // does a pre-order traversal of the tree, and Destroy() nulls out
2624 // the fields needed to reach the children of the node.
2625 nsTArray<RefPtr<HitTestingTreeNode>> nodesToDestroy;
2626 ForEachNode<ReverseIterator>(mRootNode.get(),
2627 [&nodesToDestroy](HitTestingTreeNode* aNode) {
2628 nodesToDestroy.AppendElement(aNode);
2631 for (size_t i = 0; i < nodesToDestroy.Length(); i++) {
2632 nodesToDestroy[i]->Destroy();
2634 mRootNode = nullptr;
2637 // Also remove references to APZC instances in the map
2638 MutexAutoLock lock(mMapLock);
2639 mApzcMap.clear();
2642 RefPtr<APZCTreeManager> self(this);
2643 NS_DispatchToMainThread(
2644 NS_NewRunnableFunction("layers::APZCTreeManager::ClearTree", [self] {
2645 self->mFlushObserver->Unregister();
2646 self->mFlushObserver = nullptr;
2647 }));
2650 RefPtr<HitTestingTreeNode> APZCTreeManager::GetRootNode() const {
2651 RecursiveMutexAutoLock lock(mTreeLock);
2652 return mRootNode;
2656 * Transform a displacement from the ParentLayer coordinates of a source APZC
2657 * to the ParentLayer coordinates of a target APZC.
2658 * @param aTreeManager the tree manager for the APZC tree containing |aSource|
2659 * and |aTarget|
2660 * @param aSource the source APZC
2661 * @param aTarget the target APZC
2662 * @param aStartPoint the start point of the displacement
2663 * @param aEndPoint the end point of the displacement
2664 * @return true on success, false if aStartPoint or aEndPoint cannot be
2665 * transformed into target's coordinate space
2667 static bool TransformDisplacement(APZCTreeManager* aTreeManager,
2668 AsyncPanZoomController* aSource,
2669 AsyncPanZoomController* aTarget,
2670 ParentLayerPoint& aStartPoint,
2671 ParentLayerPoint& aEndPoint) {
2672 if (aSource == aTarget) {
2673 return true;
2676 // Convert start and end points to Screen coordinates.
2677 ParentLayerToScreenMatrix4x4 untransformToApzc =
2678 aTreeManager->GetScreenToApzcTransform(aSource).Inverse();
2679 ScreenPoint screenStart = TransformBy(untransformToApzc, aStartPoint);
2680 ScreenPoint screenEnd = TransformBy(untransformToApzc, aEndPoint);
2682 // Convert start and end points to aTarget's ParentLayer coordinates.
2683 ScreenToParentLayerMatrix4x4 transformToApzc =
2684 aTreeManager->GetScreenToApzcTransform(aTarget);
2685 Maybe<ParentLayerPoint> startPoint =
2686 UntransformBy(transformToApzc, screenStart);
2687 Maybe<ParentLayerPoint> endPoint = UntransformBy(transformToApzc, screenEnd);
2688 if (!startPoint || !endPoint) {
2689 return false;
2691 aEndPoint = *endPoint;
2692 aStartPoint = *startPoint;
2694 return true;
2697 bool APZCTreeManager::DispatchScroll(
2698 AsyncPanZoomController* aPrev, ParentLayerPoint& aStartPoint,
2699 ParentLayerPoint& aEndPoint,
2700 OverscrollHandoffState& aOverscrollHandoffState) {
2701 const OverscrollHandoffChain& overscrollHandoffChain =
2702 aOverscrollHandoffState.mChain;
2703 uint32_t overscrollHandoffChainIndex = aOverscrollHandoffState.mChainIndex;
2704 RefPtr<AsyncPanZoomController> next;
2705 // If we have reached the end of the overscroll handoff chain, there is
2706 // nothing more to scroll, so we ignore the rest of the pan gesture.
2707 if (overscrollHandoffChainIndex >= overscrollHandoffChain.Length()) {
2708 // Nothing more to scroll - ignore the rest of the pan gesture.
2709 return false;
2712 next = overscrollHandoffChain.GetApzcAtIndex(overscrollHandoffChainIndex);
2714 if (next == nullptr || next->IsDestroyed()) {
2715 return false;
2718 // Convert the start and end points from |aPrev|'s coordinate space to
2719 // |next|'s coordinate space.
2720 if (!TransformDisplacement(this, aPrev, next, aStartPoint, aEndPoint)) {
2721 return false;
2724 // Scroll |next|. If this causes overscroll, it will call DispatchScroll()
2725 // again with an incremented index.
2726 if (!next->AttemptScroll(aStartPoint, aEndPoint, aOverscrollHandoffState)) {
2727 // Transform |aStartPoint| and |aEndPoint| (which now represent the
2728 // portion of the displacement that wasn't consumed by APZCs later
2729 // in the handoff chain) back into |aPrev|'s coordinate space. This
2730 // allows the caller (which is |aPrev|) to interpret the unconsumed
2731 // displacement in its own coordinate space, and make use of it
2732 // (e.g. by going into overscroll).
2733 if (!TransformDisplacement(this, next, aPrev, aStartPoint, aEndPoint)) {
2734 NS_WARNING("Failed to untransform scroll points during dispatch");
2736 return false;
2739 // Return true to indicate the scroll was consumed entirely.
2740 return true;
2743 ParentLayerPoint APZCTreeManager::DispatchFling(
2744 AsyncPanZoomController* aPrev, const FlingHandoffState& aHandoffState) {
2745 // If immediate handoff is disallowed, do not allow handoff beyond the
2746 // single APZC that's scrolled by the input block that triggered this fling.
2747 if (aHandoffState.mIsHandoff && !StaticPrefs::apz_allow_immediate_handoff() &&
2748 aHandoffState.mScrolledApzc == aPrev) {
2749 FLING_LOG("APZCTM dropping handoff due to disallowed immediate handoff\n");
2750 return aHandoffState.mVelocity;
2753 const OverscrollHandoffChain* chain = aHandoffState.mChain;
2754 RefPtr<AsyncPanZoomController> current;
2755 uint32_t overscrollHandoffChainLength = chain->Length();
2756 uint32_t startIndex;
2758 // The fling's velocity needs to be transformed from the screen coordinates
2759 // of |aPrev| to the screen coordinates of |next|. To transform a velocity
2760 // correctly, we need to convert it to a displacement. For now, we do this
2761 // by anchoring it to a start point of (0, 0).
2762 // TODO: For this to be correct in the presence of 3D transforms, we should
2763 // use the end point of the touch that started the fling as the start point
2764 // rather than (0, 0).
2765 ParentLayerPoint startPoint; // (0, 0)
2766 ParentLayerPoint endPoint;
2768 if (aHandoffState.mIsHandoff) {
2769 startIndex = chain->IndexOf(aPrev) + 1;
2771 // IndexOf will return aOverscrollHandoffChain->Length() if
2772 // |aPrev| is not found.
2773 if (startIndex >= overscrollHandoffChainLength) {
2774 return aHandoffState.mVelocity;
2776 } else {
2777 startIndex = 0;
2780 // This will store any velocity left over after the entire handoff.
2781 ParentLayerPoint finalResidualVelocity = aHandoffState.mVelocity;
2783 ParentLayerPoint currentVelocity = aHandoffState.mVelocity;
2784 for (; startIndex < overscrollHandoffChainLength; startIndex++) {
2785 current = chain->GetApzcAtIndex(startIndex);
2787 // Make sure the apzc about to be handled can be handled
2788 if (current == nullptr || current->IsDestroyed()) {
2789 break;
2792 endPoint = startPoint + currentVelocity;
2794 RefPtr<AsyncPanZoomController> prevApzc =
2795 (startIndex > 0) ? chain->GetApzcAtIndex(startIndex - 1) : nullptr;
2797 // Only transform when current apzc can be transformed with previous
2798 if (prevApzc) {
2799 if (!TransformDisplacement(this, prevApzc, current, startPoint,
2800 endPoint)) {
2801 break;
2805 ParentLayerPoint availableVelocity = (endPoint - startPoint);
2806 ParentLayerPoint residualVelocity;
2808 FlingHandoffState transformedHandoffState = aHandoffState;
2809 transformedHandoffState.mVelocity = availableVelocity;
2811 // Obey overscroll-behavior.
2812 if (prevApzc) {
2813 residualVelocity += prevApzc->AdjustHandoffVelocityForOverscrollBehavior(
2814 transformedHandoffState.mVelocity);
2817 residualVelocity += current->AttemptFling(transformedHandoffState);
2819 // If there's no residual velocity, there's nothing more to hand off.
2820 if (current->IsZero(residualVelocity)) {
2821 return ParentLayerPoint();
2824 // If any of the velocity available to be handed off was consumed,
2825 // subtract the proportion of consumed velocity from finalResidualVelocity.
2826 // Note: it's important to compare |residualVelocity| to |availableVelocity|
2827 // here and not to |transformedHandoffState.mVelocity|, since the latter
2828 // may have been modified by AdjustHandoffVelocityForOverscrollBehavior().
2829 if (!current->IsZero(availableVelocity.x - residualVelocity.x)) {
2830 finalResidualVelocity.x *= (residualVelocity.x / availableVelocity.x);
2832 if (!current->IsZero(availableVelocity.y - residualVelocity.y)) {
2833 finalResidualVelocity.y *= (residualVelocity.y / availableVelocity.y);
2836 currentVelocity = residualVelocity;
2839 // Return any residual velocity left over after the entire handoff process.
2840 return finalResidualVelocity;
2843 already_AddRefed<AsyncPanZoomController> APZCTreeManager::GetTargetAPZC(
2844 const ScrollableLayerGuid& aGuid) {
2845 RecursiveMutexAutoLock lock(mTreeLock);
2846 RefPtr<HitTestingTreeNode> node = GetTargetNode(aGuid, nullptr);
2847 MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC
2848 RefPtr<AsyncPanZoomController> apzc = node ? node->GetApzc() : nullptr;
2849 return apzc.forget();
2852 already_AddRefed<AsyncPanZoomController> APZCTreeManager::GetTargetAPZC(
2853 const LayersId& aLayersId,
2854 const ScrollableLayerGuid::ViewID& aScrollId) const {
2855 MutexAutoLock lock(mMapLock);
2856 return GetTargetAPZC(aLayersId, aScrollId, lock);
2859 already_AddRefed<AsyncPanZoomController> APZCTreeManager::GetTargetAPZC(
2860 const LayersId& aLayersId, const ScrollableLayerGuid::ViewID& aScrollId,
2861 const MutexAutoLock& aProofOfMapLock) const {
2862 ScrollableLayerGuid guid(aLayersId, 0, aScrollId);
2863 auto it = mApzcMap.find(guid);
2864 RefPtr<AsyncPanZoomController> apzc =
2865 (it != mApzcMap.end() ? it->second.apzc : nullptr);
2866 return apzc.forget();
2869 already_AddRefed<HitTestingTreeNode> APZCTreeManager::GetTargetNode(
2870 const ScrollableLayerGuid& aGuid, GuidComparator aComparator) const {
2871 mTreeLock.AssertCurrentThreadIn();
2872 RefPtr<HitTestingTreeNode> target =
2873 DepthFirstSearchPostOrder<ReverseIterator>(
2874 mRootNode.get(), [&aGuid, &aComparator](HitTestingTreeNode* node) {
2875 bool matches = false;
2876 if (node->GetApzc()) {
2877 if (aComparator) {
2878 matches = aComparator(aGuid, node->GetApzc()->GetGuid());
2879 } else {
2880 matches = node->GetApzc()->Matches(aGuid);
2883 return matches;
2885 return target.forget();
2888 APZCTreeManager::HitTestResult APZCTreeManager::GetTargetAPZC(
2889 const ScreenPoint& aPoint) {
2890 RecursiveMutexAutoLock lock(mTreeLock);
2891 MOZ_ASSERT(mHitTester);
2892 return mHitTester->GetAPZCAtPoint(aPoint, lock);
2895 APZCTreeManager::TargetApzcForNodeResult APZCTreeManager::FindHandoffParent(
2896 const AsyncPanZoomController* aApzc) {
2897 RefPtr<HitTestingTreeNode> node = GetTargetNode(aApzc->GetGuid(), nullptr);
2898 while (node) {
2899 auto result = GetTargetApzcForNode(node->GetParent());
2900 if (result.mApzc) {
2901 // avoid infinite recursion in the overscroll handoff chain.
2902 if (result.mApzc != aApzc) {
2903 return result;
2906 node = node->GetParent();
2909 return {nullptr, false};
2912 RefPtr<const OverscrollHandoffChain>
2913 APZCTreeManager::BuildOverscrollHandoffChain(
2914 const RefPtr<AsyncPanZoomController>& aInitialTarget) {
2915 // Scroll grabbing is a mechanism that allows content to specify that
2916 // the initial target of a pan should be not the innermost scrollable
2917 // frame at the touch point (which is what GetTargetAPZC finds), but
2918 // something higher up in the tree.
2919 // It's not sufficient to just find the initial target, however, as
2920 // overscroll can be handed off to another APZC. Without scroll grabbing,
2921 // handoff just occurs from child to parent. With scroll grabbing, the
2922 // handoff order can be different, so we build a chain of APZCs in the
2923 // order in which scroll will be handed off to them.
2925 // Grab tree lock since we'll be walking the APZC tree.
2926 RecursiveMutexAutoLock lock(mTreeLock);
2928 // Build the chain. If there is a scroll parent link, we use that. This is
2929 // needed to deal with scroll info layers, because they participate in handoff
2930 // but do not follow the expected layer tree structure. If there are no
2931 // scroll parent links we just walk up the tree to find the scroll parent.
2932 OverscrollHandoffChain* result = new OverscrollHandoffChain;
2933 AsyncPanZoomController* apzc = aInitialTarget;
2934 while (apzc != nullptr) {
2935 result->Add(apzc);
2937 APZCTreeManager::TargetApzcForNodeResult handoffResult =
2938 FindHandoffParent(apzc);
2940 if (!handoffResult.mIsFixed && !apzc->IsRootForLayersId() &&
2941 apzc->GetScrollHandoffParentId() ==
2942 ScrollableLayerGuid::NULL_SCROLL_ID) {
2943 // This probably indicates a bug or missed case in layout code
2944 NS_WARNING("Found a non-root APZ with no handoff parent");
2947 // If `apzc` is inside fixed content, we want to hand off to the document's
2948 // root APZC next. The scroll parent id wouldn't give us this because it's
2949 // based on ASRs.
2950 if (handoffResult.mIsFixed || apzc->GetScrollHandoffParentId() ==
2951 ScrollableLayerGuid::NULL_SCROLL_ID) {
2952 apzc = handoffResult.mApzc;
2953 continue;
2956 // Guard against a possible infinite-loop condition. If we hit this, the
2957 // layout code that generates the handoff parents did something wrong.
2958 MOZ_ASSERT(apzc->GetScrollHandoffParentId() != apzc->GetGuid().mScrollId);
2959 RefPtr<AsyncPanZoomController> scrollParent = GetTargetAPZC(
2960 apzc->GetGuid().mLayersId, apzc->GetScrollHandoffParentId());
2961 apzc = scrollParent.get();
2964 // Now adjust the chain to account for scroll grabbing. Sorting is a bit
2965 // of an overkill here, but scroll grabbing will likely be generalized
2966 // to scroll priorities, so we might as well do it this way.
2967 result->SortByScrollPriority();
2969 // Print the overscroll chain for debugging.
2970 for (uint32_t i = 0; i < result->Length(); ++i) {
2971 APZCTM_LOG("OverscrollHandoffChain[%d] = %p\n", i,
2972 result->GetApzcAtIndex(i).get());
2975 return result;
2978 void APZCTreeManager::SetLongTapEnabled(bool aLongTapEnabled) {
2979 if (!APZThreadUtils::IsControllerThread()) {
2980 APZThreadUtils::RunOnControllerThread(NewRunnableMethod<bool>(
2981 "layers::APZCTreeManager::SetLongTapEnabled", this,
2982 &APZCTreeManager::SetLongTapEnabled, aLongTapEnabled));
2983 return;
2986 APZThreadUtils::AssertOnControllerThread();
2987 GestureEventListener::SetLongTapEnabled(aLongTapEnabled);
2990 void APZCTreeManager::AddInputBlockCallback(
2991 uint64_t aInputBlockId, InputBlockCallbackInfo&& aCallbackInfo) {
2992 APZThreadUtils::AssertOnControllerThread();
2993 mInputQueue->AddInputBlockCallback(aInputBlockId, std::move(aCallbackInfo));
2996 void APZCTreeManager::FindScrollThumbNode(
2997 const AsyncDragMetrics& aDragMetrics, LayersId aLayersId,
2998 HitTestingTreeNodeAutoLock& aOutThumbNode) {
2999 if (!aDragMetrics.mDirection) {
3000 // The AsyncDragMetrics has not been initialized yet - there will be
3001 // no matching node, so don't bother searching the tree.
3002 return;
3005 RecursiveMutexAutoLock lock(mTreeLock);
3007 RefPtr<HitTestingTreeNode> result = DepthFirstSearch<ReverseIterator>(
3008 mRootNode.get(), [&aDragMetrics, &aLayersId](HitTestingTreeNode* aNode) {
3009 return aNode->MatchesScrollDragMetrics(aDragMetrics, aLayersId);
3011 if (result) {
3012 aOutThumbNode.Initialize(lock, result.forget(), mTreeLock);
3016 APZCTreeManager::TargetApzcForNodeResult APZCTreeManager::GetTargetApzcForNode(
3017 const HitTestingTreeNode* aNode) {
3018 for (const HitTestingTreeNode* n = aNode;
3019 n && n->GetLayersId() == aNode->GetLayersId(); n = n->GetParent()) {
3020 // For a fixed node, GetApzc() may return an APZC for content in the
3021 // enclosing document, so we need to check GetFixedPosTarget() before
3022 // GetApzc().
3023 if (n->GetFixedPosTarget() != ScrollableLayerGuid::NULL_SCROLL_ID) {
3024 RefPtr<AsyncPanZoomController> fpTarget =
3025 GetTargetAPZC(n->GetLayersId(), n->GetFixedPosTarget());
3026 APZCTM_LOG("Found target APZC %p using fixed-pos lookup on %" PRIu64 "\n",
3027 fpTarget.get(), n->GetFixedPosTarget());
3028 return {fpTarget.get(), true};
3030 if (n->GetApzc()) {
3031 APZCTM_LOG("Found target %p using ancestor lookup\n", n->GetApzc());
3032 return {n->GetApzc(), false};
3035 return {nullptr, false};
3038 HitTestingTreeNode* APZCTreeManager::FindRootNodeForLayersId(
3039 LayersId aLayersId) const {
3040 mTreeLock.AssertCurrentThreadIn();
3042 HitTestingTreeNode* resultNode = BreadthFirstSearch<ReverseIterator>(
3043 mRootNode.get(), [aLayersId](HitTestingTreeNode* aNode) {
3044 AsyncPanZoomController* apzc = aNode->GetApzc();
3045 return apzc && apzc->GetLayersId() == aLayersId &&
3046 apzc->IsRootForLayersId();
3048 return resultNode;
3051 already_AddRefed<AsyncPanZoomController> APZCTreeManager::FindZoomableApzc(
3052 AsyncPanZoomController* aStart) const {
3053 return GetZoomableTarget(aStart, aStart);
3056 ScreenMargin APZCTreeManager::GetCompositorFixedLayerMargins() const {
3057 RecursiveMutexAutoLock lock(mTreeLock);
3058 return mCompositorFixedLayerMargins;
3061 AsyncPanZoomController* APZCTreeManager::FindRootApzcFor(
3062 LayersId aLayersId) const {
3063 RecursiveMutexAutoLock lock(mTreeLock);
3065 HitTestingTreeNode* resultNode = FindRootNodeForLayersId(aLayersId);
3066 return resultNode ? resultNode->GetApzc() : nullptr;
3069 AsyncPanZoomController* APZCTreeManager::FindRootContentApzcForLayersId(
3070 LayersId aLayersId) const {
3071 mTreeLock.AssertCurrentThreadIn();
3073 HitTestingTreeNode* resultNode = BreadthFirstSearch<ReverseIterator>(
3074 mRootNode.get(), [aLayersId](HitTestingTreeNode* aNode) {
3075 AsyncPanZoomController* apzc = aNode->GetApzc();
3076 return apzc && apzc->GetLayersId() == aLayersId &&
3077 apzc->IsRootContent();
3079 return resultNode ? resultNode->GetApzc() : nullptr;
3082 // clang-format off
3083 /* The methods GetScreenToApzcTransform() and GetApzcToGeckoTransform() return
3084 some useful transformations that input events may need applied. This is best
3085 illustrated with an example. Consider a chain of layers, L, M, N, O, P, Q, R. Layer L
3086 is the layer that corresponds to the argument |aApzc|, and layer R is the root
3087 of the layer tree. Layer M is the parent of L, N is the parent of M, and so on.
3088 When layer L is displayed to the screen by the compositor, the set of transforms that
3089 are applied to L are (in order from top to bottom):
3091 L's CSS transform (hereafter referred to as transform matrix LC)
3092 L's async transform (hereafter referred to as transform matrix LA)
3093 M's CSS transform (hereafter referred to as transform matrix MC)
3094 M's async transform (hereafter referred to as transform matrix MA)
3096 R's CSS transform (hereafter referred to as transform matrix RC)
3097 R's async transform (hereafter referred to as transform matrix RA)
3099 If we want user input to modify L's async transform, we have to first convert
3100 user input from screen space to the coordinate space of L's async transform. Doing
3101 this involves applying the following transforms (in order from top to bottom):
3102 RA.Inverse()
3103 RC.Inverse()
3105 MA.Inverse()
3106 MC.Inverse()
3107 This combined transformation is returned by GetScreenToApzcTransform().
3109 Next, if we want user inputs sent to gecko for event-dispatching, we will need to strip
3110 out all of the async transforms that are involved in this chain. This is because async
3111 transforms are stored only in the compositor and gecko does not account for them when
3112 doing display-list-based hit-testing for event dispatching.
3113 Furthermore, because these input events are processed by Gecko in a FIFO queue that
3114 includes other things (specifically paint requests), it is possible that by time the
3115 input event reaches gecko, it will have painted something else. Therefore, we need to
3116 apply another transform to the input events to account for the possible disparity between
3117 what we know gecko last painted and the last paint request we sent to gecko. Let this
3118 transform be represented by LD, MD, ... RD.
3119 Therefore, given a user input in screen space, the following transforms need to be applied
3120 (in order from top to bottom):
3121 RT.Inverse()
3122 RN.Inverse()
3123 RC.Inverse()
3125 MT.Inverse()
3126 MN.Inverse()
3127 MC.Inverse()
3128 LT.Inverse()
3129 LN.Inverse()
3130 LC.Inverse()
3138 This sequence can be simplified and refactored to the following:
3139 GetScreenToApzcTransform()
3140 LA.Inverse()
3147 Since GetScreenToApzcTransform() can be obtained by calling that function, GetApzcToGeckoTransform()
3148 returns the remaining transforms (LA.Inverse() * LD * ... * RD), so that the caller code can
3149 combine it with GetScreenToApzcTransform() to get the final transform required in this case.
3151 Note that for many of these layers, there will be no AsyncPanZoomController attached, and
3152 so the async transform will be the identity transform. So, in the example above, if layers
3153 L and P have APZC instances attached, MT, MN, MD, NT, NN, ND, OT, ON, OD, QT, QN, QD, RT,
3154 RN and RD will be identity transforms.
3155 Additionally, for space-saving purposes, each APZC instance stores its layer's individual
3156 CSS transform and the accumulation of CSS transforms to its parent APZC. So the APZC for
3157 layer L would store LC and (MC * NC * OC), and the layer P would store PC and (QC * RC).
3158 The APZC instances track the last dispatched paint request and so are able to calculate LD and
3159 PD using those internally stored values.
3160 The APZCs also obviously have LT, LN, PT, and PN, so all of the above transformation combinations
3161 required can be generated.
3163 // clang-format on
3166 * See the long comment above for a detailed explanation of this function.
3168 ScreenToParentLayerMatrix4x4 APZCTreeManager::GetScreenToApzcTransform(
3169 const AsyncPanZoomController* aApzc) const {
3170 Matrix4x4 result;
3171 RecursiveMutexAutoLock lock(mTreeLock);
3173 // The comments below assume there is a chain of layers L..R with L and P
3174 // having APZC instances as explained in the comment above. This function is
3175 // called with aApzc at L, and the loop below performs one iteration, where
3176 // parent is at P. The comments explain what values are stored in the
3177 // variables at these two levels. All the comments use standard matrix
3178 // notation where the leftmost matrix in a multiplication is applied first.
3180 // ancestorUntransform is PC.Inverse() * OC.Inverse() * NC.Inverse() *
3181 // MC.Inverse()
3182 Matrix4x4 ancestorUntransform = aApzc->GetAncestorTransform().Inverse();
3184 // result is initialized to PC.Inverse() * OC.Inverse() * NC.Inverse() *
3185 // MC.Inverse()
3186 result = ancestorUntransform;
3188 for (AsyncPanZoomController* parent = aApzc->GetParent(); parent;
3189 parent = parent->GetParent()) {
3190 // ancestorUntransform is updated to RC.Inverse() * QC.Inverse() when parent
3191 // == P
3192 ancestorUntransform = parent->GetAncestorTransform().Inverse();
3193 // asyncUntransform is updated to PA.Inverse() when parent == P
3194 Matrix4x4 asyncUntransform = parent
3195 ->GetAsyncTransformForInputTransformation(
3196 LayoutAndVisual, aApzc->GetLayersId())
3197 .Inverse()
3198 .ToUnknownMatrix();
3199 // untransformSinceLastApzc is RC.Inverse() * QC.Inverse() * PA.Inverse()
3200 Matrix4x4 untransformSinceLastApzc = ancestorUntransform * asyncUntransform;
3202 // result is RC.Inverse() * QC.Inverse() * PA.Inverse() * PC.Inverse() *
3203 // OC.Inverse() * NC.Inverse() * MC.Inverse()
3204 result = untransformSinceLastApzc * result;
3206 // The above value for result when parent == P matches the required output
3207 // as explained in the comment above this method. Note that any missing
3208 // terms are guaranteed to be identity transforms.
3211 return ViewAs<ScreenToParentLayerMatrix4x4>(result);
3215 * See the long comment above GetScreenToApzcTransform() for a detailed
3216 * explanation of this function.
3218 ParentLayerToParentLayerMatrix4x4 APZCTreeManager::GetApzcToApzcTransform(
3219 const AsyncPanZoomController* aStartApzc,
3220 const AsyncPanZoomController* aStopApzc,
3221 const AsyncTransformComponents& aComponents) const {
3222 Matrix4x4 result;
3223 RecursiveMutexAutoLock lock(mTreeLock);
3225 // The comments below assume there is a chain of layers L..R with L and P
3226 // having APZC instances as explained in the comment above, and |aStopApzc| is
3227 // nullptr. This function is called with aStartApzc at L, and the loop below
3228 // performs one iteration, where parent is at P. The comments explain what
3229 // values are stored in the variables at these two levels. All the comments
3230 // use standard matrix notation where the leftmost matrix in a multiplication
3231 // is applied first.
3233 // asyncUntransform is LA.Inverse()
3234 Matrix4x4 asyncUntransform = aStartApzc
3235 ->GetAsyncTransformForInputTransformation(
3236 aComponents, aStartApzc->GetLayersId())
3237 .Inverse()
3238 .ToUnknownMatrix();
3240 // result is initialized to LA.Inverse() * LD * MC * NC * OC * PC
3241 result = asyncUntransform *
3242 aStartApzc->GetTransformToLastDispatchedPaint(
3243 aComponents, aStartApzc->GetLayersId()) *
3244 aStartApzc->GetAncestorTransform();
3246 for (AsyncPanZoomController* parent = aStartApzc->GetParent();
3247 parent && parent != aStopApzc; parent = parent->GetParent()) {
3248 // result is LA.Inverse() * LD * MC * NC * OC * PC * PD * QC * RC
3250 // Note: Do not pass the async transform components for the current target
3251 // to the parent.
3252 result = result *
3253 parent->GetTransformToLastDispatchedPaint(
3254 LayoutAndVisual, aStartApzc->GetLayersId()) *
3255 parent->GetAncestorTransform();
3257 // The above value for result when parent == P matches the required output
3258 // as explained in the comment above this method. Note that any missing
3259 // terms are guaranteed to be identity transforms.
3262 return ViewAs<ParentLayerToParentLayerMatrix4x4>(result);
3265 ParentLayerToScreenMatrix4x4 APZCTreeManager::GetApzcToGeckoTransform(
3266 const AsyncPanZoomController* aApzc,
3267 const AsyncTransformComponents& aComponents) const {
3268 return ViewAs<ParentLayerToScreenMatrix4x4>(
3269 GetApzcToApzcTransform(aApzc, nullptr, aComponents),
3270 PixelCastJustification::ScreenIsParentLayerForRoot);
3273 ParentLayerToScreenMatrix4x4 APZCTreeManager::GetApzcToGeckoTransformForHit(
3274 HitTestResult& aHitResult) const {
3275 // Fixed content is only subject to the visual component of the async
3276 // transform.
3277 AsyncTransformComponents components =
3278 aHitResult.mFixedPosSides == SideBits::eNone
3279 ? LayoutAndVisual
3280 : AsyncTransformComponents{AsyncTransformComponent::eVisual};
3281 return GetApzcToGeckoTransform(aHitResult.mTargetApzc, components);
3284 CSSToCSSMatrix4x4 APZCTreeManager::GetOopifToRootContentTransform(
3285 AsyncPanZoomController* aApzc) const {
3286 MOZ_ASSERT(aApzc->IsRootForLayersId());
3288 RefPtr<AsyncPanZoomController> rootContentApzc = FindZoomableApzc(aApzc);
3289 MOZ_ASSERT(aApzc->GetLayersId() != rootContentApzc->GetLayersId(),
3290 "aApzc must be out-of-process of the rootContentApzc");
3291 if (!rootContentApzc || rootContentApzc == aApzc ||
3292 rootContentApzc->GetLayersId() == aApzc->GetLayersId()) {
3293 return CSSToCSSMatrix4x4();
3295 ParentLayerToParentLayerMatrix4x4 result =
3296 GetApzcToApzcTransform(aApzc, rootContentApzc,
3297 AsyncTransformComponent::eLayout) *
3298 // We need to multiply by the root content APZC's
3299 // GetPaintedResolutionTransform() here; See
3300 // https://phabricator.services.mozilla.com/D184440?vs=755900&id=757585#6173584
3301 // for the details.
3302 ViewAs<AsyncTransformComponentMatrix>(
3303 rootContentApzc->GetPaintedResolutionTransform());
3305 CSSToParentLayerScale thisZoom = aApzc->GetZoom();
3306 result.PreScale(thisZoom.scale, thisZoom.scale, 1.0);
3307 CSSToParentLayerScale rootZoom = rootContentApzc->GetZoom();
3308 if (rootZoom != CSSToParentLayerScale(0)) {
3309 result.PostScale(1.0 / rootZoom.scale, 1.0 / rootZoom.scale, 1.0);
3312 return ViewAs<CSSToCSSMatrix4x4>(result,
3313 PixelCastJustification::UntypedPrePostScale);
3316 CSSRect APZCTreeManager::ConvertRectInApzcToRoot(AsyncPanZoomController* aApzc,
3317 const CSSRect& aRect) const {
3318 MOZ_ASSERT(aApzc->IsRootForLayersId());
3319 RefPtr<AsyncPanZoomController> rootContentApzc = FindZoomableApzc(aApzc);
3320 if (!rootContentApzc || rootContentApzc == aApzc) {
3321 return aRect;
3324 return GetOopifToRootContentTransform(aApzc).TransformBounds(aRect);
3327 ScreenPoint APZCTreeManager::GetCurrentMousePosition() const {
3328 auto pos = mCurrentMousePosition.Lock();
3329 return pos.ref();
3332 void APZCTreeManager::SetCurrentMousePosition(const ScreenPoint& aNewPos) {
3333 auto pos = mCurrentMousePosition.Lock();
3334 pos.ref() = aNewPos;
3337 static AsyncPanZoomController* GetApzcWithDifferentLayersIdByWalkingParents(
3338 AsyncPanZoomController* aApzc) {
3339 if (!aApzc) {
3340 return nullptr;
3342 AsyncPanZoomController* parent = aApzc->GetParent();
3343 while (parent && (parent->GetLayersId() == aApzc->GetLayersId())) {
3344 parent = parent->GetParent();
3346 return parent;
3349 already_AddRefed<AsyncPanZoomController> APZCTreeManager::GetZoomableTarget(
3350 AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const {
3351 RecursiveMutexAutoLock lock(mTreeLock);
3352 RefPtr<AsyncPanZoomController> apzc;
3353 // For now, we only ever want to do pinching on the root-content APZC for
3354 // a given layers id.
3355 if (aApzc1 && aApzc2 && aApzc1->GetLayersId() == aApzc2->GetLayersId()) {
3356 // If the two APZCs have the same layers id, find the root-content APZC
3357 // for that layers id. Don't call CommonAncestor() because there may not
3358 // be a common ancestor for the layers id (e.g. if one APZCs is inside a
3359 // fixed-position element).
3360 apzc = FindRootContentApzcForLayersId(aApzc1->GetLayersId());
3361 if (apzc) {
3362 return apzc.forget();
3366 // Otherwise, find the common ancestor (to reach a common layers id), and then
3367 // walk up the apzc tree until we find a root-content APZC.
3368 apzc = CommonAncestor(aApzc1, aApzc2);
3369 RefPtr<AsyncPanZoomController> zoomable;
3370 while (apzc && !zoomable) {
3371 zoomable = FindRootContentApzcForLayersId(apzc->GetLayersId());
3372 apzc = GetApzcWithDifferentLayersIdByWalkingParents(apzc);
3375 return zoomable.forget();
3378 Maybe<ScreenIntPoint> APZCTreeManager::ConvertToGecko(
3379 const ScreenIntPoint& aPoint, AsyncPanZoomController* aApzc) {
3380 RecursiveMutexAutoLock lock(mTreeLock);
3381 // TODO: The current check assumes that a touch gesture and a touchpad tap
3382 // gesture can't both be active at the same time. If we turn on double-tap-
3383 // to-zoom on a touchscreen platform like Windows or Linux, this assumption
3384 // would no longer be valid, and we'd have to instead have TapGestureInput
3385 // track and inform this function whether it was created from touch events.
3386 const HitTestResult& hit = mInputQueue->GetCurrentTouchBlock()
3387 ? mTouchBlockHitResult
3388 : mTapGestureHitResult;
3389 AsyncTransformComponents components =
3390 hit.mFixedPosSides == SideBits::eNone
3391 ? LayoutAndVisual
3392 : AsyncTransformComponents{AsyncTransformComponent::eVisual};
3393 ScreenToScreenMatrix4x4 transformScreenToGecko =
3394 GetScreenToApzcTransform(aApzc) *
3395 GetApzcToGeckoTransform(aApzc, components);
3396 Maybe<ScreenIntPoint> geckoPoint =
3397 UntransformBy(transformScreenToGecko, aPoint);
3398 if (geckoPoint) {
3399 AdjustEventPointForDynamicToolbar(*geckoPoint, hit);
3401 return geckoPoint;
3404 already_AddRefed<AsyncPanZoomController> APZCTreeManager::CommonAncestor(
3405 AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const {
3406 mTreeLock.AssertCurrentThreadIn();
3407 RefPtr<AsyncPanZoomController> ancestor;
3409 // If either aApzc1 or aApzc2 is null, min(depth1, depth2) will be 0 and this
3410 // function will return null.
3412 // Calculate depth of the APZCs in the tree
3413 int depth1 = 0, depth2 = 0;
3414 for (AsyncPanZoomController* parent = aApzc1; parent;
3415 parent = parent->GetParent()) {
3416 depth1++;
3418 for (AsyncPanZoomController* parent = aApzc2; parent;
3419 parent = parent->GetParent()) {
3420 depth2++;
3423 // At most one of the following two loops will be executed; the deeper APZC
3424 // pointer will get walked up to the depth of the shallower one.
3425 int minDepth = depth1 < depth2 ? depth1 : depth2;
3426 while (depth1 > minDepth) {
3427 depth1--;
3428 aApzc1 = aApzc1->GetParent();
3430 while (depth2 > minDepth) {
3431 depth2--;
3432 aApzc2 = aApzc2->GetParent();
3435 // Walk up the ancestor chains of both APZCs, always staying at the same depth
3436 // for either APZC, and return the the first common ancestor encountered.
3437 while (true) {
3438 if (aApzc1 == aApzc2) {
3439 ancestor = aApzc1;
3440 break;
3442 if (depth1 <= 0) {
3443 break;
3445 aApzc1 = aApzc1->GetParent();
3446 aApzc2 = aApzc2->GetParent();
3448 return ancestor.forget();
3451 bool APZCTreeManager::IsFixedToRootContent(
3452 const HitTestingTreeNode* aNode) const {
3453 MutexAutoLock lock(mMapLock);
3454 return IsFixedToRootContent(FixedPositionInfo(aNode), lock);
3457 bool APZCTreeManager::IsFixedToRootContent(
3458 const FixedPositionInfo& aFixedInfo,
3459 const MutexAutoLock& aProofOfMapLock) const {
3460 ScrollableLayerGuid::ViewID fixedTarget = aFixedInfo.mFixedPosTarget;
3461 if (fixedTarget == ScrollableLayerGuid::NULL_SCROLL_ID) {
3462 return false;
3464 auto it =
3465 mApzcMap.find(ScrollableLayerGuid(aFixedInfo.mLayersId, 0, fixedTarget));
3466 if (it == mApzcMap.end()) {
3467 return false;
3469 RefPtr<AsyncPanZoomController> targetApzc = it->second.apzc;
3470 return targetApzc && targetApzc->IsRootContent();
3473 SideBits APZCTreeManager::SidesStuckToRootContent(
3474 const HitTestingTreeNode* aNode, AsyncTransformConsumer aMode) const {
3475 MutexAutoLock lock(mMapLock);
3476 return SidesStuckToRootContent(StickyPositionInfo(aNode), aMode, lock);
3479 SideBits APZCTreeManager::SidesStuckToRootContent(
3480 const StickyPositionInfo& aStickyInfo, AsyncTransformConsumer aMode,
3481 const MutexAutoLock& aProofOfMapLock) const {
3482 SideBits result = SideBits::eNone;
3484 ScrollableLayerGuid::ViewID stickyTarget = aStickyInfo.mStickyPosTarget;
3485 if (stickyTarget == ScrollableLayerGuid::NULL_SCROLL_ID) {
3486 return result;
3489 // We support the dynamic toolbar at top and bottom.
3490 if ((aStickyInfo.mFixedPosSides & SideBits::eTopBottom) == SideBits::eNone) {
3491 return result;
3494 auto it = mApzcMap.find(
3495 ScrollableLayerGuid(aStickyInfo.mLayersId, 0, stickyTarget));
3496 if (it == mApzcMap.end()) {
3497 return result;
3499 RefPtr<AsyncPanZoomController> stickyTargetApzc = it->second.apzc;
3500 if (!stickyTargetApzc || !stickyTargetApzc->IsRootContent()) {
3501 return result;
3504 ParentLayerPoint translation =
3505 stickyTargetApzc
3506 ->GetCurrentAsyncTransform(
3507 aMode, AsyncTransformComponents{AsyncTransformComponent::eLayout})
3508 .mTranslation;
3510 if (apz::IsStuckAtTop(translation.y, aStickyInfo.mStickyScrollRangeInner,
3511 aStickyInfo.mStickyScrollRangeOuter)) {
3512 result |= SideBits::eTop;
3514 if (apz::IsStuckAtBottom(translation.y, aStickyInfo.mStickyScrollRangeInner,
3515 aStickyInfo.mStickyScrollRangeOuter)) {
3516 result |= SideBits::eBottom;
3518 return result;
3521 LayerToParentLayerMatrix4x4 APZCTreeManager::ComputeTransformForScrollThumbNode(
3522 const HitTestingTreeNode* aNode) const {
3523 mTreeLock.AssertCurrentThreadIn();
3524 // The async transform applied here is for hit-testing purposes, and is
3525 // intended to match the one we tell WebRender to apply in
3526 // SampleForWebRender for rendering purposes.
3527 MOZ_ASSERT(aNode->IsScrollThumbNode());
3528 // If the scrollable element scrolled by the thumb is layerized, compute and
3529 // apply the transformation that will be applied to the thumb in
3530 // AsyncCompositionManager.
3531 ScrollableLayerGuid guid{aNode->GetLayersId(), 0, aNode->GetScrollTargetId()};
3532 if (RefPtr<HitTestingTreeNode> scrollTargetNode =
3533 GetTargetNode(guid, &ScrollableLayerGuid::EqualsIgnoringPresShell)) {
3534 AsyncPanZoomController* scrollTargetApzc = scrollTargetNode->GetApzc();
3535 MOZ_ASSERT(scrollTargetApzc);
3536 return scrollTargetApzc->CallWithLastContentPaintMetrics(
3537 [&](const FrameMetrics& aMetrics) {
3538 return ComputeTransformForScrollThumb(
3539 aNode->GetTransform() * AsyncTransformMatrix(),
3540 scrollTargetNode->GetTransform().ToUnknownMatrix(),
3541 scrollTargetApzc, aMetrics, aNode->GetScrollbarData(),
3542 scrollTargetNode->IsAncestorOf(aNode));
3545 // Otherwise, the node does not have an async transform.
3546 return aNode->GetTransform() * AsyncTransformMatrix();
3549 already_AddRefed<wr::WebRenderAPI> APZCTreeManager::GetWebRenderAPI() const {
3550 RefPtr<wr::WebRenderAPI> api;
3551 CompositorBridgeParent::CallWithIndirectShadowTree(
3552 mRootLayersId, [&](LayerTreeState& aState) -> void {
3553 if (aState.mWrBridge) {
3554 api = aState.mWrBridge->GetWebRenderAPI();
3557 return api.forget();
3560 /*static*/
3561 already_AddRefed<GeckoContentController> APZCTreeManager::GetContentController(
3562 LayersId aLayersId) {
3563 RefPtr<GeckoContentController> controller;
3564 CompositorBridgeParent::CallWithIndirectShadowTree(
3565 aLayersId,
3566 [&](LayerTreeState& aState) -> void { controller = aState.mController; });
3567 return controller.forget();
3570 ScreenMargin APZCTreeManager::GetCompositorFixedLayerMargins(
3571 const MutexAutoLock& aProofOfMapLock) const {
3572 ScreenMargin result = mCompositorFixedLayerMargins;
3573 if (StaticPrefs::apz_fixed_margin_override_enabled()) {
3574 result.top = StaticPrefs::apz_fixed_margin_override_top();
3575 result.bottom = StaticPrefs::apz_fixed_margin_override_bottom();
3577 return result;
3580 bool APZCTreeManager::GetAPZTestData(LayersId aLayersId,
3581 APZTestData* aOutData) {
3582 AssertOnUpdaterThread();
3584 { // copy the relevant test data into aOutData while holding the
3585 // mTestDataLock
3586 MutexAutoLock lock(mTestDataLock);
3587 auto it = mTestData.find(aLayersId);
3588 if (it == mTestData.end()) {
3589 return false;
3591 *aOutData = *(it->second);
3594 { // add some additional "current state" into the returned APZTestData
3595 MutexAutoLock mapLock(mMapLock);
3597 ClippedCompositionBoundsMap clippedCompBounds;
3598 for (const auto& mapping : mApzcMap) {
3599 if (mapping.first.mLayersId != aLayersId) {
3600 continue;
3603 ParentLayerRect clippedBounds = ComputeClippedCompositionBounds(
3604 mapLock, clippedCompBounds, mapping.first);
3605 AsyncPanZoomController* apzc = mapping.second.apzc;
3606 std::string viewId = std::to_string(mapping.first.mScrollId);
3607 std::string apzcState;
3608 if (apzc->GetCheckerboardMagnitude(clippedBounds)) {
3609 apzcState += "checkerboarding,";
3611 if (apzc->IsOverscrolled()) {
3612 apzcState += "overscrolled,";
3614 aOutData->RecordAdditionalData(viewId, apzcState);
3617 return true;
3620 void APZCTreeManager::SendSubtreeTransformsToChromeMainThread(
3621 const AsyncPanZoomController* aAncestor) {
3622 RefPtr<GeckoContentController> controller =
3623 GetContentController(mRootLayersId);
3624 if (!controller) {
3625 return;
3627 nsTArray<MatrixMessage> messages;
3628 bool underAncestor = (aAncestor == nullptr);
3629 bool shouldNotify = false;
3631 RecursiveMutexAutoLock lock(mTreeLock);
3632 if (!mRootNode) {
3633 // Event dispatched during shutdown, after ClearTree().
3634 // Note, mRootNode needs to be checked with mTreeLock held.
3635 return;
3637 // This formulation duplicates matrix multiplications closer
3638 // to the root of the tree. For now, aiming for separation
3639 // of concerns rather than minimum number of multiplications.
3640 ForEachNode<ReverseIterator>(
3641 mRootNode.get(),
3642 [&](HitTestingTreeNode* aNode) {
3643 mTreeLock.AssertCurrentThreadIn();
3644 bool atAncestor = (aAncestor && aNode->GetApzc() == aAncestor);
3645 MOZ_ASSERT(!(underAncestor && atAncestor));
3646 underAncestor |= atAncestor;
3647 if (!underAncestor) {
3648 return;
3650 LayersId layersId = aNode->GetLayersId();
3651 HitTestingTreeNode* parent = aNode->GetParent();
3652 if (!parent) {
3653 messages.AppendElement(MatrixMessage(Some(LayerToScreenMatrix4x4()),
3654 ScreenRect(), layersId));
3655 } else if (layersId != parent->GetLayersId()) {
3656 if (mDetachedLayersIds.find(layersId) != mDetachedLayersIds.end()) {
3657 messages.AppendElement(
3658 MatrixMessage(Nothing(), ScreenRect(), layersId));
3659 } else {
3660 // It's important to pass aRemoteLayersId=layersId here (not
3661 // parent->GetLayersId()). layersId is the LayersId of the
3662 // remote content for which this transform is being computed.
3663 messages.AppendElement(MatrixMessage(
3664 Some(parent->GetTransformToGecko(layersId)),
3665 parent->GetRemoteDocumentScreenRect(layersId), layersId));
3669 [&](HitTestingTreeNode* aNode) {
3670 bool atAncestor = (aAncestor && aNode->GetApzc() == aAncestor);
3671 if (atAncestor) {
3672 MOZ_ASSERT(underAncestor);
3673 underAncestor = false;
3676 if (messages != mLastMessages) {
3677 mLastMessages = messages;
3678 shouldNotify = true;
3681 if (shouldNotify) {
3682 controller->NotifyLayerTransforms(std::move(messages));
3686 void APZCTreeManager::SetFixedLayerMargins(ScreenIntCoord aTop,
3687 ScreenIntCoord aBottom) {
3688 MutexAutoLock lock(mMapLock);
3689 mCompositorFixedLayerMargins.top = ScreenCoord(aTop);
3690 mCompositorFixedLayerMargins.bottom = ScreenCoord(aBottom);
3693 /*static*/
3694 LayerToParentLayerMatrix4x4 APZCTreeManager::ComputeTransformForScrollThumb(
3695 const LayerToParentLayerMatrix4x4& aCurrentTransform,
3696 const Matrix4x4& aScrollableContentTransform, AsyncPanZoomController* aApzc,
3697 const FrameMetrics& aMetrics, const ScrollbarData& aScrollbarData,
3698 bool aScrollbarIsDescendant) {
3699 return apz::ComputeTransformForScrollThumb(
3700 aCurrentTransform, aScrollableContentTransform, aApzc, aMetrics,
3701 aScrollbarData, aScrollbarIsDescendant);
3704 APZSampler* APZCTreeManager::GetSampler() const {
3705 // We should always have a sampler here, since in practice the sampler
3706 // is destroyed at the same time that this APZCTreeMAnager instance is.
3707 MOZ_ASSERT(mSampler);
3708 return mSampler;
3711 void APZCTreeManager::AssertOnSamplerThread() {
3712 GetSampler()->AssertOnSamplerThread();
3715 APZUpdater* APZCTreeManager::GetUpdater() const {
3716 // We should always have an updater here, since in practice the updater
3717 // is destroyed at the same time that this APZCTreeManager instance is.
3718 MOZ_ASSERT(mUpdater);
3719 return mUpdater;
3722 void APZCTreeManager::AssertOnUpdaterThread() {
3723 GetUpdater()->AssertOnUpdaterThread();
3726 MOZ_PUSH_IGNORE_THREAD_SAFETY
3727 void APZCTreeManager::LockTree() {
3728 AssertOnUpdaterThread();
3729 mTreeLock.Lock();
3732 void APZCTreeManager::UnlockTree() {
3733 AssertOnUpdaterThread();
3734 mTreeLock.Unlock();
3736 MOZ_POP_THREAD_SAFETY
3738 void APZCTreeManager::SetDPI(float aDpiValue) {
3739 if (!APZThreadUtils::IsControllerThread()) {
3740 APZThreadUtils::RunOnControllerThread(
3741 NewRunnableMethod<float>("layers::APZCTreeManager::SetDPI", this,
3742 &APZCTreeManager::SetDPI, aDpiValue));
3743 return;
3746 APZThreadUtils::AssertOnControllerThread();
3747 mDPI = aDpiValue;
3750 float APZCTreeManager::GetDPI() const {
3751 APZThreadUtils::AssertOnControllerThread();
3752 return mDPI;
3755 APZCTreeManager::FixedPositionInfo::FixedPositionInfo(
3756 const HitTestingTreeNode* aNode) {
3757 mFixedPositionAnimationId = aNode->GetFixedPositionAnimationId();
3758 mFixedPosSides = aNode->GetFixedPosSides();
3759 mFixedPosTarget = aNode->GetFixedPosTarget();
3760 mLayersId = aNode->GetLayersId();
3763 APZCTreeManager::StickyPositionInfo::StickyPositionInfo(
3764 const HitTestingTreeNode* aNode) {
3765 mStickyPositionAnimationId = aNode->GetStickyPositionAnimationId();
3766 mFixedPosSides = aNode->GetFixedPosSides();
3767 mStickyPosTarget = aNode->GetStickyPosTarget();
3768 mLayersId = aNode->GetLayersId();
3769 mStickyScrollRangeInner = aNode->GetStickyScrollRangeInner();
3770 mStickyScrollRangeOuter = aNode->GetStickyScrollRangeOuter();
3773 } // namespace layers
3774 } // namespace mozilla