summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/src/APZCTreeManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/layers/apz/src/APZCTreeManager.cpp')
-rw-r--r--gfx/layers/apz/src/APZCTreeManager.cpp2099
1 files changed, 2099 insertions, 0 deletions
diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp
new file mode 100644
index 000000000..857ae5958
--- /dev/null
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -0,0 +1,2099 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stack>
+#include "APZCTreeManager.h"
+#include "AsyncPanZoomController.h"
+#include "Compositor.h" // for Compositor
+#include "DragTracker.h" // for DragTracker
+#include "gfxPrefs.h" // for gfxPrefs
+#include "HitTestingTreeNode.h" // for HitTestingTreeNode
+#include "InputBlockState.h" // for InputBlockState
+#include "InputData.h" // for InputData, etc
+#include "Layers.h" // for Layer, etc
+#include "mozilla/dom/Touch.h" // for Touch
+#include "mozilla/gfx/GPUParent.h" // for GPUParent
+#include "mozilla/gfx/Logging.h" // for gfx::TreeLog
+#include "mozilla/gfx/Point.h" // for Point
+#include "mozilla/layers/APZThreadUtils.h" // for AssertOnCompositorThread, etc
+#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
+#include "mozilla/layers/AsyncDragMetrics.h" // for AsyncDragMetrics
+#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc
+#include "mozilla/layers/LayerMetricsWrapper.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/mozalloc.h" // for operator new
+#include "mozilla/TouchEvents.h"
+#include "mozilla/Preferences.h" // for Preferences
+#include "mozilla/EventStateManager.h" // for WheelPrefs
+#include "nsDebug.h" // for NS_WARNING
+#include "nsPoint.h" // for nsIntPoint
+#include "nsThreadUtils.h" // for NS_IsMainThread
+#include "OverscrollHandoffState.h" // for OverscrollHandoffState
+#include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch, etc
+#include "LayersLogging.h" // for Stringify
+#include "Units.h" // for ParentlayerPixel
+#include "GestureEventListener.h" // for GestureEventListener::setLongTapEnabled
+#include "UnitTransforms.h" // for ViewAs
+
+#define ENABLE_APZCTM_LOGGING 0
+// #define ENABLE_APZCTM_LOGGING 1
+
+#if ENABLE_APZCTM_LOGGING
+# define APZCTM_LOG(...) printf_stderr("APZCTM: " __VA_ARGS__)
+#else
+# define APZCTM_LOG(...)
+#endif
+
+namespace mozilla {
+namespace layers {
+
+typedef mozilla::gfx::Point Point;
+typedef mozilla::gfx::Point4D Point4D;
+typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+
+float APZCTreeManager::sDPI = 160.0;
+
+struct APZCTreeManager::TreeBuildingState {
+ TreeBuildingState(const CompositorBridgeParent::LayerTreeState* const aLayerTreeState,
+ bool aIsFirstPaint, uint64_t aOriginatingLayersId,
+ APZTestData* aTestData, uint32_t aPaintSequence)
+ : mLayerTreeState(aLayerTreeState)
+ , mIsFirstPaint(aIsFirstPaint)
+ , mOriginatingLayersId(aOriginatingLayersId)
+ , mPaintLogger(aTestData, aPaintSequence)
+ {
+ }
+
+ // State that doesn't change as we recurse in the tree building
+ const CompositorBridgeParent::LayerTreeState* const mLayerTreeState;
+ const bool mIsFirstPaint;
+ const uint64_t mOriginatingLayersId;
+ const APZPaintLogHelper mPaintLogger;
+
+ // State that is updated as we perform the tree build
+
+ // A list of nodes that need to be destroyed at the end of the tree building.
+ // This is initialized with all nodes in the old tree, and nodes are removed
+ // from it as we reuse them in the new tree.
+ nsTArray<RefPtr<HitTestingTreeNode>> mNodesToDestroy;
+
+ // This map is populated as we place APZCs into the new tree. Its purpose is
+ // to facilitate re-using the same APZC for different layers that scroll
+ // together (and thus have the same ScrollableLayerGuid).
+ std::map<ScrollableLayerGuid, AsyncPanZoomController*> mApzcMap;
+};
+
+class APZCTreeManager::CheckerboardFlushObserver : public nsIObserver {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ explicit CheckerboardFlushObserver(APZCTreeManager* aTreeManager)
+ : mTreeManager(aTreeManager)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsSvc);
+ if (obsSvc) {
+ obsSvc->AddObserver(this, "APZ:FlushActiveCheckerboard", false);
+ }
+ }
+
+ void Unregister()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(this, "APZ:FlushActiveCheckerboard");
+ }
+ mTreeManager = nullptr;
+ }
+
+protected:
+ virtual ~CheckerboardFlushObserver() {}
+
+private:
+ RefPtr<APZCTreeManager> mTreeManager;
+};
+
+NS_IMPL_ISUPPORTS(APZCTreeManager::CheckerboardFlushObserver, nsIObserver)
+
+NS_IMETHODIMP
+APZCTreeManager::CheckerboardFlushObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t*)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mTreeManager.get());
+
+ MutexAutoLock lock(mTreeManager->mTreeLock);
+ if (mTreeManager->mRootNode) {
+ ForEachNode<ReverseIterator>(mTreeManager->mRootNode.get(),
+ [](HitTestingTreeNode* aNode)
+ {
+ if (aNode->IsPrimaryHolder()) {
+ MOZ_ASSERT(aNode->GetApzc());
+ aNode->GetApzc()->FlushActiveCheckerboardReport();
+ }
+ });
+ }
+ if (XRE_IsGPUProcess()) {
+ if (gfx::GPUParent* gpu = gfx::GPUParent::GetSingleton()) {
+ nsCString topic("APZ:FlushActiveCheckerboard:Done");
+ Unused << gpu->SendNotifyUiObservers(topic);
+ }
+ } else {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->NotifyObservers(nullptr, "APZ:FlushActiveCheckerboard:Done", nullptr);
+ }
+ }
+ return NS_OK;
+}
+
+
+/*static*/ const ScreenMargin
+APZCTreeManager::CalculatePendingDisplayPort(
+ const FrameMetrics& aFrameMetrics,
+ const ParentLayerPoint& aVelocity)
+{
+ return AsyncPanZoomController::CalculatePendingDisplayPort(
+ aFrameMetrics, aVelocity);
+}
+
+APZCTreeManager::APZCTreeManager()
+ : mInputQueue(new InputQueue()),
+ mTreeLock("APZCTreeLock"),
+ mHitResultForInputBlock(HitNothing),
+ mRetainedTouchIdentifier(-1),
+ mApzcTreeLog("apzctree")
+{
+ RefPtr<APZCTreeManager> self(this);
+ NS_DispatchToMainThread(NS_NewRunnableFunction([self] {
+ self->mFlushObserver = new CheckerboardFlushObserver(self);
+ }));
+ AsyncPanZoomController::InitializeGlobalState();
+ mApzcTreeLog.ConditionOnPrefFunction(gfxPrefs::APZPrintTree);
+}
+
+APZCTreeManager::~APZCTreeManager()
+{
+}
+
+/*static*/ void
+APZCTreeManager::InitializeGlobalState()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ AsyncPanZoomController::InitializeGlobalState();
+}
+
+AsyncPanZoomController*
+APZCTreeManager::NewAPZCInstance(uint64_t aLayersId,
+ GeckoContentController* aController)
+{
+ return new AsyncPanZoomController(aLayersId, this, mInputQueue,
+ aController, AsyncPanZoomController::USE_GESTURE_DETECTOR);
+}
+
+TimeStamp
+APZCTreeManager::GetFrameTime()
+{
+ return TimeStamp::Now();
+}
+
+void
+APZCTreeManager::SetAllowedTouchBehavior(uint64_t aInputBlockId,
+ const nsTArray<TouchBehaviorFlags> &aValues)
+{
+ mInputQueue->SetAllowedTouchBehavior(aInputBlockId, aValues);
+}
+
+void
+APZCTreeManager::UpdateHitTestingTree(uint64_t aRootLayerTreeId,
+ Layer* aRoot,
+ bool aIsFirstPaint,
+ uint64_t aOriginatingLayersId,
+ uint32_t aPaintSequenceNumber)
+{
+ APZThreadUtils::AssertOnCompositorThread();
+
+ MutexAutoLock lock(mTreeLock);
+
+ // For testing purposes, we log some data to the APZTestData associated with
+ // the layers id that originated this update.
+ APZTestData* testData = nullptr;
+ if (gfxPrefs::APZTestLoggingEnabled()) {
+ if (CompositorBridgeParent::LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(aOriginatingLayersId)) {
+ testData = &state->mApzTestData;
+ testData->StartNewPaint(aPaintSequenceNumber);
+ }
+ }
+
+ const CompositorBridgeParent::LayerTreeState* treeState =
+ CompositorBridgeParent::GetIndirectShadowTree(aRootLayerTreeId);
+ MOZ_ASSERT(treeState);
+ TreeBuildingState state(treeState, aIsFirstPaint, aOriginatingLayersId,
+ testData, aPaintSequenceNumber);
+
+ // We do this business with collecting the entire tree into an array because otherwise
+ // it's very hard to determine which APZC instances need to be destroyed. In the worst
+ // case, there are two scenarios: (a) a layer with an APZC is removed from the layer
+ // tree and (b) a layer with an APZC is moved in the layer tree from one place to a
+ // completely different place. In scenario (a) we would want to destroy the APZC while
+ // walking the layer tree and noticing that the layer/APZC is no longer there. But if
+ // we do that then we run into a problem in scenario (b) because we might encounter that
+ // layer later during the walk. To handle both of these we have to 'remember' that the
+ // layer was not found, and then do the destroy only at the end of the tree walk after
+ // we are sure that the layer was removed and not just transplanted elsewhere. Doing that
+ // as part of a recursive tree walk is hard and so maintaining a list and removing
+ // APZCs that are still alive is much simpler.
+ ForEachNode<ReverseIterator>(mRootNode.get(),
+ [&state] (HitTestingTreeNode* aNode)
+ {
+ state.mNodesToDestroy.AppendElement(aNode);
+ });
+ mRootNode = nullptr;
+
+ if (aRoot) {
+ std::stack<gfx::TreeAutoIndent> indents;
+ std::stack<gfx::Matrix4x4> ancestorTransforms;
+ HitTestingTreeNode* parent = nullptr;
+ HitTestingTreeNode* next = nullptr;
+ uint64_t layersId = aRootLayerTreeId;
+ ancestorTransforms.push(Matrix4x4());
+
+ mApzcTreeLog << "[start]\n";
+ LayerMetricsWrapper root(aRoot);
+ mTreeLock.AssertCurrentThreadOwns();
+
+ ForEachNode<ReverseIterator>(root,
+ [&](LayerMetricsWrapper aLayerMetrics)
+ {
+ mApzcTreeLog << aLayerMetrics.Name() << '\t';
+
+ HitTestingTreeNode* node = PrepareNodeForLayer(aLayerMetrics,
+ aLayerMetrics.Metrics(), layersId, ancestorTransforms.top(),
+ parent, next, state);
+ MOZ_ASSERT(node);
+ AsyncPanZoomController* apzc = node->GetApzc();
+ aLayerMetrics.SetApzc(apzc);
+
+ mApzcTreeLog << '\n';
+
+ // Accumulate the CSS transform between layers that have an APZC.
+ // In the terminology of the big comment above APZCTreeManager::GetScreenToApzcTransform, if
+ // we are at layer M, then aAncestorTransform is NC * OC * PC, and we left-multiply MC and
+ // compute ancestorTransform to be MC * NC * OC * PC. This gets passed down as the ancestor
+ // transform to layer L when we recurse into the children below. If we are at a layer
+ // with an APZC, such as P, then we reset the ancestorTransform to just PC, to start
+ // the new accumulation as we go down.
+ // If a transform is a perspective transform, it's ignored for this purpose
+ // (see bug 1168263).
+ Matrix4x4 currentTransform = aLayerMetrics.TransformIsPerspective() ? Matrix4x4() : aLayerMetrics.GetTransform();
+ if (!apzc) {
+ currentTransform = currentTransform * ancestorTransforms.top();
+ }
+ ancestorTransforms.push(currentTransform);
+
+ // Note that |node| at this point will not have any children, otherwise we
+ // we would have to set next to node->GetFirstChild().
+ MOZ_ASSERT(!node->GetFirstChild());
+ parent = node;
+ next = nullptr;
+ layersId = (aLayerMetrics.AsRefLayer() ? aLayerMetrics.AsRefLayer()->GetReferentId() : layersId);
+ indents.push(gfx::TreeAutoIndent(mApzcTreeLog));
+ },
+ [&](LayerMetricsWrapper aLayerMetrics)
+ {
+ next = parent;
+ parent = parent->GetParent();
+ layersId = next->GetLayersId();
+ ancestorTransforms.pop();
+ indents.pop();
+ });
+
+ mApzcTreeLog << "[end]\n";
+ }
+
+ // We do not support tree structures where the root node has siblings.
+ MOZ_ASSERT(!(mRootNode && mRootNode->GetPrevSibling()));
+
+ for (size_t i = 0; i < state.mNodesToDestroy.Length(); i++) {
+ APZCTM_LOG("Destroying node at %p with APZC %p\n",
+ state.mNodesToDestroy[i].get(),
+ state.mNodesToDestroy[i]->GetApzc());
+ state.mNodesToDestroy[i]->Destroy();
+ }
+
+#if ENABLE_APZCTM_LOGGING
+ // Make the hit-test tree line up with the layer dump
+ printf_stderr("APZCTreeManager (%p)\n", this);
+ mRootNode->Dump(" ");
+#endif
+}
+
+// Compute the clip region to be used for a layer with an APZC. This function
+// is only called for layers which actually have scrollable metrics and an APZC.
+static ParentLayerIntRegion
+ComputeClipRegion(GeckoContentController* aController,
+ const LayerMetricsWrapper& aLayer)
+{
+ ParentLayerIntRegion clipRegion;
+ if (aLayer.GetClipRect()) {
+ clipRegion = *aLayer.GetClipRect();
+ } else {
+ // if there is no clip on this layer (which should only happen for the
+ // root scrollable layer in a process, or for some of the LayerMetrics
+ // expansions of a multi-metrics layer), fall back to using the comp
+ // bounds which should be equivalent.
+ clipRegion = RoundedToInt(aLayer.Metrics().GetCompositionBounds());
+ }
+
+ return clipRegion;
+}
+
+void
+APZCTreeManager::PrintAPZCInfo(const LayerMetricsWrapper& aLayer,
+ const AsyncPanZoomController* apzc)
+{
+ const FrameMetrics& metrics = aLayer.Metrics();
+ mApzcTreeLog << "APZC " << apzc->GetGuid()
+ << "\tcb=" << metrics.GetCompositionBounds()
+ << "\tsr=" << metrics.GetScrollableRect()
+ << (aLayer.IsScrollInfoLayer() ? "\tscrollinfo" : "")
+ << (apzc->HasScrollgrab() ? "\tscrollgrab" : "") << "\t"
+ << aLayer.Metadata().GetContentDescription().get();
+}
+
+void
+APZCTreeManager::AttachNodeToTree(HitTestingTreeNode* aNode,
+ HitTestingTreeNode* aParent,
+ HitTestingTreeNode* aNextSibling)
+{
+ if (aNextSibling) {
+ aNextSibling->SetPrevSibling(aNode);
+ } else if (aParent) {
+ aParent->SetLastChild(aNode);
+ } else {
+ MOZ_ASSERT(!mRootNode);
+ mRootNode = aNode;
+ aNode->MakeRoot();
+ }
+}
+
+static EventRegions
+GetEventRegions(const LayerMetricsWrapper& aLayer)
+{
+ if (aLayer.IsScrollInfoLayer()) {
+ ParentLayerIntRect compositionBounds(RoundedToInt(aLayer.Metrics().GetCompositionBounds()));
+ nsIntRegion hitRegion(compositionBounds.ToUnknownRect());
+ EventRegions eventRegions(hitRegion);
+ eventRegions.mDispatchToContentHitRegion = eventRegions.mHitRegion;
+ return eventRegions;
+ }
+ return aLayer.GetEventRegions();
+}
+
+already_AddRefed<HitTestingTreeNode>
+APZCTreeManager::RecycleOrCreateNode(TreeBuildingState& aState,
+ AsyncPanZoomController* aApzc,
+ uint64_t aLayersId)
+{
+ // Find a node without an APZC and return it. Note that unless the layer tree
+ // actually changes, this loop should generally do an early-return on the
+ // first iteration, so it should be cheap in the common case.
+ for (size_t i = 0; i < aState.mNodesToDestroy.Length(); i++) {
+ RefPtr<HitTestingTreeNode> node = aState.mNodesToDestroy[i];
+ if (!node->IsPrimaryHolder()) {
+ aState.mNodesToDestroy.RemoveElement(node);
+ node->RecycleWith(aApzc, aLayersId);
+ return node.forget();
+ }
+ }
+ RefPtr<HitTestingTreeNode> node = new HitTestingTreeNode(aApzc, false, aLayersId);
+ return node.forget();
+}
+
+static EventRegionsOverride
+GetEventRegionsOverride(HitTestingTreeNode* aParent,
+ const LayerMetricsWrapper& aLayer)
+{
+ // Make it so that if the flag is set on the layer tree, it automatically
+ // propagates to all the nodes in the corresponding subtree rooted at that
+ // layer in the hit-test tree. This saves having to walk up the tree every
+ // we want to see if a hit-test node is affected by this flag.
+ EventRegionsOverride result = aLayer.GetEventRegionsOverride();
+ if (aParent) {
+ result |= aParent->GetEventRegionsOverride();
+ }
+ return result;
+}
+
+void
+APZCTreeManager::StartScrollbarDrag(const ScrollableLayerGuid& aGuid,
+ const AsyncDragMetrics& aDragMetrics)
+{
+
+ RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
+ if (!apzc) {
+ return;
+ }
+
+ uint64_t inputBlockId = aDragMetrics.mDragStartSequenceNumber;
+ mInputQueue->ConfirmDragBlock(inputBlockId, apzc, aDragMetrics);
+}
+
+HitTestingTreeNode*
+APZCTreeManager::PrepareNodeForLayer(const LayerMetricsWrapper& aLayer,
+ const FrameMetrics& aMetrics,
+ uint64_t aLayersId,
+ const gfx::Matrix4x4& aAncestorTransform,
+ HitTestingTreeNode* aParent,
+ HitTestingTreeNode* aNextSibling,
+ TreeBuildingState& aState)
+{
+ mTreeLock.AssertCurrentThreadOwns();
+
+ bool needsApzc = true;
+ if (!aMetrics.IsScrollable()) {
+ needsApzc = false;
+ }
+
+ const CompositorBridgeParent::LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(aLayersId);
+ if (!(state && state->mController.get())) {
+ needsApzc = false;
+ }
+
+ RefPtr<HitTestingTreeNode> node = nullptr;
+ if (!needsApzc) {
+ node = RecycleOrCreateNode(aState, nullptr, aLayersId);
+ AttachNodeToTree(node, aParent, aNextSibling);
+ node->SetHitTestData(
+ GetEventRegions(aLayer),
+ aLayer.GetTransformTyped(),
+ aLayer.GetClipRect() ? Some(ParentLayerIntRegion(*aLayer.GetClipRect())) : Nothing(),
+ GetEventRegionsOverride(aParent, aLayer));
+ node->SetScrollbarData(aLayer.GetScrollbarTargetContainerId(),
+ aLayer.GetScrollbarDirection(),
+ aLayer.GetScrollbarSize(),
+ aLayer.IsScrollbarContainer());
+ node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId());
+ return node;
+ }
+
+ AsyncPanZoomController* apzc = nullptr;
+ // If we get here, aLayer is a scrollable layer and somebody
+ // has registered a GeckoContentController for it, so we need to ensure
+ // it has an APZC instance to manage its scrolling.
+
+ // aState.mApzcMap allows reusing the exact same APZC instance for different layers
+ // with the same FrameMetrics data. This is needed because in some cases content
+ // that is supposed to scroll together is split into multiple layers because of
+ // e.g. non-scrolling content interleaved in z-index order.
+ ScrollableLayerGuid guid(aLayersId, aMetrics);
+ auto insertResult = aState.mApzcMap.insert(std::make_pair(guid, static_cast<AsyncPanZoomController*>(nullptr)));
+ if (!insertResult.second) {
+ apzc = insertResult.first->second;
+ PrintAPZCInfo(aLayer, apzc);
+ }
+ APZCTM_LOG("Found APZC %p for layer %p with identifiers %" PRId64 " %" PRId64 "\n", apzc, aLayer.GetLayer(), guid.mLayersId, guid.mScrollId);
+
+ // If we haven't encountered a layer already with the same metrics, then we need to
+ // do the full reuse-or-make-an-APZC algorithm, which is contained inside the block
+ // below.
+ if (apzc == nullptr) {
+ apzc = aLayer.GetApzc();
+
+ // If the content represented by the scrollable layer has changed (which may
+ // be possible because of DLBI heuristics) then we don't want to keep using
+ // the same old APZC for the new content. Also, when reparenting a tab into a
+ // new window a layer might get moved to a different layer tree with a
+ // different APZCTreeManager. In these cases we don't want to reuse the same
+ // APZC, so null it out so we run through the code to find another one or
+ // create one.
+ if (apzc && (!apzc->Matches(guid) || !apzc->HasTreeManager(this))) {
+ apzc = nullptr;
+ }
+
+ // See if we can find an APZC from the previous tree that matches the
+ // ScrollableLayerGuid from this layer. If there is one, then we know that
+ // the layout of the page changed causing the layer tree to be rebuilt, but
+ // the underlying content for the APZC is still there somewhere. Therefore,
+ // we want to find the APZC instance and continue using it here.
+ //
+ // We particularly want to find the primary-holder node from the previous
+ // tree that matches, because we don't want that node to get destroyed. If
+ // it does get destroyed, then the APZC will get destroyed along with it by
+ // definition, but we want to keep that APZC around in the new tree.
+ // We leave non-primary-holder nodes in the destroy list because we don't
+ // care about those nodes getting destroyed.
+ for (size_t i = 0; i < aState.mNodesToDestroy.Length(); i++) {
+ RefPtr<HitTestingTreeNode> n = aState.mNodesToDestroy[i];
+ if (n->IsPrimaryHolder() && n->GetApzc() && n->GetApzc()->Matches(guid)) {
+ node = n;
+ if (apzc != nullptr) {
+ // If there is an APZC already then it should match the one from the
+ // old primary-holder node
+ MOZ_ASSERT(apzc == node->GetApzc());
+ }
+ apzc = node->GetApzc();
+ break;
+ }
+ }
+
+ // The APZC we get off the layer may have been destroyed previously if the
+ // layer was inactive or omitted from the layer tree for whatever reason
+ // from a layers update. If it later comes back it will have a reference to
+ // a destroyed APZC and so we need to throw that out and make a new one.
+ bool newApzc = (apzc == nullptr || apzc->IsDestroyed());
+ if (newApzc) {
+ MOZ_ASSERT(aState.mLayerTreeState);
+ apzc = NewAPZCInstance(aLayersId, state->mController);
+ apzc->SetCompositorController(aState.mLayerTreeState->GetCompositorController());
+ if (state->mCrossProcessParent) {
+ apzc->SetMetricsSharingController(state->CrossProcessSharingController());
+ } else {
+ apzc->SetMetricsSharingController(aState.mLayerTreeState->InProcessSharingController());
+ }
+ MOZ_ASSERT(node == nullptr);
+ node = new HitTestingTreeNode(apzc, true, aLayersId);
+ } else {
+ // If we are re-using a node for this layer clear the tree pointers
+ // so that it doesn't continue pointing to nodes that might no longer
+ // be in the tree. These pointers will get reset properly as we continue
+ // building the tree. Also remove it from the set of nodes that are going
+ // to be destroyed, because it's going to remain active.
+ aState.mNodesToDestroy.RemoveElement(node);
+ node->SetPrevSibling(nullptr);
+ node->SetLastChild(nullptr);
+ }
+
+ APZCTM_LOG("Using APZC %p for layer %p with identifiers %" PRId64 " %" PRId64 "\n", apzc, aLayer.GetLayer(), aLayersId, aMetrics.GetScrollId());
+
+ apzc->NotifyLayersUpdated(aLayer.Metadata(), aState.mIsFirstPaint,
+ aLayersId == aState.mOriginatingLayersId);
+
+ // Since this is the first time we are encountering an APZC with this guid,
+ // the node holding it must be the primary holder. It may be newly-created
+ // or not, depending on whether it went through the newApzc branch above.
+ MOZ_ASSERT(node->IsPrimaryHolder() && node->GetApzc() && node->GetApzc()->Matches(guid));
+
+ ParentLayerIntRegion clipRegion = ComputeClipRegion(state->mController, aLayer);
+ node->SetHitTestData(
+ GetEventRegions(aLayer),
+ aLayer.GetTransformTyped(),
+ Some(clipRegion),
+ GetEventRegionsOverride(aParent, aLayer));
+ apzc->SetAncestorTransform(aAncestorTransform);
+
+ PrintAPZCInfo(aLayer, apzc);
+
+ // Bind the APZC instance into the tree of APZCs
+ AttachNodeToTree(node, aParent, aNextSibling);
+
+ // For testing, log the parent scroll id of every APZC that has a
+ // parent. This allows test code to reconstruct the APZC tree.
+ // Note that we currently only do this for APZCs in the layer tree
+ // that originated the update, because the only identifying information
+ // we are logging about APZCs is the scroll id, and otherwise we could
+ // confuse APZCs from different layer trees with the same scroll id.
+ if (aLayersId == aState.mOriginatingLayersId) {
+ if (apzc->HasNoParentWithSameLayersId()) {
+ aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
+ "hasNoParentWithSameLayersId", true);
+ } else {
+ MOZ_ASSERT(apzc->GetParent());
+ aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
+ "parentScrollId", apzc->GetParent()->GetGuid().mScrollId);
+ }
+ if (aMetrics.IsRootContent()) {
+ aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
+ "isRootContent", true);
+ }
+ // Note that the async scroll offset is in ParentLayer pixels
+ aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(), "asyncScrollOffset",
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::NORMAL));
+ }
+
+ if (newApzc) {
+ auto it = mZoomConstraints.find(guid);
+ if (it != mZoomConstraints.end()) {
+ // We have a zoomconstraints for this guid, apply it.
+ apzc->UpdateZoomConstraints(it->second);
+ } else if (!apzc->HasNoParentWithSameLayersId()) {
+ // This is a sub-APZC, so inherit the zoom constraints from its parent.
+ // This ensures that if e.g. user-scalable=no was specified, none of the
+ // APZCs for that subtree allow double-tap to zoom.
+ apzc->UpdateZoomConstraints(apzc->GetParent()->GetZoomConstraints());
+ }
+ // Otherwise, this is the root of a layers id, but we didn't have a saved
+ // zoom constraints. Leave it empty for now.
+ }
+
+ // Add a guid -> APZC mapping for the newly created APZC.
+ insertResult.first->second = apzc;
+ } else {
+ // We already built an APZC earlier in this tree walk, but we have another layer
+ // now that will also be using that APZC. The hit-test region on the APZC needs
+ // to be updated to deal with the new layer's hit region.
+
+ node = RecycleOrCreateNode(aState, apzc, aLayersId);
+ AttachNodeToTree(node, aParent, aNextSibling);
+
+ // Even though different layers associated with a given APZC may be at
+ // different levels in the layer tree (e.g. one being an uncle of another),
+ // we require from Layout that the CSS transforms up to their common
+ // ancestor be roughly the same. There are cases in which the transforms
+ // are not exactly the same, for example if the parent is container layer
+ // for an opacity, and this container layer has a resolution-induced scale
+ // as its base transform and a prescale that is supposed to undo that scale.
+ // Due to floating point inaccuracies those transforms can end up not quite
+ // canceling each other. That's why we're using a fuzzy comparison here
+ // instead of an exact one.
+ MOZ_ASSERT(aAncestorTransform.FuzzyEqualsMultiplicative(apzc->GetAncestorTransform()));
+
+ ParentLayerIntRegion clipRegion = ComputeClipRegion(state->mController, aLayer);
+ node->SetHitTestData(
+ GetEventRegions(aLayer),
+ aLayer.GetTransformTyped(),
+ Some(clipRegion),
+ GetEventRegionsOverride(aParent, aLayer));
+ }
+
+ node->SetScrollbarData(aLayer.GetScrollbarTargetContainerId(),
+ aLayer.GetScrollbarDirection(),
+ aLayer.GetScrollbarSize(),
+ aLayer.IsScrollbarContainer());
+ node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId());
+ return node;
+}
+
+template<typename PanGestureOrScrollWheelInput>
+static bool
+WillHandleInput(const PanGestureOrScrollWheelInput& aPanInput)
+{
+ if (!NS_IsMainThread()) {
+ return true;
+ }
+
+ WidgetWheelEvent wheelEvent = aPanInput.ToWidgetWheelEvent(nullptr);
+ return WillHandleWheelEvent(&wheelEvent);
+}
+
+void
+APZCTreeManager::FlushApzRepaints(uint64_t aLayersId)
+{
+ // Previously, paints were throttled and therefore this method was used to
+ // ensure any pending paints were flushed. Now, paints are flushed
+ // immediately, so it is safe to simply send a notification now.
+ APZCTM_LOG("Flushing repaints for layers id %" PRIu64, aLayersId);
+ const CompositorBridgeParent::LayerTreeState* state =
+ CompositorBridgeParent::GetIndirectShadowTree(aLayersId);
+ MOZ_ASSERT(state && state->mController);
+ state->mController->DispatchToRepaintThread(NewRunnableMethod(
+ state->mController, &GeckoContentController::NotifyFlushComplete));
+}
+
+nsEventStatus
+APZCTreeManager::ReceiveInputEvent(InputData& aEvent,
+ ScrollableLayerGuid* aOutTargetGuid,
+ uint64_t* aOutInputBlockId)
+{
+ APZThreadUtils::AssertOnControllerThread();
+
+ // Initialize aOutInputBlockId to a sane value, and then later we overwrite
+ // it if the input event goes into a block.
+ if (aOutInputBlockId) {
+ *aOutInputBlockId = InputBlockState::NO_BLOCK_ID;
+ }
+ nsEventStatus result = nsEventStatus_eIgnore;
+ HitTestResult hitResult = HitNothing;
+ switch (aEvent.mInputType) {
+ case MULTITOUCH_INPUT: {
+ MultiTouchInput& touchInput = aEvent.AsMultiTouchInput();
+ result = ProcessTouchInput(touchInput, aOutTargetGuid, aOutInputBlockId);
+ break;
+ } case MOUSE_INPUT: {
+ MouseInput& mouseInput = aEvent.AsMouseInput();
+ mouseInput.mHandledByAPZ = true;
+
+ if (DragTracker::StartsDrag(mouseInput)) {
+ // If this is the start of a drag we need to unambiguously know if it's
+ // going to land on a scrollbar or not. We can't apply an untransform
+ // here without knowing that, so we need to ensure the untransform is
+ // a no-op.
+ FlushRepaintsToClearScreenToGeckoTransform();
+ }
+
+ bool hitScrollbar = false;
+ RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(mouseInput.mOrigin,
+ &hitResult, &hitScrollbar);
+
+ // When the mouse is outside the window we still want to handle dragging
+ // but we won't find an APZC. Fallback to root APZC then.
+ { // scope lock
+ MutexAutoLock lock(mTreeLock);
+ if (!apzc && mRootNode) {
+ apzc = mRootNode->GetApzc();
+ }
+ }
+
+ if (apzc) {
+ bool targetConfirmed = (hitResult != HitNothing && hitResult != HitDispatchToContentRegion);
+ if (gfxPrefs::APZDragEnabled() && hitScrollbar) {
+ // If scrollbar dragging is enabled and we hit a scrollbar, wait
+ // for the main-thread confirmation because it contains drag metrics
+ // that we need.
+ targetConfirmed = false;
+ }
+ result = mInputQueue->ReceiveInputEvent(
+ apzc, targetConfirmed,
+ mouseInput, aOutInputBlockId);
+
+ if (result == nsEventStatus_eConsumeDoDefault) {
+ // This input event is part of a drag block, so whether or not it is
+ // directed at a scrollbar depends on whether the drag block started
+ // on a scrollbar.
+ hitScrollbar = mInputQueue->IsDragOnScrollbar(hitScrollbar);
+ }
+
+ // Update the out-parameters so they are what the caller expects.
+ apzc->GetGuid(aOutTargetGuid);
+
+ if (!hitScrollbar) {
+ // The input was not targeted at a scrollbar, so we untransform it
+ // like we do for other content. Scrollbars are "special" because they
+ // have special handling in AsyncCompositionManager when resolution is
+ // applied. TODO: we should find a better way to deal with this.
+ ScreenToParentLayerMatrix4x4 transformToApzc = GetScreenToApzcTransform(apzc);
+ ParentLayerToScreenMatrix4x4 transformToGecko = GetApzcToGeckoTransform(apzc);
+ ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko;
+ Maybe<ScreenPoint> untransformedRefPoint = UntransformBy(
+ outTransform, mouseInput.mOrigin);
+ if (untransformedRefPoint) {
+ mouseInput.mOrigin = *untransformedRefPoint;
+ }
+ } else {
+ // Likewise, if the input was targeted at a scrollbar, we don't want to
+ // apply the callback transform in the main thread, so we remove the
+ // scrollid from the guid. We need to keep the layersId intact so
+ // that the response from the child process doesn't get discarded.
+ aOutTargetGuid->mScrollId = FrameMetrics::NULL_SCROLL_ID;
+ }
+ }
+ break;
+ } case SCROLLWHEEL_INPUT: {
+ FlushRepaintsToClearScreenToGeckoTransform();
+
+ ScrollWheelInput& wheelInput = aEvent.AsScrollWheelInput();
+
+ wheelInput.mHandledByAPZ = WillHandleInput(wheelInput);
+ if (!wheelInput.mHandledByAPZ) {
+ return result;
+ }
+
+ RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(wheelInput.mOrigin,
+ &hitResult);
+ if (apzc) {
+ MOZ_ASSERT(hitResult != HitNothing);
+
+ // For wheel events, the call to ReceiveInputEvent below may result in
+ // scrolling, which changes the async transform. However, the event we
+ // want to pass to gecko should be the pre-scroll event coordinates,
+ // transformed into the gecko space. (pre-scroll because the mouse
+ // cursor is stationary during wheel scrolling, unlike touchmove
+ // events). Since we just flushed the pending repaints the transform to
+ // gecko space should only consist of overscroll-cancelling transforms.
+ ScreenToScreenMatrix4x4 transformToGecko = GetScreenToApzcTransform(apzc)
+ * GetApzcToGeckoTransform(apzc);
+ Maybe<ScreenPoint> untransformedOrigin = UntransformBy(
+ transformToGecko, wheelInput.mOrigin);
+
+ if (!untransformedOrigin) {
+ return result;
+ }
+
+ result = mInputQueue->ReceiveInputEvent(
+ apzc,
+ /* aTargetConfirmed = */ hitResult != HitDispatchToContentRegion,
+ wheelInput, aOutInputBlockId);
+
+ // Update the out-parameters so they are what the caller expects.
+ apzc->GetGuid(aOutTargetGuid);
+ wheelInput.mOrigin = *untransformedOrigin;
+ }
+ break;
+ } case PANGESTURE_INPUT: {
+ FlushRepaintsToClearScreenToGeckoTransform();
+
+ PanGestureInput& panInput = aEvent.AsPanGestureInput();
+ panInput.mHandledByAPZ = WillHandleInput(panInput);
+ if (!panInput.mHandledByAPZ) {
+ return result;
+ }
+
+ // If/when we enable support for pan inputs off-main-thread, we'll need
+ // to duplicate this EventStateManager code or something. See the other
+ // call to GetUserPrefsForWheelEvent in this file for why these fields
+ // are stored separately.
+ MOZ_ASSERT(NS_IsMainThread());
+ WidgetWheelEvent wheelEvent = panInput.ToWidgetWheelEvent(nullptr);
+ EventStateManager::GetUserPrefsForWheelEvent(&wheelEvent,
+ &panInput.mUserDeltaMultiplierX,
+ &panInput.mUserDeltaMultiplierY);
+
+ RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(panInput.mPanStartPoint,
+ &hitResult);
+ if (apzc) {
+ MOZ_ASSERT(hitResult != HitNothing);
+
+ // For pan gesture events, the call to ReceiveInputEvent below may result in
+ // scrolling, which changes the async transform. However, the event we
+ // want to pass to gecko should be the pre-scroll event coordinates,
+ // transformed into the gecko space. (pre-scroll because the mouse
+ // cursor is stationary during pan gesture scrolling, unlike touchmove
+ // events). Since we just flushed the pending repaints the transform to
+ // gecko space should only consist of overscroll-cancelling transforms.
+ ScreenToScreenMatrix4x4 transformToGecko = GetScreenToApzcTransform(apzc)
+ * GetApzcToGeckoTransform(apzc);
+ Maybe<ScreenPoint> untransformedStartPoint = UntransformBy(
+ transformToGecko, panInput.mPanStartPoint);
+ Maybe<ScreenPoint> untransformedDisplacement = UntransformVector(
+ transformToGecko, panInput.mPanDisplacement, panInput.mPanStartPoint);
+
+ if (!untransformedStartPoint || !untransformedDisplacement) {
+ return result;
+ }
+
+ result = mInputQueue->ReceiveInputEvent(
+ apzc,
+ /* aTargetConfirmed = */ hitResult != HitDispatchToContentRegion,
+ panInput, aOutInputBlockId);
+
+ // Update the out-parameters so they are what the caller expects.
+ apzc->GetGuid(aOutTargetGuid);
+ panInput.mPanStartPoint = *untransformedStartPoint;
+ panInput.mPanDisplacement = *untransformedDisplacement;
+ }
+ break;
+ } case PINCHGESTURE_INPUT: { // note: no one currently sends these
+ PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
+ RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(pinchInput.mFocusPoint,
+ &hitResult);
+ if (apzc) {
+ MOZ_ASSERT(hitResult != HitNothing);
+
+ ScreenToScreenMatrix4x4 outTransform = GetScreenToApzcTransform(apzc)
+ * GetApzcToGeckoTransform(apzc);
+ Maybe<ScreenPoint> untransformedFocusPoint = UntransformBy(
+ outTransform, pinchInput.mFocusPoint);
+
+ if (!untransformedFocusPoint) {
+ return result;
+ }
+
+ result = mInputQueue->ReceiveInputEvent(
+ apzc,
+ /* aTargetConfirmed = */ hitResult != HitDispatchToContentRegion,
+ pinchInput, aOutInputBlockId);
+
+ // Update the out-parameters so they are what the caller expects.
+ apzc->GetGuid(aOutTargetGuid);
+ pinchInput.mFocusPoint = *untransformedFocusPoint;
+ }
+ break;
+ } case TAPGESTURE_INPUT: { // note: no one currently sends these
+ TapGestureInput& tapInput = aEvent.AsTapGestureInput();
+ RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(tapInput.mPoint,
+ &hitResult);
+ if (apzc) {
+ MOZ_ASSERT(hitResult != HitNothing);
+
+ ScreenToScreenMatrix4x4 outTransform = GetScreenToApzcTransform(apzc)
+ * GetApzcToGeckoTransform(apzc);
+ Maybe<ScreenIntPoint> untransformedPoint =
+ UntransformBy(outTransform, tapInput.mPoint);
+
+ if (!untransformedPoint) {
+ return result;
+ }
+
+ result = mInputQueue->ReceiveInputEvent(
+ apzc,
+ /* aTargetConfirmed = */ hitResult != HitDispatchToContentRegion,
+ tapInput, aOutInputBlockId);
+
+ // Update the out-parameters so they are what the caller expects.
+ apzc->GetGuid(aOutTargetGuid);
+ tapInput.mPoint = *untransformedPoint;
+ }
+ break;
+ } case SENTINEL_INPUT: {
+ MOZ_ASSERT_UNREACHABLE("Invalid InputType.");
+ break;
+ }
+ }
+ return result;
+}
+
+static TouchBehaviorFlags
+ConvertToTouchBehavior(HitTestResult result)
+{
+ switch (result) {
+ case HitNothing:
+ return AllowedTouchBehavior::NONE;
+ case HitLayer:
+ return AllowedTouchBehavior::VERTICAL_PAN
+ | AllowedTouchBehavior::HORIZONTAL_PAN
+ | AllowedTouchBehavior::PINCH_ZOOM
+ | AllowedTouchBehavior::DOUBLE_TAP_ZOOM;
+ case HitLayerTouchActionNone:
+ return AllowedTouchBehavior::NONE;
+ case HitLayerTouchActionPanX:
+ return AllowedTouchBehavior::HORIZONTAL_PAN;
+ case HitLayerTouchActionPanY:
+ return AllowedTouchBehavior::VERTICAL_PAN;
+ case HitLayerTouchActionPanXY:
+ return AllowedTouchBehavior::HORIZONTAL_PAN
+ | AllowedTouchBehavior::VERTICAL_PAN;
+ case HitDispatchToContentRegion:
+ default:
+ return AllowedTouchBehavior::UNKNOWN;
+ }
+}
+
+already_AddRefed<AsyncPanZoomController>
+APZCTreeManager::GetTouchInputBlockAPZC(const MultiTouchInput& aEvent,
+ nsTArray<TouchBehaviorFlags>* aOutTouchBehaviors,
+ HitTestResult* aOutHitResult)
+{
+ RefPtr<AsyncPanZoomController> apzc;
+ if (aEvent.mTouches.Length() == 0) {
+ return apzc.forget();
+ }
+
+ FlushRepaintsToClearScreenToGeckoTransform();
+
+ HitTestResult hitResult;
+ apzc = GetTargetAPZC(aEvent.mTouches[0].mScreenPoint, &hitResult);
+ if (aOutTouchBehaviors) {
+ aOutTouchBehaviors->AppendElement(ConvertToTouchBehavior(hitResult));
+ }
+ for (size_t i = 1; i < aEvent.mTouches.Length(); i++) {
+ RefPtr<AsyncPanZoomController> apzc2 = GetTargetAPZC(aEvent.mTouches[i].mScreenPoint, &hitResult);
+ if (aOutTouchBehaviors) {
+ aOutTouchBehaviors->AppendElement(ConvertToTouchBehavior(hitResult));
+ }
+ apzc = GetMultitouchTarget(apzc, apzc2);
+ APZCTM_LOG("Using APZC %p as the root APZC for multi-touch\n", apzc.get());
+ }
+
+ if (aOutHitResult) {
+ // XXX we should probably be combining the hit results from the different
+ // touch points somehow, instead of just using the last one.
+ *aOutHitResult = hitResult;
+ }
+ return apzc.forget();
+}
+
+nsEventStatus
+APZCTreeManager::ProcessTouchInput(MultiTouchInput& aInput,
+ ScrollableLayerGuid* aOutTargetGuid,
+ uint64_t* aOutInputBlockId)
+{
+ aInput.mHandledByAPZ = true;
+ nsTArray<TouchBehaviorFlags> touchBehaviors;
+ if (aInput.mType == MultiTouchInput::MULTITOUCH_START) {
+ // If we are panned into overscroll and a second finger goes down,
+ // ignore that second touch point completely. The touch-start for it is
+ // dropped completely; subsequent touch events until the touch-end for it
+ // will have this touch point filtered out.
+ // (By contrast, if we're in overscroll but not panning, such as after
+ // putting two fingers down during an overscroll animation, we process the
+ // second touch and proceed to pinch.)
+ if (mApzcForInputBlock &&
+ mApzcForInputBlock->IsInPanningState() &&
+ BuildOverscrollHandoffChain(mApzcForInputBlock)->HasOverscrolledApzc()) {
+ if (mRetainedTouchIdentifier == -1) {
+ mRetainedTouchIdentifier = mApzcForInputBlock->GetLastTouchIdentifier();
+ }
+ return nsEventStatus_eConsumeNoDefault;
+ }
+
+ mHitResultForInputBlock = HitNothing;
+ mApzcForInputBlock = GetTouchInputBlockAPZC(aInput, &touchBehaviors, &mHitResultForInputBlock);
+ MOZ_ASSERT(touchBehaviors.Length() == aInput.mTouches.Length());
+ for (size_t i = 0; i < touchBehaviors.Length(); i++) {
+ APZCTM_LOG("Touch point has allowed behaviours 0x%02x\n", touchBehaviors[i]);
+ if (touchBehaviors[i] == AllowedTouchBehavior::UNKNOWN) {
+ // If there's any unknown items in the list, throw it out and we'll
+ // wait for the main thread to send us a notification.
+ touchBehaviors.Clear();
+ break;
+ }
+ }
+ } else if (mApzcForInputBlock) {
+ APZCTM_LOG("Re-using APZC %p as continuation of event block\n", mApzcForInputBlock.get());
+ }
+
+ // If we receive a touch-cancel, it means all touches are finished, so we
+ // can stop ignoring any that we were ignoring.
+ if (aInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
+ mRetainedTouchIdentifier = -1;
+ }
+
+ // If we are currently ignoring any touch points, filter them out from the
+ // set of touch points included in this event. Note that we modify aInput
+ // itself, so that the touch points are also filtered out when the caller
+ // passes the event on to content.
+ if (mRetainedTouchIdentifier != -1) {
+ for (size_t j = 0; j < aInput.mTouches.Length(); ++j) {
+ if (aInput.mTouches[j].mIdentifier != mRetainedTouchIdentifier) {
+ aInput.mTouches.RemoveElementAt(j);
+ if (!touchBehaviors.IsEmpty()) {
+ MOZ_ASSERT(touchBehaviors.Length() > j);
+ touchBehaviors.RemoveElementAt(j);
+ }
+ --j;
+ }
+ }
+ if (aInput.mTouches.IsEmpty()) {
+ return nsEventStatus_eConsumeNoDefault;
+ }
+ }
+
+ nsEventStatus result = nsEventStatus_eIgnore;
+ if (mApzcForInputBlock) {
+ MOZ_ASSERT(mHitResultForInputBlock != HitNothing);
+
+ mApzcForInputBlock->GetGuid(aOutTargetGuid);
+ uint64_t inputBlockId = 0;
+ result = mInputQueue->ReceiveInputEvent(mApzcForInputBlock,
+ /* aTargetConfirmed = */ mHitResultForInputBlock != HitDispatchToContentRegion,
+ aInput, &inputBlockId);
+ if (aOutInputBlockId) {
+ *aOutInputBlockId = inputBlockId;
+ }
+ if (!touchBehaviors.IsEmpty()) {
+ mInputQueue->SetAllowedTouchBehavior(inputBlockId, touchBehaviors);
+ }
+
+ // For computing the event to pass back to Gecko, use up-to-date transforms
+ // (i.e. not anything cached in an input block).
+ // This ensures that transformToApzc and transformToGecko are in sync.
+ ScreenToParentLayerMatrix4x4 transformToApzc = GetScreenToApzcTransform(mApzcForInputBlock);
+ ParentLayerToScreenMatrix4x4 transformToGecko = GetApzcToGeckoTransform(mApzcForInputBlock);
+ ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko;
+
+ for (size_t i = 0; i < aInput.mTouches.Length(); i++) {
+ SingleTouchData& touchData = aInput.mTouches[i];
+ Maybe<ScreenIntPoint> untransformedScreenPoint = UntransformBy(
+ outTransform, touchData.mScreenPoint);
+ if (!untransformedScreenPoint) {
+ return nsEventStatus_eIgnore;
+ }
+ touchData.mScreenPoint = *untransformedScreenPoint;
+ }
+ }
+
+ mTouchCounter.Update(aInput);
+
+ // If it's the end of the touch sequence then clear out variables so we
+ // don't keep dangling references and leak things.
+ if (mTouchCounter.GetActiveTouchCount() == 0) {
+ mApzcForInputBlock = nullptr;
+ mHitResultForInputBlock = HitNothing;
+ mRetainedTouchIdentifier = -1;
+ }
+
+ return result;
+}
+
+void
+APZCTreeManager::UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint,
+ EventMessage aEventMessage)
+{
+ WheelBlockState* txn = mInputQueue->GetActiveWheelTransaction();
+ if (!txn) {
+ return;
+ }
+
+ // If the transaction has simply timed out, we don't need to do anything
+ // else.
+ if (txn->MaybeTimeout(TimeStamp::Now())) {
+ return;
+ }
+
+ switch (aEventMessage) {
+ case eMouseMove:
+ case eDragOver: {
+
+ ScreenIntPoint point =
+ ViewAs<ScreenPixel>(aRefPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+
+ txn->OnMouseMove(point);
+
+ return;
+ }
+ case eKeyPress:
+ case eKeyUp:
+ case eKeyDown:
+ case eMouseUp:
+ case eMouseDown:
+ case eMouseDoubleClick:
+ case eMouseClick:
+ case eContextMenu:
+ case eDrop:
+ txn->EndTransaction();
+ return;
+ default:
+ break;
+ }
+}
+
+void
+APZCTreeManager::TransformEventRefPoint(LayoutDeviceIntPoint* aRefPoint,
+ ScrollableLayerGuid* aOutTargetGuid)
+{
+ // Transform the aRefPoint.
+ // If the event hits an overscrolled APZC, instruct the caller to ignore it.
+ HitTestResult hitResult = HitNothing;
+ PixelCastJustification LDIsScreen = PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent;
+ ScreenIntPoint refPointAsScreen =
+ ViewAs<ScreenPixel>(*aRefPoint, LDIsScreen);
+ RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(refPointAsScreen, &hitResult);
+ if (apzc) {
+ MOZ_ASSERT(hitResult != HitNothing);
+ apzc->GetGuid(aOutTargetGuid);
+ ScreenToParentLayerMatrix4x4 transformToApzc = GetScreenToApzcTransform(apzc);
+ ParentLayerToScreenMatrix4x4 transformToGecko = GetApzcToGeckoTransform(apzc);
+ ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko;
+ Maybe<ScreenIntPoint> untransformedRefPoint =
+ UntransformBy(outTransform, refPointAsScreen);
+ if (untransformedRefPoint) {
+ *aRefPoint =
+ ViewAs<LayoutDevicePixel>(*untransformedRefPoint, LDIsScreen);
+ }
+ }
+}
+
+void
+APZCTreeManager::ProcessTouchVelocity(uint32_t aTimestampMs, float aSpeedY)
+{
+ if (mApzcForInputBlock) {
+ mApzcForInputBlock->HandleTouchVelocity(aTimestampMs, aSpeedY);
+ }
+}
+
+void
+APZCTreeManager::ZoomToRect(const ScrollableLayerGuid& aGuid,
+ const CSSRect& aRect,
+ const uint32_t aFlags)
+{
+ RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
+ if (apzc) {
+ apzc->ZoomToRect(aRect, aFlags);
+ }
+}
+
+void
+APZCTreeManager::ContentReceivedInputBlock(uint64_t aInputBlockId, bool aPreventDefault)
+{
+ APZThreadUtils::AssertOnControllerThread();
+
+ mInputQueue->ContentReceivedInputBlock(aInputBlockId, aPreventDefault);
+}
+
+void
+APZCTreeManager::SetTargetAPZC(uint64_t aInputBlockId,
+ const nsTArray<ScrollableLayerGuid>& aTargets)
+{
+ APZThreadUtils::AssertOnControllerThread();
+
+ RefPtr<AsyncPanZoomController> target = nullptr;
+ if (aTargets.Length() > 0) {
+ target = GetTargetAPZC(aTargets[0]);
+ }
+ for (size_t i = 1; i < aTargets.Length(); i++) {
+ RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aTargets[i]);
+ target = GetMultitouchTarget(target, apzc);
+ }
+ mInputQueue->SetConfirmedTargetApzc(aInputBlockId, target);
+}
+
+void
+APZCTreeManager::SetTargetAPZC(uint64_t aInputBlockId, const ScrollableLayerGuid& aTarget)
+{
+ APZThreadUtils::AssertOnControllerThread();
+
+ RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aTarget);
+ mInputQueue->SetConfirmedTargetApzc(aInputBlockId, apzc);
+}
+
+void
+APZCTreeManager::UpdateZoomConstraints(const ScrollableLayerGuid& aGuid,
+ const Maybe<ZoomConstraints>& aConstraints)
+{
+ MutexAutoLock lock(mTreeLock);
+ RefPtr<HitTestingTreeNode> node = GetTargetNode(aGuid, nullptr);
+ MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC
+
+ // Propagate the zoom constraints down to the subtree, stopping at APZCs
+ // which have their own zoom constraints or are in a different layers id.
+ if (aConstraints) {
+ APZCTM_LOG("Recording constraints %s for guid %s\n",
+ Stringify(aConstraints.value()).c_str(), Stringify(aGuid).c_str());
+ mZoomConstraints[aGuid] = aConstraints.ref();
+ } else {
+ APZCTM_LOG("Removing constraints for guid %s\n", Stringify(aGuid).c_str());
+ mZoomConstraints.erase(aGuid);
+ }
+ if (node && aConstraints) {
+ ForEachNode<ReverseIterator>(node.get(),
+ [&aConstraints, &node, this](HitTestingTreeNode* aNode)
+ {
+ if (aNode != node) {
+ if (AsyncPanZoomController* childApzc = aNode->GetApzc()) {
+ // We can have subtrees with their own zoom constraints or separate layers
+ // id - leave these alone.
+ if (childApzc->HasNoParentWithSameLayersId() ||
+ this->mZoomConstraints.find(childApzc->GetGuid()) != this->mZoomConstraints.end()) {
+ return TraversalFlag::Skip;
+ }
+ }
+ }
+ if (aNode->IsPrimaryHolder()) {
+ MOZ_ASSERT(aNode->GetApzc());
+ aNode->GetApzc()->UpdateZoomConstraints(aConstraints.ref());
+ }
+ return TraversalFlag::Continue;
+ });
+ }
+}
+
+void
+APZCTreeManager::FlushRepaintsToClearScreenToGeckoTransform()
+{
+ // As the name implies, we flush repaint requests for the entire APZ tree in
+ // order to clear the screen-to-gecko transform (aka the "untransform" applied
+ // to incoming input events before they can be passed on to Gecko).
+ //
+ // The primary reason we do this is to avoid the problem where input events,
+ // after being untransformed, end up hit-testing differently in Gecko. This
+ // might happen in cases where the input event lands on content that is async-
+ // scrolled into view, but Gecko still thinks it is out of view given the
+ // visible area of a scrollframe.
+ //
+ // Another reason we want to clear the untransform is that if our APZ hit-test
+ // hits a dispatch-to-content region then that's an ambiguous result and we
+ // need to ask Gecko what actually got hit. In order to do this we need to
+ // untransform the input event into Gecko space - but to do that we need to
+ // know which APZC got hit! This leads to a circular dependency; the only way
+ // to get out of it is to make sure that the untransform for all the possible
+ // matched APZCs is the same. It is simplest to ensure that by flushing the
+ // pending repaint requests, which makes all of the untransforms empty (and
+ // therefore equal).
+ MutexAutoLock lock(mTreeLock);
+ mTreeLock.AssertCurrentThreadOwns();
+
+ ForEachNode<ReverseIterator>(mRootNode.get(),
+ [](HitTestingTreeNode* aNode)
+ {
+ if (aNode->IsPrimaryHolder()) {
+ MOZ_ASSERT(aNode->GetApzc());
+ aNode->GetApzc()->FlushRepaintForNewInputBlock();
+ }
+ });
+}
+
+void
+APZCTreeManager::CancelAnimation(const ScrollableLayerGuid &aGuid)
+{
+ RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
+ if (apzc) {
+ apzc->CancelAnimation();
+ }
+}
+
+void
+APZCTreeManager::AdjustScrollForSurfaceShift(const ScreenPoint& aShift)
+{
+ MutexAutoLock lock(mTreeLock);
+ RefPtr<AsyncPanZoomController> apzc = FindRootContentOrRootApzc();
+ if (apzc) {
+ apzc->AdjustScrollForSurfaceShift(aShift);
+ }
+}
+
+void
+APZCTreeManager::ClearTree()
+{
+ // Ensure that no references to APZCs are alive in any lingering input
+ // blocks. This breaks cycles from InputBlockState::mTargetApzc back to
+ // the InputQueue.
+ APZThreadUtils::RunOnControllerThread(NewRunnableMethod(mInputQueue, &InputQueue::Clear));
+
+ MutexAutoLock lock(mTreeLock);
+
+ // Collect the nodes into a list, and then destroy each one.
+ // We can't destroy them as we collect them, because ForEachNode()
+ // does a pre-order traversal of the tree, and Destroy() nulls out
+ // the fields needed to reach the children of the node.
+ nsTArray<RefPtr<HitTestingTreeNode>> nodesToDestroy;
+ ForEachNode<ReverseIterator>(mRootNode.get(),
+ [&nodesToDestroy](HitTestingTreeNode* aNode)
+ {
+ nodesToDestroy.AppendElement(aNode);
+ });
+
+ for (size_t i = 0; i < nodesToDestroy.Length(); i++) {
+ nodesToDestroy[i]->Destroy();
+ }
+ mRootNode = nullptr;
+
+ RefPtr<APZCTreeManager> self(this);
+ NS_DispatchToMainThread(NS_NewRunnableFunction([self] {
+ self->mFlushObserver->Unregister();
+ self->mFlushObserver = nullptr;
+ }));
+}
+
+RefPtr<HitTestingTreeNode>
+APZCTreeManager::GetRootNode() const
+{
+ MutexAutoLock lock(mTreeLock);
+ return mRootNode;
+}
+
+/**
+ * Transform a displacement from the ParentLayer coordinates of a source APZC
+ * to the ParentLayer coordinates of a target APZC.
+ * @param aTreeManager the tree manager for the APZC tree containing |aSource|
+ * and |aTarget|
+ * @param aSource the source APZC
+ * @param aTarget the target APZC
+ * @param aStartPoint the start point of the displacement
+ * @param aEndPoint the end point of the displacement
+ * @return true on success, false if aStartPoint or aEndPoint cannot be transformed into target's coordinate space
+ */
+static bool
+TransformDisplacement(APZCTreeManager* aTreeManager,
+ AsyncPanZoomController* aSource,
+ AsyncPanZoomController* aTarget,
+ ParentLayerPoint& aStartPoint,
+ ParentLayerPoint& aEndPoint) {
+ if (aSource == aTarget) {
+ return true;
+ }
+
+ // Convert start and end points to Screen coordinates.
+ ParentLayerToScreenMatrix4x4 untransformToApzc = aTreeManager->GetScreenToApzcTransform(aSource).Inverse();
+ ScreenPoint screenStart = TransformBy(untransformToApzc, aStartPoint);
+ ScreenPoint screenEnd = TransformBy(untransformToApzc, aEndPoint);
+
+ // Convert start and end points to aTarget's ParentLayer coordinates.
+ ScreenToParentLayerMatrix4x4 transformToApzc = aTreeManager->GetScreenToApzcTransform(aTarget);
+ Maybe<ParentLayerPoint> startPoint = UntransformBy(transformToApzc, screenStart);
+ Maybe<ParentLayerPoint> endPoint = UntransformBy(transformToApzc, screenEnd);
+ if (!startPoint || !endPoint) {
+ return false;
+ }
+ aEndPoint = *endPoint;
+ aStartPoint = *startPoint;
+
+ return true;
+}
+
+void
+APZCTreeManager::DispatchScroll(AsyncPanZoomController* aPrev,
+ ParentLayerPoint& aStartPoint,
+ ParentLayerPoint& aEndPoint,
+ OverscrollHandoffState& aOverscrollHandoffState)
+{
+ const OverscrollHandoffChain& overscrollHandoffChain = aOverscrollHandoffState.mChain;
+ uint32_t overscrollHandoffChainIndex = aOverscrollHandoffState.mChainIndex;
+ RefPtr<AsyncPanZoomController> next;
+ // If we have reached the end of the overscroll handoff chain, there is
+ // nothing more to scroll, so we ignore the rest of the pan gesture.
+ if (overscrollHandoffChainIndex >= overscrollHandoffChain.Length()) {
+ // Nothing more to scroll - ignore the rest of the pan gesture.
+ return;
+ }
+
+ next = overscrollHandoffChain.GetApzcAtIndex(overscrollHandoffChainIndex);
+
+ if (next == nullptr || next->IsDestroyed()) {
+ return;
+ }
+
+ // Convert the start and end points from |aPrev|'s coordinate space to
+ // |next|'s coordinate space.
+ if (!TransformDisplacement(this, aPrev, next, aStartPoint, aEndPoint)) {
+ return;
+ }
+
+ // Scroll |next|. If this causes overscroll, it will call DispatchScroll()
+ // again with an incremented index.
+ if (!next->AttemptScroll(aStartPoint, aEndPoint, aOverscrollHandoffState)) {
+ // Transform |aStartPoint| and |aEndPoint| (which now represent the
+ // portion of the displacement that wasn't consumed by APZCs later
+ // in the handoff chain) back into |aPrev|'s coordinate space. This
+ // allows the caller (which is |aPrev|) to interpret the unconsumed
+ // displacement in its own coordinate space, and make use of it
+ // (e.g. by going into overscroll).
+ if (!TransformDisplacement(this, next, aPrev, aStartPoint, aEndPoint)) {
+ NS_WARNING("Failed to untransform scroll points during dispatch");
+ }
+ }
+}
+
+void
+APZCTreeManager::DispatchFling(AsyncPanZoomController* aPrev,
+ FlingHandoffState& aHandoffState)
+{
+ // If immediate handoff is disallowed, do not allow handoff beyond the
+ // single APZC that's scrolled by the input block that triggered this fling.
+ if (aHandoffState.mIsHandoff &&
+ !gfxPrefs::APZAllowImmediateHandoff() &&
+ aHandoffState.mScrolledApzc == aPrev) {
+ return;
+ }
+
+ const OverscrollHandoffChain* chain = aHandoffState.mChain;
+ RefPtr<AsyncPanZoomController> current;
+ uint32_t overscrollHandoffChainLength = chain->Length();
+ uint32_t startIndex;
+
+ // This will store any velocity left over after the entire handoff.
+ ParentLayerPoint finalResidualVelocity = aHandoffState.mVelocity;
+
+ // The fling's velocity needs to be transformed from the screen coordinates
+ // of |aPrev| to the screen coordinates of |next|. To transform a velocity
+ // correctly, we need to convert it to a displacement. For now, we do this
+ // by anchoring it to a start point of (0, 0).
+ // TODO: For this to be correct in the presence of 3D transforms, we should
+ // use the end point of the touch that started the fling as the start point
+ // rather than (0, 0).
+ ParentLayerPoint startPoint; // (0, 0)
+ ParentLayerPoint endPoint;
+
+ if (aHandoffState.mIsHandoff) {
+ startIndex = chain->IndexOf(aPrev) + 1;
+
+ // IndexOf will return aOverscrollHandoffChain->Length() if
+ // |aPrev| is not found.
+ if (startIndex >= overscrollHandoffChainLength) {
+ return;
+ }
+ } else {
+ startIndex = 0;
+ }
+
+ for (; startIndex < overscrollHandoffChainLength; startIndex++) {
+ current = chain->GetApzcAtIndex(startIndex);
+
+ // Make sure the apcz about to be handled can be handled
+ if (current == nullptr || current->IsDestroyed()) {
+ return;
+ }
+
+ endPoint = startPoint + aHandoffState.mVelocity;
+
+ // Only transform when current apcz can be transformed with previous
+ if (startIndex > 0) {
+ if (!TransformDisplacement(this,
+ chain->GetApzcAtIndex(startIndex - 1),
+ current,
+ startPoint,
+ endPoint)) {
+ return;
+ }
+ }
+
+ ParentLayerPoint transformedVelocity = endPoint - startPoint;
+ aHandoffState.mVelocity = transformedVelocity;
+
+ if (current->AttemptFling(aHandoffState)) {
+ // Coming out of AttemptFling(), the handoff state's velocity is the
+ // residual velocity after attempting to fling |current|.
+ ParentLayerPoint residualVelocity = aHandoffState.mVelocity;
+
+ // If there's no residual velocity, there's nothing more to hand off.
+ if (IsZero(residualVelocity)) {
+ finalResidualVelocity = ParentLayerPoint();
+ break;
+ }
+
+ // If there is residual velocity, subtract the proportion of used
+ // velocity from finalResidualVelocity and continue handoff along the
+ // chain.
+ if (!FuzzyEqualsAdditive(transformedVelocity.x,
+ residualVelocity.x, COORDINATE_EPSILON)) {
+ finalResidualVelocity.x *= (residualVelocity.x / transformedVelocity.x);
+ }
+ if (!FuzzyEqualsAdditive(transformedVelocity.y,
+ residualVelocity.y, COORDINATE_EPSILON)) {
+ finalResidualVelocity.y *= (residualVelocity.y / transformedVelocity.y);
+ }
+ }
+ }
+
+ // Set the handoff state's velocity to any residual velocity left over
+ // after the entire handoff process.
+ aHandoffState.mVelocity = finalResidualVelocity;
+}
+
+bool
+APZCTreeManager::HitTestAPZC(const ScreenIntPoint& aPoint)
+{
+ RefPtr<AsyncPanZoomController> target = GetTargetAPZC(aPoint, nullptr);
+ return target != nullptr;
+}
+
+already_AddRefed<AsyncPanZoomController>
+APZCTreeManager::GetTargetAPZC(const ScrollableLayerGuid& aGuid)
+{
+ MutexAutoLock lock(mTreeLock);
+ RefPtr<HitTestingTreeNode> node = GetTargetNode(aGuid, nullptr);
+ MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC
+ RefPtr<AsyncPanZoomController> apzc = node ? node->GetApzc() : nullptr;
+ return apzc.forget();
+}
+
+already_AddRefed<HitTestingTreeNode>
+APZCTreeManager::GetTargetNode(const ScrollableLayerGuid& aGuid,
+ GuidComparator aComparator)
+{
+ mTreeLock.AssertCurrentThreadOwns();
+ RefPtr<HitTestingTreeNode> target = DepthFirstSearchPostOrder<ReverseIterator>(mRootNode.get(),
+ [&aGuid, &aComparator](HitTestingTreeNode* node)
+ {
+ bool matches = false;
+ if (node->GetApzc()) {
+ if (aComparator) {
+ matches = aComparator(aGuid, node->GetApzc()->GetGuid());
+ } else {
+ matches = node->GetApzc()->Matches(aGuid);
+ }
+ }
+ return matches;
+ }
+ );
+ return target.forget();
+}
+
+already_AddRefed<AsyncPanZoomController>
+APZCTreeManager::GetTargetAPZC(const ScreenPoint& aPoint,
+ HitTestResult* aOutHitResult,
+ bool* aOutHitScrollbar)
+{
+ MutexAutoLock lock(mTreeLock);
+ HitTestResult hitResult = HitNothing;
+ ParentLayerPoint point = ViewAs<ParentLayerPixel>(aPoint,
+ PixelCastJustification::ScreenIsParentLayerForRoot);
+ RefPtr<AsyncPanZoomController> target = GetAPZCAtPoint(mRootNode, point,
+ &hitResult, aOutHitScrollbar);
+
+ if (aOutHitResult) {
+ *aOutHitResult = hitResult;
+ }
+ return target.forget();
+}
+
+static bool
+GuidComparatorIgnoringPresShell(const ScrollableLayerGuid& aOne, const ScrollableLayerGuid& aTwo)
+{
+ return aOne.mLayersId == aTwo.mLayersId
+ && aOne.mScrollId == aTwo.mScrollId;
+}
+
+RefPtr<const OverscrollHandoffChain>
+APZCTreeManager::BuildOverscrollHandoffChain(const RefPtr<AsyncPanZoomController>& aInitialTarget)
+{
+ // Scroll grabbing is a mechanism that allows content to specify that
+ // the initial target of a pan should be not the innermost scrollable
+ // frame at the touch point (which is what GetTargetAPZC finds), but
+ // something higher up in the tree.
+ // It's not sufficient to just find the initial target, however, as
+ // overscroll can be handed off to another APZC. Without scroll grabbing,
+ // handoff just occurs from child to parent. With scroll grabbing, the
+ // handoff order can be different, so we build a chain of APZCs in the
+ // order in which scroll will be handed off to them.
+
+ // Grab tree lock since we'll be walking the APZC tree.
+ MutexAutoLock lock(mTreeLock);
+
+ // Build the chain. If there is a scroll parent link, we use that. This is
+ // needed to deal with scroll info layers, because they participate in handoff
+ // but do not follow the expected layer tree structure. If there are no
+ // scroll parent links we just walk up the tree to find the scroll parent.
+ OverscrollHandoffChain* result = new OverscrollHandoffChain;
+ AsyncPanZoomController* apzc = aInitialTarget;
+ while (apzc != nullptr) {
+ result->Add(apzc);
+
+ if (apzc->GetScrollHandoffParentId() == FrameMetrics::NULL_SCROLL_ID) {
+ if (!apzc->IsRootForLayersId()) {
+ // This probably indicates a bug or missed case in layout code
+ NS_WARNING("Found a non-root APZ with no handoff parent");
+ }
+ apzc = apzc->GetParent();
+ continue;
+ }
+
+ // Guard against a possible infinite-loop condition. If we hit this, the
+ // layout code that generates the handoff parents did something wrong.
+ MOZ_ASSERT(apzc->GetScrollHandoffParentId() != apzc->GetGuid().mScrollId);
+
+ // Find the AsyncPanZoomController instance with a matching layersId and
+ // the scroll id that matches apzc->GetScrollHandoffParentId().
+ // As an optimization, we start by walking up the APZC tree from 'apzc'
+ // until we reach the top of the layer subtree for this layers id.
+ AsyncPanZoomController* scrollParent = nullptr;
+ AsyncPanZoomController* parent = apzc;
+ while (!parent->HasNoParentWithSameLayersId()) {
+ parent = parent->GetParent();
+ // While walking up to find the root of the subtree, if we encounter the
+ // handoff parent, we don't actually need to do the search so we can
+ // just abort here.
+ if (parent->GetGuid().mScrollId == apzc->GetScrollHandoffParentId()) {
+ scrollParent = parent;
+ break;
+ }
+ }
+ // If that heuristic didn't turn up the scroll parent, do a full tree search.
+ if (!scrollParent) {
+ ScrollableLayerGuid guid(parent->GetGuid().mLayersId, 0, apzc->GetScrollHandoffParentId());
+ RefPtr<HitTestingTreeNode> node = GetTargetNode(guid, &GuidComparatorIgnoringPresShell);
+ MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC
+ scrollParent = node ? node->GetApzc() : nullptr;
+ }
+ apzc = scrollParent;
+ }
+
+ // Now adjust the chain to account for scroll grabbing. Sorting is a bit
+ // of an overkill here, but scroll grabbing will likely be generalized
+ // to scroll priorities, so we might as well do it this way.
+ result->SortByScrollPriority();
+
+ // Print the overscroll chain for debugging.
+ for (uint32_t i = 0; i < result->Length(); ++i) {
+ APZCTM_LOG("OverscrollHandoffChain[%d] = %p\n", i, result->GetApzcAtIndex(i).get());
+ }
+
+ return result;
+}
+
+void
+APZCTreeManager::SetLongTapEnabled(bool aLongTapEnabled)
+{
+ APZThreadUtils::RunOnControllerThread(
+ NewRunnableFunction(GestureEventListener::SetLongTapEnabled, aLongTapEnabled));
+}
+
+RefPtr<HitTestingTreeNode>
+APZCTreeManager::FindScrollNode(const AsyncDragMetrics& aDragMetrics)
+{
+ MutexAutoLock lock(mTreeLock);
+
+ return DepthFirstSearch<ReverseIterator>(mRootNode.get(),
+ [&aDragMetrics](HitTestingTreeNode* aNode) {
+ return aNode->MatchesScrollDragMetrics(aDragMetrics);
+ });
+}
+
+AsyncPanZoomController*
+APZCTreeManager::GetTargetApzcForNode(HitTestingTreeNode* aNode)
+{
+ for (const HitTestingTreeNode* n = aNode;
+ n && n->GetLayersId() == aNode->GetLayersId();
+ n = n->GetParent()) {
+ if (n->GetApzc()) {
+ APZCTM_LOG("Found target %p using ancestor lookup\n", n->GetApzc());
+ return n->GetApzc();
+ }
+ if (n->GetFixedPosTarget() != FrameMetrics::NULL_SCROLL_ID) {
+ ScrollableLayerGuid guid(n->GetLayersId(), 0, n->GetFixedPosTarget());
+ RefPtr<HitTestingTreeNode> fpNode = GetTargetNode(guid, &GuidComparatorIgnoringPresShell);
+ APZCTM_LOG("Found target node %p using fixed-pos lookup on %" PRIu64 "\n", fpNode.get(), n->GetFixedPosTarget());
+ return fpNode ? fpNode->GetApzc() : nullptr;
+ }
+ }
+ return nullptr;
+}
+
+AsyncPanZoomController*
+APZCTreeManager::GetAPZCAtPoint(HitTestingTreeNode* aNode,
+ const ParentLayerPoint& aHitTestPoint,
+ HitTestResult* aOutHitResult,
+ bool* aOutHitScrollbar)
+{
+ mTreeLock.AssertCurrentThreadOwns();
+
+ // This walks the tree in depth-first, reverse order, so that it encounters
+ // APZCs front-to-back on the screen.
+ HitTestingTreeNode* resultNode;
+ HitTestingTreeNode* root = aNode;
+ std::stack<ParentLayerPoint> hitTestPoints;
+ hitTestPoints.push(aHitTestPoint);
+
+ ForEachNode<ReverseIterator>(root,
+ [&hitTestPoints](HitTestingTreeNode* aNode) {
+ if (aNode->IsOutsideClip(hitTestPoints.top())) {
+ // If the point being tested is outside the clip region for this node
+ // then we don't need to test against this node or any of its children.
+ // Just skip it and move on.
+ APZCTM_LOG("Point %f %f outside clip for node %p\n",
+ hitTestPoints.top().x, hitTestPoints.top().y, aNode);
+ return TraversalFlag::Skip;
+ }
+ // First check the subtree rooted at this node, because deeper nodes
+ // are more "in front".
+ Maybe<LayerPoint> hitTestPointForChildLayers = aNode->Untransform(hitTestPoints.top());
+ APZCTM_LOG("Transformed ParentLayer point %s to layer %s\n",
+ Stringify(hitTestPoints.top()).c_str(),
+ hitTestPointForChildLayers ? Stringify(hitTestPointForChildLayers.ref()).c_str() : "nil");
+ if (!hitTestPointForChildLayers) {
+ return TraversalFlag::Skip;
+ }
+ hitTestPoints.push(ViewAs<ParentLayerPixel>(hitTestPointForChildLayers.ref(),
+ PixelCastJustification::MovingDownToChildren));
+ return TraversalFlag::Continue;
+ },
+ [&resultNode, &hitTestPoints, &aOutHitResult](HitTestingTreeNode* aNode) {
+ hitTestPoints.pop();
+ HitTestResult hitResult = aNode->HitTest(hitTestPoints.top());
+ APZCTM_LOG("Testing ParentLayer point %s against node %p\n",
+ Stringify(hitTestPoints.top()).c_str(), aNode);
+ if (hitResult != HitTestResult::HitNothing) {
+ resultNode = aNode;
+ // If event regions are disabled, *aOutHitResult will be HitLayer
+ *aOutHitResult = hitResult;
+ return TraversalFlag::Abort;
+ }
+ return TraversalFlag::Continue;
+ }
+ );
+
+ if (*aOutHitResult != HitNothing) {
+ MOZ_ASSERT(resultNode);
+ if (aOutHitScrollbar) {
+ for (HitTestingTreeNode* n = resultNode; n; n = n->GetParent()) {
+ if (n->IsScrollbarNode()) {
+ *aOutHitScrollbar = true;
+ }
+ }
+ }
+
+ AsyncPanZoomController* result = GetTargetApzcForNode(resultNode);
+ if (!result) {
+ result = FindRootApzcForLayersId(resultNode->GetLayersId());
+ MOZ_ASSERT(result);
+ APZCTM_LOG("Found target %p using root lookup\n", result);
+ }
+ APZCTM_LOG("Successfully matched APZC %p via node %p (hit result %d)\n",
+ result, resultNode, *aOutHitResult);
+ return result;
+ }
+
+ return nullptr;
+}
+
+AsyncPanZoomController*
+APZCTreeManager::FindRootApzcForLayersId(uint64_t aLayersId) const
+{
+ mTreeLock.AssertCurrentThreadOwns();
+
+ HitTestingTreeNode* resultNode = BreadthFirstSearch<ReverseIterator>(mRootNode.get(),
+ [aLayersId](HitTestingTreeNode* aNode) {
+ AsyncPanZoomController* apzc = aNode->GetApzc();
+ return apzc
+ && apzc->GetLayersId() == aLayersId
+ && apzc->IsRootForLayersId();
+ });
+ return resultNode ? resultNode->GetApzc() : nullptr;
+}
+
+AsyncPanZoomController*
+APZCTreeManager::FindRootContentApzcForLayersId(uint64_t aLayersId) const
+{
+ mTreeLock.AssertCurrentThreadOwns();
+
+ HitTestingTreeNode* resultNode = BreadthFirstSearch<ReverseIterator>(mRootNode.get(),
+ [aLayersId](HitTestingTreeNode* aNode) {
+ AsyncPanZoomController* apzc = aNode->GetApzc();
+ return apzc
+ && apzc->GetLayersId() == aLayersId
+ && apzc->IsRootContent();
+ });
+ return resultNode ? resultNode->GetApzc() : nullptr;
+}
+
+AsyncPanZoomController*
+APZCTreeManager::FindRootContentOrRootApzc() const
+{
+ mTreeLock.AssertCurrentThreadOwns();
+
+ // Note: this is intended to find the same "root" that would be found
+ // by AsyncCompositionManager::ApplyAsyncContentTransformToTree inside
+ // the MOZ_WIDGET_ANDROID block. That is, it should find the RCD node if there
+ // is one, or the root APZC if there is not.
+ // Since BreadthFirstSearch is a pre-order search, we first do a search for
+ // the RCD, and then if we don't find one, we do a search for the root APZC.
+ HitTestingTreeNode* resultNode = BreadthFirstSearch<ReverseIterator>(mRootNode.get(),
+ [](HitTestingTreeNode* aNode) {
+ AsyncPanZoomController* apzc = aNode->GetApzc();
+ return apzc && apzc->IsRootContent();
+ });
+ if (resultNode) {
+ return resultNode->GetApzc();
+ }
+ resultNode = BreadthFirstSearch<ReverseIterator>(mRootNode.get(),
+ [](HitTestingTreeNode* aNode) {
+ AsyncPanZoomController* apzc = aNode->GetApzc();
+ return (apzc != nullptr);
+ });
+ return resultNode ? resultNode->GetApzc() : nullptr;
+}
+
+/* The methods GetScreenToApzcTransform() and GetApzcToGeckoTransform() return
+ some useful transformations that input events may need applied. This is best
+ illustrated with an example. Consider a chain of layers, L, M, N, O, P, Q, R. Layer L
+ is the layer that corresponds to the argument |aApzc|, and layer R is the root
+ of the layer tree. Layer M is the parent of L, N is the parent of M, and so on.
+ When layer L is displayed to the screen by the compositor, the set of transforms that
+ are applied to L are (in order from top to bottom):
+
+ L's CSS transform (hereafter referred to as transform matrix LC)
+ L's nontransient async transform (hereafter referred to as transform matrix LN)
+ L's transient async transform (hereafter referred to as transform matrix LT)
+ M's CSS transform (hereafter referred to as transform matrix MC)
+ M's nontransient async transform (hereafter referred to as transform matrix MN)
+ M's transient async transform (hereafter referred to as transform matrix MT)
+ ...
+ R's CSS transform (hereafter referred to as transform matrix RC)
+ R's nontransient async transform (hereafter referred to as transform matrix RN)
+ R's transient async transform (hereafter referred to as transform matrix RT)
+
+ Also, for any layer, the async transform is the combination of its transient and non-transient
+ parts. That is, for any layer L:
+ LA === LN * LT
+ LA.Inverse() === LT.Inverse() * LN.Inverse()
+
+ If we want user input to modify L's transient async transform, we have to first convert
+ user input from screen space to the coordinate space of L's transient async transform. Doing
+ this involves applying the following transforms (in order from top to bottom):
+ RT.Inverse()
+ RN.Inverse()
+ RC.Inverse()
+ ...
+ MT.Inverse()
+ MN.Inverse()
+ MC.Inverse()
+ This combined transformation is returned by GetScreenToApzcTransform().
+
+ Next, if we want user inputs sent to gecko for event-dispatching, we will need to strip
+ out all of the async transforms that are involved in this chain. This is because async
+ transforms are stored only in the compositor and gecko does not account for them when
+ doing display-list-based hit-testing for event dispatching.
+ Furthermore, because these input events are processed by Gecko in a FIFO queue that
+ includes other things (specifically paint requests), it is possible that by time the
+ input event reaches gecko, it will have painted something else. Therefore, we need to
+ apply another transform to the input events to account for the possible disparity between
+ what we know gecko last painted and the last paint request we sent to gecko. Let this
+ transform be represented by LD, MD, ... RD.
+ Therefore, given a user input in screen space, the following transforms need to be applied
+ (in order from top to bottom):
+ RT.Inverse()
+ RN.Inverse()
+ RC.Inverse()
+ ...
+ MT.Inverse()
+ MN.Inverse()
+ MC.Inverse()
+ LT.Inverse()
+ LN.Inverse()
+ LC.Inverse()
+ LC
+ LD
+ MC
+ MD
+ ...
+ RC
+ RD
+ This sequence can be simplified and refactored to the following:
+ GetScreenToApzcTransform()
+ LA.Inverse()
+ LD
+ MC
+ MD
+ ...
+ RC
+ RD
+ Since GetScreenToApzcTransform() can be obtained by calling that function, GetApzcToGeckoTransform()
+ returns the remaining transforms (LA.Inverse() * LD * ... * RD), so that the caller code can
+ combine it with GetScreenToApzcTransform() to get the final transform required in this case.
+
+ Note that for many of these layers, there will be no AsyncPanZoomController attached, and
+ so the async transform will be the identity transform. So, in the example above, if layers
+ L and P have APZC instances attached, MT, MN, MD, NT, NN, ND, OT, ON, OD, QT, QN, QD, RT,
+ RN and RD will be identity transforms.
+ Additionally, for space-saving purposes, each APZC instance stores its layer's individual
+ CSS transform and the accumulation of CSS transforms to its parent APZC. So the APZC for
+ layer L would store LC and (MC * NC * OC), and the layer P would store PC and (QC * RC).
+ The APZC instances track the last dispatched paint request and so are able to calculate LD and
+ PD using those internally stored values.
+ The APZCs also obviously have LT, LN, PT, and PN, so all of the above transformation combinations
+ required can be generated.
+ */
+
+/*
+ * See the long comment above for a detailed explanation of this function.
+ */
+ScreenToParentLayerMatrix4x4
+APZCTreeManager::GetScreenToApzcTransform(const AsyncPanZoomController *aApzc) const
+{
+ Matrix4x4 result;
+ MutexAutoLock lock(mTreeLock);
+
+ // The comments below assume there is a chain of layers L..R with L and P having APZC instances as
+ // explained in the comment above. This function is called with aApzc at L, and the loop
+ // below performs one iteration, where parent is at P. The comments explain what values are stored
+ // in the variables at these two levels. All the comments use standard matrix notation where the
+ // leftmost matrix in a multiplication is applied first.
+
+ // ancestorUntransform is PC.Inverse() * OC.Inverse() * NC.Inverse() * MC.Inverse()
+ Matrix4x4 ancestorUntransform = aApzc->GetAncestorTransform().Inverse();
+
+ // result is initialized to PC.Inverse() * OC.Inverse() * NC.Inverse() * MC.Inverse()
+ result = ancestorUntransform;
+
+ for (AsyncPanZoomController* parent = aApzc->GetParent(); parent; parent = parent->GetParent()) {
+ // ancestorUntransform is updated to RC.Inverse() * QC.Inverse() when parent == P
+ ancestorUntransform = parent->GetAncestorTransform().Inverse();
+ // asyncUntransform is updated to PA.Inverse() when parent == P
+ Matrix4x4 asyncUntransform = parent->GetCurrentAsyncTransformWithOverscroll(AsyncPanZoomController::NORMAL).Inverse().ToUnknownMatrix();
+ // untransformSinceLastApzc is RC.Inverse() * QC.Inverse() * PA.Inverse()
+ Matrix4x4 untransformSinceLastApzc = ancestorUntransform * asyncUntransform;
+
+ // result is RC.Inverse() * QC.Inverse() * PA.Inverse() * PC.Inverse() * OC.Inverse() * NC.Inverse() * MC.Inverse()
+ result = untransformSinceLastApzc * result;
+
+ // The above value for result when parent == P matches the required output
+ // as explained in the comment above this method. Note that any missing
+ // terms are guaranteed to be identity transforms.
+ }
+
+ return ViewAs<ScreenToParentLayerMatrix4x4>(result);
+}
+
+/*
+ * See the long comment above GetScreenToApzcTransform() for a detailed
+ * explanation of this function.
+ */
+ParentLayerToScreenMatrix4x4
+APZCTreeManager::GetApzcToGeckoTransform(const AsyncPanZoomController *aApzc) const
+{
+ Matrix4x4 result;
+ MutexAutoLock lock(mTreeLock);
+
+ // The comments below assume there is a chain of layers L..R with L and P having APZC instances as
+ // explained in the comment above. This function is called with aApzc at L, and the loop
+ // below performs one iteration, where parent is at P. The comments explain what values are stored
+ // in the variables at these two levels. All the comments use standard matrix notation where the
+ // leftmost matrix in a multiplication is applied first.
+
+ // asyncUntransform is LA.Inverse()
+ Matrix4x4 asyncUntransform = aApzc->GetCurrentAsyncTransformWithOverscroll(AsyncPanZoomController::NORMAL).Inverse().ToUnknownMatrix();
+
+ // aTransformToGeckoOut is initialized to LA.Inverse() * LD * MC * NC * OC * PC
+ result = asyncUntransform * aApzc->GetTransformToLastDispatchedPaint() * aApzc->GetAncestorTransform();
+
+ for (AsyncPanZoomController* parent = aApzc->GetParent(); parent; parent = parent->GetParent()) {
+ // aTransformToGeckoOut is LA.Inverse() * LD * MC * NC * OC * PC * PD * QC * RC
+ result = result * parent->GetTransformToLastDispatchedPaint() * parent->GetAncestorTransform();
+
+ // The above value for result when parent == P matches the required output
+ // as explained in the comment above this method. Note that any missing
+ // terms are guaranteed to be identity transforms.
+ }
+
+ return ViewAs<ParentLayerToScreenMatrix4x4>(result);
+}
+
+already_AddRefed<AsyncPanZoomController>
+APZCTreeManager::GetMultitouchTarget(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const
+{
+ MutexAutoLock lock(mTreeLock);
+ RefPtr<AsyncPanZoomController> apzc;
+ // For now, we only ever want to do pinching on the root-content APZC for
+ // a given layers id.
+ if (aApzc1 && aApzc2 && aApzc1->GetLayersId() == aApzc2->GetLayersId()) {
+ // If the two APZCs have the same layers id, find the root-content APZC
+ // for that layers id. Don't call CommonAncestor() because there may not
+ // be a common ancestor for the layers id (e.g. if one APZCs is inside a
+ // fixed-position element).
+ apzc = FindRootContentApzcForLayersId(aApzc1->GetLayersId());
+ } else {
+ // Otherwise, find the common ancestor (to reach a common layers id), and
+ // get the root-content APZC for that layers id.
+ apzc = CommonAncestor(aApzc1, aApzc2);
+ if (apzc) {
+ apzc = FindRootContentApzcForLayersId(apzc->GetLayersId());
+ }
+ }
+ return apzc.forget();
+}
+
+already_AddRefed<AsyncPanZoomController>
+APZCTreeManager::CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const
+{
+ mTreeLock.AssertCurrentThreadOwns();
+ RefPtr<AsyncPanZoomController> ancestor;
+
+ // If either aApzc1 or aApzc2 is null, min(depth1, depth2) will be 0 and this function
+ // will return null.
+
+ // Calculate depth of the APZCs in the tree
+ int depth1 = 0, depth2 = 0;
+ for (AsyncPanZoomController* parent = aApzc1; parent; parent = parent->GetParent()) {
+ depth1++;
+ }
+ for (AsyncPanZoomController* parent = aApzc2; parent; parent = parent->GetParent()) {
+ depth2++;
+ }
+
+ // At most one of the following two loops will be executed; the deeper APZC pointer
+ // will get walked up to the depth of the shallower one.
+ int minDepth = depth1 < depth2 ? depth1 : depth2;
+ while (depth1 > minDepth) {
+ depth1--;
+ aApzc1 = aApzc1->GetParent();
+ }
+ while (depth2 > minDepth) {
+ depth2--;
+ aApzc2 = aApzc2->GetParent();
+ }
+
+ // Walk up the ancestor chains of both APZCs, always staying at the same depth for
+ // either APZC, and return the the first common ancestor encountered.
+ while (true) {
+ if (aApzc1 == aApzc2) {
+ ancestor = aApzc1;
+ break;
+ }
+ if (depth1 <= 0) {
+ break;
+ }
+ aApzc1 = aApzc1->GetParent();
+ aApzc2 = aApzc2->GetParent();
+ }
+ return ancestor.forget();
+}
+
+} // namespace layers
+} // namespace mozilla