Bug 1109873 - Introduce the HitTestingTreeNode class. r=botond
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 08 Jan 2015 09:40:01 -0500
changeset 248596 e92ec65eb66b4fb4d940e8bb55eefb47f23554fd
parent 248595 3352803d949bf54210c0c1ea80b1efad280df12a
child 248597 83aa477d175baf0f4628f4151ce0c827ef7d5fd7
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond
bugs1109873
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1109873 - Introduce the HitTestingTreeNode class. r=botond This patch extracts a HitTestingTreeNode from AsyncPanZoomController. The tree formed by these nodes is very similar in structure to the APZC tree that we had previously. The main difference is that we can have multiple HitTestingTreeNode instances per APZC; each HitTestingTreeNode corresponds to a different layer with the same metrics. This is a first step in extracting the hit-test data entirely from the AsyncPanZoomController class and having a 1:1 mapping with the layer tree.
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/APZCTreeManager.h
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
gfx/layers/apz/src/HitTestingTreeNode.cpp
gfx/layers/apz/src/HitTestingTreeNode.h
gfx/layers/moz.build
gfx/tests/gtest/TestAsyncPanZoomController.cpp
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "APZCTreeManager.h"
 #include "AsyncPanZoomController.h"
 #include "Compositor.h"                 // for Compositor
+#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/Point.h"          // for Point
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
 #include "mozilla/layers/CompositorParent.h" // for CompositorParent, etc
 #include "mozilla/layers/LayerMetricsWrapper.h"
@@ -53,20 +54,20 @@ struct APZCTreeManager::TreeBuildingStat
   // State that doesn't change as we recurse in the tree building
   CompositorParent* const mCompositor;
   const bool mIsFirstPaint;
   const uint64_t mOriginatingLayersId;
   const APZPaintLogHelper mPaintLogger;
 
   // State that is updated as we perform the tree build
 
-  // A list of APZCs that need to be destroyed at the end of the tree building.
-  // This is initialized with all APZCs in the old tree, and APZCs are removed
+  // 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< nsRefPtr<AsyncPanZoomController> > mApzcsToDestroy;
+  nsTArray<nsRefPtr<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;
 
   // A stack of event regions which corresponds to the call stack of recursive
   // UpdatePanZoomController calls during tree-building, except that we don't
@@ -135,24 +136,24 @@ APZCTreeManager::GetAllowedTouchBehavior
 
 void
 APZCTreeManager::SetAllowedTouchBehavior(uint64_t aInputBlockId,
                                          const nsTArray<TouchBehaviorFlags> &aValues)
 {
   mInputQueue->SetAllowedTouchBehavior(aInputBlockId, aValues);
 }
 
-/* Flatten the tree of APZC instances into the given nsTArray */
+/* Flatten the tree of nodes into the given nsTArray */
 static void
-Collect(AsyncPanZoomController* aApzc, nsTArray< nsRefPtr<AsyncPanZoomController> >* aCollection)
+Collect(HitTestingTreeNode* aNode, nsTArray<nsRefPtr<HitTestingTreeNode>>* aCollection)
 {
-  if (aApzc) {
-    aCollection->AppendElement(aApzc);
-    Collect(aApzc->GetLastChild(), aCollection);
-    Collect(aApzc->GetPrevSibling(), aCollection);
+  if (aNode) {
+    aCollection->AppendElement(aNode);
+    Collect(aNode->GetLastChild(), aCollection);
+    Collect(aNode->GetPrevSibling(), aCollection);
   }
 }
 
 void
 APZCTreeManager::UpdatePanZoomControllerTree(CompositorParent* aCompositor,
                                              Layer* aRoot,
                                              bool aIsFirstPaint,
                                              uint64_t aOriginatingLayersId,
@@ -184,33 +185,35 @@ APZCTreeManager::UpdatePanZoomController
   // 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.
-  Collect(mRootApzc, &state.mApzcsToDestroy);
-  mRootApzc = nullptr;
+  Collect(mRootNode, &state.mNodesToDestroy);
+  mRootNode = nullptr;
 
   if (aRoot) {
     mApzcTreeLog << "[start]\n";
     LayerMetricsWrapper root(aRoot);
     UpdatePanZoomControllerTree(state, root,
                                 // aCompositor is null in gtest scenarios
                                 aCompositor ? aCompositor->RootLayerTreeId() : 0,
                                 Matrix4x4(), nullptr, nullptr, nsIntRegion());
     mApzcTreeLog << "[end]\n";
   }
   MOZ_ASSERT(state.mEventRegions.Length() == 0);
 
-  for (size_t i = 0; i < state.mApzcsToDestroy.Length(); i++) {
-    APZCTM_LOG("Destroying APZC at %p\n", state.mApzcsToDestroy[i].get());
-    state.mApzcsToDestroy[i]->Destroy();
+  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]->Apzc());
+    state.mNodesToDestroy[i]->Destroy();
   }
 }
 
 // Compute the touch-sensitive region of an APZC. This is used only when
 // event-regions are disabled.
 static nsIntRegion
 ComputeTouchSensitiveRegion(GeckoContentController* aController,
                             const FrameMetrics& aMetrics,
@@ -252,24 +255,40 @@ APZCTreeManager::PrintAPZCInfo(const Lay
   const FrameMetrics& metrics = aLayer.Metrics();
   mApzcTreeLog << "APZC " << apzc->GetGuid() << "\tcb=" << metrics.mCompositionBounds
                << "\tsr=" << metrics.GetScrollableRect()
                << (aLayer.IsScrollInfoLayer() ? "\tscrollinfo" : "")
                << (apzc->HasScrollgrab() ? "\tscrollgrab" : "") << "\t"
                << metrics.GetContentDescription().get();
 }
 
-AsyncPanZoomController*
-APZCTreeManager::PrepareAPZCForLayer(const LayerMetricsWrapper& aLayer,
+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();
+  }
+}
+
+HitTestingTreeNode*
+APZCTreeManager::PrepareNodeForLayer(const LayerMetricsWrapper& aLayer,
                                      const FrameMetrics& aMetrics,
                                      uint64_t aLayersId,
                                      const gfx::Matrix4x4& aAncestorTransform,
                                      const nsIntRegion& aObscured,
-                                     AsyncPanZoomController* aParent,
-                                     AsyncPanZoomController* aNextSibling,
+                                     HitTestingTreeNode* aParent,
+                                     HitTestingTreeNode* aNextSibling,
                                      TreeBuildingState& aState)
 {
   if (!aMetrics.IsScrollable()) {
     return nullptr;
   }
   if (gfxPrefs::LayoutEventRegionsEnabled() && aLayer.IsScrollInfoLayer()) {
     return nullptr;
   }
@@ -294,92 +313,104 @@ APZCTreeManager::PrepareAPZCForLayer(con
     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.
+  nsRefPtr<HitTestingTreeNode> node = nullptr;
   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. Null it out so we run through the
     // code to find another one or create one.
     if (apzc && !apzc->Matches(guid)) {
       apzc = nullptr;
     }
 
-    // If the layer doesn't have an APZC already, try to find one of our
-    // pre-existing ones that matches. In particular, if we find an APZC whose
-    // ScrollableLayerGuid is the same, then we know what happened is that the
-    // layout of the page changed causing the layer tree to be rebuilt, but the
-    // underlying content for which the APZC was originally created is still
-    // there. So it makes sense to pick up that APZC instance again and use it here.
-    if (apzc == nullptr) {
-      for (size_t i = 0; i < aState.mApzcsToDestroy.Length(); i++) {
-        if (aState.mApzcsToDestroy.ElementAt(i)->Matches(guid)) {
-          apzc = aState.mApzcsToDestroy.ElementAt(i);
-          break;
+    // 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++) {
+      nsRefPtr<HitTestingTreeNode> n = aState.mNodesToDestroy[i];
+      if (n->IsPrimaryHolder() && n->Apzc()->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->Apzc());
         }
+        apzc = node->Apzc();
+        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.
+    // 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) {
       apzc = MakeAPZCInstance(aLayersId, state->mController);
       apzc->SetCompositorParent(aState.mCompositor);
       if (state->mCrossProcessParent != nullptr) {
         apzc->ShareFrameMetricsAcrossProcesses();
       }
+      MOZ_ASSERT(node == nullptr);
+      node = new HitTestingTreeNode(apzc, true);
     } else {
-      // If there was already an APZC for the layer clear the tree pointers
-      // so that it doesn't continue pointing to APZCs that should no longer
+      // 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 APZCs that are going
+      // 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.mApzcsToDestroy.RemoveElement(apzc);
-      apzc->SetPrevSibling(nullptr);
-      apzc->SetLastChild(nullptr);
+      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(aMetrics,
         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->Apzc()->Matches(guid));
+
     nsIntRegion unobscured;
     if (!gfxPrefs::LayoutEventRegionsEnabled()) {
       unobscured = ComputeTouchSensitiveRegion(state->mController, aMetrics, aObscured);
     }
     // This initializes, among other things, the APZC's hit-region.
     // If event-regions are disabled, this will initialize it to the empty
     // region, but UpdatePanZoomControllerTree will add the hit-region from
     // the event regions later.
     apzc->SetLayerHitTestData(EventRegions(unobscured), aAncestorTransform);
     APZCTM_LOG("Setting region %s as visible region for APZC %p\n",
         Stringify(unobscured).c_str(), apzc);
 
     PrintAPZCInfo(aLayer, apzc);
 
     // Bind the APZC instance into the tree of APZCs
-    if (aNextSibling) {
-      aNextSibling->SetPrevSibling(apzc);
-    } else if (aParent) {
-      aParent->SetLastChild(apzc);
-    } else {
-      MOZ_ASSERT(!mRootApzc);
-      mRootApzc = apzc;
-      apzc->MakeRoot();
-    }
+    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 && apzc->GetParent()) {
@@ -418,42 +449,56 @@ APZCTreeManager::PrepareAPZCForLayer(con
     // Consider the case where we have three layers: A, B, and C. A is at the top in
     // z-order and C is at the bottom. A and C share a scrollid and scroll together; but
     // B has a different scrollid and scrolls independently. Depending on how B moves
     // and the async transform on it, a larger/smaller area of C may be unobscured.
     // However, when we combine the hit regions of A and C here we are ignoring the
     // async transform and so we basically assume the same amount of C is always visible
     // on top of B. Fixing this doesn't appear to be very easy so I'm leaving it for
     // now in the hopes that we won't run into this problem a lot.
+
+    // TODO(optimization): we could recycle one of the non-primary-holder nodes
+    // from mNodesToDestroy instead of creating a new one since those are going
+    // to get discarded anyway.
+    node = new HitTestingTreeNode(apzc, false);
+    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 the same.
+    MOZ_ASSERT(aAncestorTransform == apzc->GetAncestorTransform());
+
     if (!gfxPrefs::LayoutEventRegionsEnabled()) {
       nsIntRegion unobscured = ComputeTouchSensitiveRegion(state->mController, aMetrics, aObscured);
       apzc->AddHitTestRegions(EventRegions(unobscured));
       APZCTM_LOG("Adding region %s to visible region of APZC %p\n", Stringify(unobscured).c_str(), apzc);
     }
   }
 
-  return apzc;
+  return node;
 }
 
-AsyncPanZoomController*
+HitTestingTreeNode*
 APZCTreeManager::UpdatePanZoomControllerTree(TreeBuildingState& aState,
                                              const LayerMetricsWrapper& aLayer,
                                              uint64_t aLayersId,
                                              const gfx::Matrix4x4& aAncestorTransform,
-                                             AsyncPanZoomController* aParent,
-                                             AsyncPanZoomController* aNextSibling,
+                                             HitTestingTreeNode* aParent,
+                                             HitTestingTreeNode* aNextSibling,
                                              const nsIntRegion& aObscured)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
   mApzcTreeLog << aLayer.Name() << '\t';
 
-  AsyncPanZoomController* apzc = PrepareAPZCForLayer(aLayer,
+  HitTestingTreeNode* node = PrepareNodeForLayer(aLayer,
         aLayer.Metrics(), aLayersId, aAncestorTransform,
         aObscured, aParent, aNextSibling, aState);
+  AsyncPanZoomController* apzc = (node ? node->Apzc() : nullptr);
   aLayer.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
@@ -482,28 +527,25 @@ APZCTreeManager::UpdatePanZoomController
     // have to check for mask layers and so on in order to properly handle
     // that case.
     obscured = aObscured;
     obscured.Transform(To3DMatrix(transform).Inverse());
   }
 
   // If there's no APZC at this level, any APZCs for our child layers will
   // have our siblings as their siblings, and our parent as their parent.
-  AsyncPanZoomController* next = aNextSibling;
-  if (apzc) {
+  HitTestingTreeNode* next = aNextSibling;
+  if (node) {
     // Otherwise, use this APZC as the parent going downwards, and start off
     // with its first child as the next sibling.
-    // Note that |apzc| at this point will not have children corresponding to
-    // the subtree of aLayer (those children will be populated in the loop
-    // below). However, it might have children corresponding to the subtree of
-    // another layer that shares the same APZC and that has already been
-    // visited; the children added at this level will become prev-siblings
-    // of those.
-    aParent = apzc;
-    next = apzc->GetFirstChild();
+    // 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());
+    aParent = node;
+    next = nullptr;
   }
 
   // In our recursive downward traversal, track event regions for layers once
   // we encounter an APZC. Push a new empty region on the mEventRegions stack
   // which will accumulate the hit area of descendants of aLayer. In general,
   // the mEventRegions stack is used to accumulate event regions from descendant
   // layers because the event regions for a layer don't include those of its
   // children.
@@ -599,22 +641,22 @@ APZCTreeManager::UpdatePanZoomController
     } else {
       // If we don't have an APZC at this level, carry the subtree hit region
       // up to the parent.
       MOZ_ASSERT(aState.mEventRegions.Length() > 0);
       aState.mEventRegions.LastElement().OrWith(subtreeEventRegions);
     }
   }
 
-  // Return the APZC that should be the sibling of other APZCs as we continue
+  // Return the node that should be the sibling of other nodes as we continue
   // moving towards the first child at this depth in the layer tree.
-  // If this layer doesn't have an APZC, we promote any APZCs in the subtree
+  // If this layer doesn't have an APZC, we promote any nodes in the subtree
   // upwards. Otherwise we fall back to the aNextSibling that was passed in.
-  if (apzc) {
-    return apzc;
+  if (node) {
+    return node;
   }
   if (next) {
     return next;
   }
   return aNextSibling;
 }
 
 nsEventStatus
@@ -745,18 +787,18 @@ APZCTreeManager::GetTouchInputBlockAPZC(
     // at the start of an input block for a number of reasons. One of the reasons is so that
     // after we untransform the event into gecko space, it doesn't end up under something
     // else. Another reason is that if we hit-test this event and end up on a layer's
     // dispatch-to-content region we cannot be sure we actually got the correct layer. We
     // have to fall back to the gecko hit-test to handle this case, but we can't untransform
     // the event we send to gecko because we don't know the layer to untransform with
     // respect to.
     MonitorAutoLock lock(mTreeLock);
-    for (AsyncPanZoomController* apzc = mRootApzc; apzc; apzc = apzc->GetPrevSibling()) {
-      FlushRepaintsRecursively(apzc);
+    for (HitTestingTreeNode* node = mRootNode; node; node = node->GetPrevSibling()) {
+      FlushRepaintsRecursively(node);
     }
   }
 
   apzc = GetTargetAPZC(aEvent.mTouches[0].mScreenPoint, aOutHitResult);
   for (size_t i = 1; i < aEvent.mTouches.Length(); i++) {
     nsRefPtr<AsyncPanZoomController> apzc2 = GetTargetAPZC(aEvent.mTouches[i].mScreenPoint, aOutHitResult);
     apzc = GetMultitouchTarget(apzc, apzc2);
     APZCTM_LOG("Using APZC %p as the root APZC for multi-touch\n", apzc.get());
@@ -1029,64 +1071,68 @@ void
 APZCTreeManager::SetTargetAPZC(uint64_t aInputBlockId,
                                const nsTArray<ScrollableLayerGuid>& aTargets)
 {
   nsRefPtr<AsyncPanZoomController> target = nullptr;
   if (aTargets.Length() > 0) {
     target = GetTargetAPZC(aTargets[0]);
   }
   for (size_t i = 1; i < aTargets.Length(); i++) {
-    nsRefPtr<AsyncPanZoomController> apzc2 = GetTargetAPZC(aTargets[i]);
-    target = GetMultitouchTarget(target, apzc2);
+    nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aTargets[i]);
+    target = GetMultitouchTarget(target, apzc);
   }
   mInputQueue->SetConfirmedTargetApzc(aInputBlockId, target);
 }
 
 void
 APZCTreeManager::SetTargetAPZC(uint64_t aInputBlockId, const ScrollableLayerGuid& aTarget)
 {
-  nsRefPtr<AsyncPanZoomController> target = GetTargetAPZC(aTarget);
-  mInputQueue->SetConfirmedTargetApzc(aInputBlockId, target);
+  nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aTarget);
+  mInputQueue->SetConfirmedTargetApzc(aInputBlockId, apzc);
 }
 
 void
 APZCTreeManager::UpdateZoomConstraints(const ScrollableLayerGuid& aGuid,
                                        const ZoomConstraints& aConstraints)
 {
-  nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
+  nsRefPtr<HitTestingTreeNode> node = GetTargetNode(aGuid, nullptr);
   // For a given layers id, non-root APZCs inherit the zoom constraints
   // of their root.
-  if (apzc && apzc->IsRootForLayersId()) {
+  if (node && node->Apzc()->IsRootForLayersId()) {
     MonitorAutoLock lock(mTreeLock);
-    UpdateZoomConstraintsRecursively(apzc.get(), aConstraints);
+    UpdateZoomConstraintsRecursively(node.get(), aConstraints);
   }
 }
 
 void
-APZCTreeManager::UpdateZoomConstraintsRecursively(AsyncPanZoomController* aApzc,
+APZCTreeManager::UpdateZoomConstraintsRecursively(HitTestingTreeNode* aNode,
                                                   const ZoomConstraints& aConstraints)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
-  aApzc->UpdateZoomConstraints(aConstraints);
-  for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
+  if (aNode->IsPrimaryHolder()) {
+    aNode->Apzc()->UpdateZoomConstraints(aConstraints);
+  }
+  for (HitTestingTreeNode* child = aNode->GetLastChild(); child; child = child->GetPrevSibling()) {
     // We can have subtrees with their own layers id - leave those alone.
-    if (!child->IsRootForLayersId()) {
+    if (!child->Apzc()->IsRootForLayersId()) {
       UpdateZoomConstraintsRecursively(child, aConstraints);
     }
   }
 }
 
 void
-APZCTreeManager::FlushRepaintsRecursively(AsyncPanZoomController* aApzc)
+APZCTreeManager::FlushRepaintsRecursively(HitTestingTreeNode* aNode)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
-  aApzc->FlushRepaintForNewInputBlock();
-  for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
+  if (aNode->IsPrimaryHolder()) {
+    aNode->Apzc()->FlushRepaintForNewInputBlock();
+  }
+  for (HitTestingTreeNode* child = aNode->GetLastChild(); child; child = child->GetPrevSibling()) {
     FlushRepaintsRecursively(child);
   }
 }
 
 void
 APZCTreeManager::CancelAnimation(const ScrollableLayerGuid &aGuid)
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
@@ -1098,22 +1144,29 @@ APZCTreeManager::CancelAnimation(const S
 void
 APZCTreeManager::ClearTree()
 {
   MonitorAutoLock lock(mTreeLock);
 
   // This can be done as part of a tree walk but it's easier to
   // just re-use the Collect method that we need in other places.
   // If this is too slow feel free to change it to a recursive walk.
-  nsTArray< nsRefPtr<AsyncPanZoomController> > apzcsToDestroy;
-  Collect(mRootApzc, &apzcsToDestroy);
-  for (size_t i = 0; i < apzcsToDestroy.Length(); i++) {
-    apzcsToDestroy[i]->Destroy();
+  nsTArray<nsRefPtr<HitTestingTreeNode>> nodesToDestroy;
+  Collect(mRootNode, &nodesToDestroy);
+  for (size_t i = 0; i < nodesToDestroy.Length(); i++) {
+    nodesToDestroy[i]->Destroy();
   }
-  mRootApzc = nullptr;
+  mRootNode = nullptr;
+}
+
+nsRefPtr<HitTestingTreeNode>
+APZCTreeManager::GetRootNode() const
+{
+  MonitorAutoLock 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
@@ -1242,53 +1295,79 @@ APZCTreeManager::DispatchFling(AsyncPanZ
 bool
 APZCTreeManager::HitTestAPZC(const ScreenIntPoint& aPoint)
 {
   nsRefPtr<AsyncPanZoomController> target = GetTargetAPZC(aPoint, nullptr);
   return target != nullptr;
 }
 
 already_AddRefed<AsyncPanZoomController>
-APZCTreeManager::GetTargetAPZC(const ScrollableLayerGuid& aGuid)
+APZCTreeManager::GetTargetAPZC(const ScrollableLayerGuid& aGuid,
+                               GuidComparator aComparator)
+{
+  nsRefPtr<HitTestingTreeNode> node = GetTargetNode(aGuid, aComparator);
+  nsRefPtr<AsyncPanZoomController> apzc = node ? node->Apzc() : nullptr;
+  return apzc.forget();
+}
+
+already_AddRefed<HitTestingTreeNode>
+APZCTreeManager::GetTargetNode(const ScrollableLayerGuid& aGuid,
+                               GuidComparator aComparator)
 {
   MonitorAutoLock lock(mTreeLock);
-  nsRefPtr<AsyncPanZoomController> target;
+  nsRefPtr<HitTestingTreeNode> target;
   // The root may have siblings, check those too
-  for (AsyncPanZoomController* apzc = mRootApzc; apzc; apzc = apzc->GetPrevSibling()) {
-    target = FindTargetAPZC(apzc, aGuid);
+  for (HitTestingTreeNode* node = mRootNode; node; node = node->GetPrevSibling()) {
+    target = FindTargetNode(node, aGuid, aComparator);
     if (target) {
       break;
     }
   }
   return target.forget();
 }
 
 already_AddRefed<AsyncPanZoomController>
 APZCTreeManager::GetTargetAPZC(const ScreenPoint& aPoint, HitTestResult* aOutHitResult)
 {
   MonitorAutoLock lock(mTreeLock);
   nsRefPtr<AsyncPanZoomController> target;
   // The root may have siblings, so check those too
   HitTestResult hitResult = NoApzcHit;
-  for (AsyncPanZoomController* apzc = mRootApzc; apzc; apzc = apzc->GetPrevSibling()) {
-    target = GetAPZCAtPoint(apzc, aPoint.ToUnknownPoint(), &hitResult);
+  for (HitTestingTreeNode* node = mRootNode; node; node = node->GetPrevSibling()) {
+    target = GetAPZCAtPoint(node, aPoint.ToUnknownPoint(), &hitResult);
+    if (target == node->Apzc() && node->GetPrevSibling() && target == node->GetPrevSibling()->Apzc()) {
+      // We might be able to do better if we keep looping, so let's do that.
+      // This happens because the hit-test data stored in "target" actually
+      // includes the areas of all the nodes that have "target" as their APZC.
+      // One of these nodes (that we haven't yet encountered) may have a child
+      // that is on top of this area, so we need to check for that. A future
+      // patch will deal with this more correctly.
+      continue;
+    }
     // If we hit an overscrolled APZC, 'target' will be nullptr but it's still
     // a hit so we don't search further siblings.
     if (target || (hitResult == OverscrolledApzc)) {
       break;
     }
   }
   // If we are in an overscrolled APZC, we should be returning nullptr.
   MOZ_ASSERT(!(target && (hitResult == OverscrolledApzc)));
   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;
+}
+
 nsRefPtr<const OverscrollHandoffChain>
 APZCTreeManager::BuildOverscrollHandoffChain(const nsRefPtr<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
@@ -1330,17 +1409,19 @@ APZCTreeManager::BuildOverscrollHandoffC
       // 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 (!scrollParent) {
-      scrollParent = FindTargetAPZC(parent, apzc->GetScrollHandoffParentId());
+      ScrollableLayerGuid guid(parent->GetGuid().mLayersId, 0, apzc->GetScrollHandoffParentId());
+      nsRefPtr<AsyncPanZoomController> scrollParentPtr = GetTargetAPZC(guid, &GuidComparatorIgnoringPresShell);
+      scrollParent = scrollParentPtr.get();
     }
     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();
@@ -1348,127 +1429,121 @@ APZCTreeManager::BuildOverscrollHandoffC
   // 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;
 }
 
-/* Find the apzc in the subtree rooted at aApzc that has the same layers id as
-   aApzc, and that has the given scroll id. Generally this function should be called
-   with aApzc being the root of its layers id subtree. */
-AsyncPanZoomController*
-APZCTreeManager::FindTargetAPZC(AsyncPanZoomController* aApzc, FrameMetrics::ViewID aScrollId)
-{
-  mTreeLock.AssertCurrentThreadOwns();
-
-  if (aApzc->GetGuid().mScrollId == aScrollId) {
-    return aApzc;
-  }
-  for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
-    if (child->GetGuid().mLayersId != aApzc->GetGuid().mLayersId) {
-      continue;
-    }
-    AsyncPanZoomController* match = FindTargetAPZC(child, aScrollId);
-    if (match) {
-      return match;
-    }
-  }
-
-  return nullptr;
-}
-
-AsyncPanZoomController*
-APZCTreeManager::FindTargetAPZC(AsyncPanZoomController* aApzc, const ScrollableLayerGuid& aGuid)
+HitTestingTreeNode*
+APZCTreeManager::FindTargetNode(HitTestingTreeNode* aNode,
+                                const ScrollableLayerGuid& aGuid,
+                                GuidComparator aComparator)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
   // This walks the tree in depth-first, reverse order, so that it encounters
   // APZCs front-to-back on the screen.
-  for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
-    AsyncPanZoomController* match = FindTargetAPZC(child, aGuid);
+  for (HitTestingTreeNode* child = aNode->GetLastChild(); child; child = child->GetPrevSibling()) {
+    HitTestingTreeNode* match = FindTargetNode(child, aGuid, aComparator);
     if (match) {
       return match;
     }
   }
 
-  if (aApzc->Matches(aGuid)) {
-    return aApzc;
+  bool matches = false;
+  if (aComparator) {
+    matches = aComparator(aGuid, aNode->Apzc()->GetGuid());
+  } else {
+    matches = aNode->Apzc()->Matches(aGuid);
+  }
+  if (matches) {
+    return aNode;
   }
   return nullptr;
 }
 
 AsyncPanZoomController*
-APZCTreeManager::GetAPZCAtPoint(AsyncPanZoomController* aApzc,
+APZCTreeManager::GetAPZCAtPoint(HitTestingTreeNode* aNode,
                                 const Point& aHitTestPoint,
                                 HitTestResult* aOutHitResult)
 {
   mTreeLock.AssertCurrentThreadOwns();
+  AsyncPanZoomController* apzc = aNode->Apzc();
 
   // 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 GetScreenToApzcTransform. This function will recurse with aApzc at L and P, and the
+  // explained in the comment above GetScreenToApzcTransform. This function will recurse with apzc at L and P, and 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 takes points from aApzc's parent APZC's CSS-transformed layer coordinates
-  // to aApzc's parent layer's layer coordinates.
+  // ancestorUntransform takes points from apzc's parent APZC's CSS-transformed layer coordinates
+  // to apzc's parent layer's layer coordinates.
   // It is PC.Inverse() * OC.Inverse() * NC.Inverse() * MC.Inverse() at recursion level for L,
   //   and RC.Inverse() * QC.Inverse()                               at recursion level for P.
-  Matrix4x4 ancestorUntransform = aApzc->GetAncestorTransform().Inverse();
+  Matrix4x4 ancestorUntransform = apzc->GetAncestorTransform().Inverse();
 
   // Hit testing for this layer takes place in our parent layer coordinates,
   // since the composition bounds (used to initialize the visible rect against
   // which we hit test are in those coordinates).
   Point4D hitTestPointForThisLayer = ancestorUntransform.ProjectPoint(aHitTestPoint);
   APZCTM_LOG("Untransformed %f %f to transient coordinates %f %f for hit-testing APZC %p\n",
            aHitTestPoint.x, aHitTestPoint.y,
-           hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, aApzc);
+           hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, apzc);
 
-  // childUntransform takes points from aApzc's parent APZC's CSS-transformed layer coordinates
-  // to aApzc's CSS-transformed layer coordinates.
+  // childUntransform takes points from apzc's parent APZC's CSS-transformed layer coordinates
+  // to apzc's CSS-transformed layer coordinates.
   // It is PC.Inverse() * OC.Inverse() * NC.Inverse() * MC.Inverse() * LA.Inverse() at L
   //   and RC.Inverse() * QC.Inverse() * PA.Inverse()                               at P.
-  Matrix4x4 childUntransform = ancestorUntransform * Matrix4x4(aApzc->GetCurrentAsyncTransform()).Inverse();
+  Matrix4x4 childUntransform = ancestorUntransform * Matrix4x4(apzc->GetCurrentAsyncTransform()).Inverse();
   Point4D hitTestPointForChildLayers = childUntransform.ProjectPoint(aHitTestPoint);
   APZCTM_LOG("Untransformed %f %f to layer coordinates %f %f for APZC %p\n",
            aHitTestPoint.x, aHitTestPoint.y,
-           hitTestPointForChildLayers.x, hitTestPointForChildLayers.y, aApzc);
+           hitTestPointForChildLayers.x, hitTestPointForChildLayers.y, apzc);
 
   AsyncPanZoomController* result = nullptr;
   // This walks the tree in depth-first, reverse order, so that it encounters
   // APZCs front-to-back on the screen.
   if (hitTestPointForChildLayers.HasPositiveWCoord()) {
-    for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
+    for (HitTestingTreeNode* child = aNode->GetLastChild(); child; child = child->GetPrevSibling()) {
       AsyncPanZoomController* match = GetAPZCAtPoint(child, hitTestPointForChildLayers.As2DPoint(), aOutHitResult);
+      if (match == child->Apzc() && child->GetPrevSibling() && match == child->GetPrevSibling()->Apzc()) {
+        // We might be able to do better if we keep looping, so let's do that.
+        // This happens because the hit-test data stored in "target" actually
+        // includes the areas of all the nodes that have "target" as their APZC.
+        // One of these nodes (that we haven't yet encountered) may have a child
+        // that is on top of this area, so we need to check for that. A future
+        // patch will deal with this more correctly.
+        continue;
+      }
       if (*aOutHitResult == OverscrolledApzc) {
         // We matched an overscrolled APZC, abort.
         return nullptr;
       }
       if (match) {
         result = match;
         break;
       }
     }
   }
   if (!result && hitTestPointForThisLayer.HasPositiveWCoord()) {
     ParentLayerPoint point = ParentLayerPoint::FromUnknownPoint(hitTestPointForThisLayer.As2DPoint());
-    if (aApzc->HitRegionContains(point)) {
+    if (apzc->HitRegionContains(point)) {
       APZCTM_LOG("Successfully matched untransformed point %f %f to visible region for APZC %p\n",
-                 hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, aApzc);
-      result = aApzc;
+                 hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, apzc);
+      result = apzc;
       // If event regions are disabled, *aOutHitResult will be ApzcHitRegion
-      *aOutHitResult = (aApzc->DispatchToContentRegionContains(point) ? ApzcContentRegion : ApzcHitRegion);
+      *aOutHitResult = (apzc->DispatchToContentRegionContains(point) ? ApzcContentRegion : ApzcHitRegion);
     }
   }
 
   // If we are overscrolled, and the point matches us or one of our children,
   // the result is inside an overscrolled APZC, inform our caller of this
   // (callers typically ignore events targeted at overscrolled APZCs).
-  if (result && aApzc->IsOverscrolled()) {
+  if (result && apzc->IsOverscrolled()) {
     *aOutHitResult = OverscrolledApzc;
     result = nullptr;
   }
 
   return result;
 }
 
 /* The methods GetScreenToApzcTransform() and GetApzcToGeckoTransform() return
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -43,16 +43,17 @@ class Layer;
 class AsyncPanZoomController;
 class CompositorParent;
 class APZPaintLogHelper;
 class OverscrollHandoffChain;
 struct OverscrollHandoffState;
 class LayerMetricsWrapper;
 class InputQueue;
 class GeckoContentController;
+class HitTestingTreeNode;
 
 /**
  * ****************** NOTE ON LOCK ORDERING IN APZ **************************
  *
  * There are two kinds of locks used by APZ: APZCTreeManager::mTreeLock
  * ("the tree lock") and AsyncPanZoomController::mMonitor ("APZC locks").
  *
  * To avoid deadlock, we impose a lock ordering between these locks, which is:
@@ -400,54 +401,64 @@ public:
   */
   enum HitTestResult {
     NoApzcHit,
     ApzcHitRegion,
     ApzcContentRegion,
     OverscrolledApzc,
   };
 
-  already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScrollableLayerGuid& aGuid);
+  nsRefPtr<HitTestingTreeNode> GetRootNode() const;
   already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint,
                                                          HitTestResult* aOutHitResult);
   gfx::Matrix4x4 GetScreenToApzcTransform(const AsyncPanZoomController *aApzc) const;
   gfx::Matrix4x4 GetApzcToGeckoTransform(const AsyncPanZoomController *aApzc) const;
 private:
+  typedef bool (*GuidComparator)(const ScrollableLayerGuid&, const ScrollableLayerGuid&);
+
   /* Helpers */
-  AsyncPanZoomController* FindTargetAPZC(AsyncPanZoomController* aApzc, FrameMetrics::ViewID aScrollId);
-  AsyncPanZoomController* FindTargetAPZC(AsyncPanZoomController* aApzc, const ScrollableLayerGuid& aGuid);
-  AsyncPanZoomController* GetAPZCAtPoint(AsyncPanZoomController* aApzc,
+  void AttachNodeToTree(HitTestingTreeNode* aNode,
+                        HitTestingTreeNode* aParent,
+                        HitTestingTreeNode* aNextSibling);
+  already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScrollableLayerGuid& aGuid,
+                                                         GuidComparator aComparator = nullptr);
+  already_AddRefed<HitTestingTreeNode> GetTargetNode(const ScrollableLayerGuid& aGuid,
+                                                     GuidComparator aComparator);
+  HitTestingTreeNode* FindTargetNode(HitTestingTreeNode* aNode,
+                                     const ScrollableLayerGuid& aGuid,
+                                     GuidComparator aComparator);
+  AsyncPanZoomController* GetAPZCAtPoint(HitTestingTreeNode* aNode,
                                          const gfx::Point& aHitTestPoint,
                                          HitTestResult* aOutHitResult);
   already_AddRefed<AsyncPanZoomController> GetMultitouchTarget(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const;
   already_AddRefed<AsyncPanZoomController> CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const;
   already_AddRefed<AsyncPanZoomController> RootAPZCForLayersId(AsyncPanZoomController* aApzc) const;
   already_AddRefed<AsyncPanZoomController> GetTouchInputBlockAPZC(const MultiTouchInput& aEvent,
                                                                   HitTestResult* aOutHitResult);
   nsEventStatus ProcessTouchInput(MultiTouchInput& aInput,
                                   ScrollableLayerGuid* aOutTargetGuid,
                                   uint64_t* aOutInputBlockId);
   nsEventStatus ProcessWheelEvent(WidgetWheelEvent& aEvent,
                                   ScrollableLayerGuid* aOutTargetGuid,
                                   uint64_t* aOutInputBlockId);
   nsEventStatus ProcessEvent(WidgetInputEvent& inputEvent,
                              ScrollableLayerGuid* aOutTargetGuid,
                              uint64_t* aOutInputBlockId);
-  void UpdateZoomConstraintsRecursively(AsyncPanZoomController* aApzc,
+  void UpdateZoomConstraintsRecursively(HitTestingTreeNode* aNode,
                                         const ZoomConstraints& aConstraints);
-  void FlushRepaintsRecursively(AsyncPanZoomController* aApzc);
+  void FlushRepaintsRecursively(HitTestingTreeNode* aNode);
 
-  AsyncPanZoomController* PrepareAPZCForLayer(const LayerMetricsWrapper& aLayer,
-                                              const FrameMetrics& aMetrics,
-                                              uint64_t aLayersId,
-                                              const gfx::Matrix4x4& aAncestorTransform,
-                                              const nsIntRegion& aObscured,
-                                              AsyncPanZoomController* aParent,
-                                              AsyncPanZoomController* aNextSibling,
-                                              TreeBuildingState& aState);
+  HitTestingTreeNode* PrepareNodeForLayer(const LayerMetricsWrapper& aLayer,
+                                          const FrameMetrics& aMetrics,
+                                          uint64_t aLayersId,
+                                          const gfx::Matrix4x4& aAncestorTransform,
+                                          const nsIntRegion& aObscured,
+                                          HitTestingTreeNode* aParent,
+                                          HitTestingTreeNode* aNextSibling,
+                                          TreeBuildingState& aState);
 
   /**
    * Recursive helper function to build the APZC tree. The tree of APZC instances has
    * the same shape as the layer tree, but excludes all the layers that are not scrollable.
    * Note that this means APZCs corresponding to layers at different depths in the tree
    * may end up becoming siblings. It also means that the "root" APZC may have siblings.
    * This function walks the layer tree backwards through siblings and constructs the APZC
    * tree also as a last-child-prev-sibling tree because that simplifies the hit detection
@@ -457,53 +468,53 @@ private:
    * @param aLayer             The (layer, metrics) pair which is the current
    *                           position in the recursive walk of the layer tree.
    *                           This calls builds an APZC subtree corresponding
    *                           to the layer subtree rooted at aLayer.
    * @param aLayersId          The layers id of the layer in aLayer.
    * @param aAncestorTransform The accumulated CSS transforms of all the
    *                           layers from aLayer up (via the parent chain)
    *                           to the next APZC-bearing layer.
-   * @param aParent            The parent of any APZC built at this level.
-   * @param aNextSibling       The next sibling any APZC built at this level.
+   * @param aParent            The parent of any node built at this level.
+   * @param aNextSibling       The next sibling of any node built at this level.
    * @param aObscured          The region that is obscured by APZCs above
    *                           the one at this level (in practice, the
    *                           next-siblings of this one), in the ParentLayer
    *                           pixels of the APZC at this level.
    * @return                   The root of the APZC subtree at this level, or
    *                           aNextSibling if no APZCs were built for the
    *                           layer subtree at this level.
    */
-  AsyncPanZoomController* UpdatePanZoomControllerTree(TreeBuildingState& aState,
-                                                      const LayerMetricsWrapper& aLayer,
-                                                      uint64_t aLayersId,
-                                                      const gfx::Matrix4x4& aAncestorTransform,
-                                                      AsyncPanZoomController* aParent,
-                                                      AsyncPanZoomController* aNextSibling,
-                                                      const nsIntRegion& aObscured);
+  HitTestingTreeNode* UpdatePanZoomControllerTree(TreeBuildingState& aState,
+                                                  const LayerMetricsWrapper& aLayer,
+                                                  uint64_t aLayersId,
+                                                  const gfx::Matrix4x4& aAncestorTransform,
+                                                  HitTestingTreeNode* aParent,
+                                                  HitTestingTreeNode* aNextSibling,
+                                                  const nsIntRegion& aObscured);
 
   void PrintAPZCInfo(const LayerMetricsWrapper& aLayer,
                      const AsyncPanZoomController* apzc);
 
 protected:
   /* The input queue where input events are held until we know enough to
    * figure out where they're going. Protected so gtests can access it.
    */
   nsRefPtr<InputQueue> mInputQueue;
 
 private:
-  /* Whenever walking or mutating the tree rooted at mRootApzc, mTreeLock must be held.
+  /* Whenever walking or mutating the tree rooted at mRootNode, mTreeLock must be held.
    * This lock does not need to be held while manipulating a single APZC instance in
    * isolation (that is, if its tree pointers are not being accessed or mutated). The
-   * lock also needs to be held when accessing the mRootApzc instance variable, as that
+   * lock also needs to be held when accessing the mRootNode instance variable, as that
    * is considered part of the APZC tree management state.
    * Finally, the lock needs to be held when accessing mOverscrollHandoffChain.
    * IMPORTANT: See the note about lock ordering at the top of this file. */
   mutable mozilla::Monitor mTreeLock;
-  nsRefPtr<AsyncPanZoomController> mRootApzc;
+  nsRefPtr<HitTestingTreeNode> mRootNode;
   /* This tracks the APZC that should receive all inputs for the current input event block.
    * This allows touch points to move outside the thing they started on, but still have the
    * touch events delivered to the same initial APZC. This will only ever be touched on the
    * input delivery thread, and so does not require locking.
    */
   nsRefPtr<AsyncPanZoomController> mApzcForInputBlock;
   /* The hit result for the current input event block; this should always be in
    * sync with mApzcForInputBlock.
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -911,17 +911,19 @@ AsyncPanZoomController::AsyncPanZoomCont
      mSharedLock(nullptr),
      mAsyncTransformAppliedToContent(false)
 {
   if (aGestures == USE_GESTURE_DETECTOR) {
     mGestureEventListener = new GestureEventListener(this);
   }
 }
 
-AsyncPanZoomController::~AsyncPanZoomController() {
+AsyncPanZoomController::~AsyncPanZoomController()
+{
+  MOZ_ASSERT(IsDestroyed());
 }
 
 PCompositorParent*
 AsyncPanZoomController::GetSharedFrameMetricsCompositor()
 {
   AssertOnCompositorThread();
 
   if (mSharingFrameMetricsAcrossProcesses) {
@@ -958,18 +960,16 @@ AsyncPanZoomController::Destroy()
 
   CancelAnimation();
 
   { // scope the lock
     MonitorAutoLock lock(mRefPtrMonitor);
     mGeckoContentController = nullptr;
     mGestureEventListener = nullptr;
   }
-  mPrevSibling = nullptr;
-  mLastChild = nullptr;
   mParent = nullptr;
   mTreeManager = nullptr;
 
   PCompositorParent* compositor = GetSharedFrameMetricsCompositor();
   // Only send the release message if the SharedFrameMetrics has been created.
   if (compositor && mSharedFrameMetricsBuffer) {
     unused << compositor->SendReleaseSharedCompositorFrameMetrics(mFrameMetrics.GetScrollId(), mAPZCId);
   }
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -840,51 +840,27 @@ private:
                    bool aHandoff);
 
   // Start an overscroll animation with the given initial velocity.
   void StartOverscrollAnimation(const ParentLayerPoint& aVelocity);
 
   void StartSmoothScroll();
 
   /* ===================================================================
-   * The functions and members in this section are used to build a tree
-   * structure out of APZC instances. This tree can only be walked or
-   * manipulated while holding the lock in the associated APZCTreeManager
-   * instance.
+   * The functions and members in this section are used to make ancestor chains
+   * out of APZC instances. These chains can only be walked or manipulated
+   * while holding the lock in the associated APZCTreeManager instance.
    */
 public:
-  void SetLastChild(AsyncPanZoomController* child) {
-    mLastChild = child;
-    if (child) {
-      child->mParent = this;
-    }
+  void SetParent(AsyncPanZoomController* aParent) {
+    mParent = aParent;
   }
 
-  void SetPrevSibling(AsyncPanZoomController* sibling) {
-    mPrevSibling = sibling;
-    if (sibling) {
-      sibling->mParent = mParent;
-    }
-  }
-
-  // Make this APZC the root of the APZC tree. Clears the parent pointer.
-  void MakeRoot() {
-    mParent = nullptr;
-  }
-
-  AsyncPanZoomController* GetLastChild() const { return mLastChild; }
-  AsyncPanZoomController* GetPrevSibling() const { return mPrevSibling; }
-  AsyncPanZoomController* GetParent() const { return mParent; }
-
-  AsyncPanZoomController* GetFirstChild() const {
-    AsyncPanZoomController* child = GetLastChild();
-    while (child && child->GetPrevSibling()) {
-      child = child->GetPrevSibling();
-    }
-    return child;
+  AsyncPanZoomController* GetParent() const {
+    return mParent;
   }
 
   /* Returns true if there is no APZC higher in the tree with the same
    * layers id.
    */
   bool IsRootForLayersId() const {
     return !mParent || (mParent->mLayersId != mLayersId);
   }
@@ -892,18 +868,16 @@ public:
 private:
   // This is a raw pointer to avoid introducing a reference cycle between
   // AsyncPanZoomController and APZCTreeManager. Since these objects don't
   // live on the main thread, we can't use the cycle collector with them.
   // The APZCTreeManager owns the lifetime of the APZCs, so nulling this
   // pointer out in Destroy() will prevent accessing deleted memory.
   Atomic<APZCTreeManager*> mTreeManager;
 
-  nsRefPtr<AsyncPanZoomController> mLastChild;
-  nsRefPtr<AsyncPanZoomController> mPrevSibling;
   nsRefPtr<AsyncPanZoomController> mParent;
 
 
   /* ===================================================================
    * The functions and members in this section are used for scrolling,
    * including handing off scroll to another APZC, and overscrolling.
    */
 public:
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/HitTestingTreeNode.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HitTestingTreeNode.h"
+
+#include "AsyncPanZoomController.h"
+#include "LayersLogging.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+namespace layers {
+
+HitTestingTreeNode::HitTestingTreeNode(AsyncPanZoomController* aApzc,
+                                       bool aIsPrimaryHolder)
+  : mApzc(aApzc)
+  , mIsPrimaryApzcHolder(aIsPrimaryHolder)
+{
+  MOZ_ASSERT(mApzc);
+}
+
+HitTestingTreeNode::~HitTestingTreeNode()
+{
+}
+
+void
+HitTestingTreeNode::Destroy()
+{
+  AsyncPanZoomController::AssertOnCompositorThread();
+
+  mPrevSibling = nullptr;
+  mLastChild = nullptr;
+  mParent = nullptr;
+
+  if (mApzc) {
+    if (mIsPrimaryApzcHolder) {
+      mApzc->Destroy();
+    }
+    mApzc = nullptr;
+  }
+}
+
+void
+HitTestingTreeNode::SetLastChild(HitTestingTreeNode* aChild)
+{
+  mLastChild = aChild;
+  if (aChild) {
+    // We assume that HitTestingTreeNodes with an ancestor/descendant
+    // relationship cannot both point to the same APZC instance. This assertion
+    // only covers a subset of cases in which that might occur, but it's better
+    // than nothing.
+    MOZ_ASSERT(aChild->Apzc() != Apzc());
+
+    aChild->mParent = this;
+    aChild->Apzc()->SetParent(Apzc());
+  }
+}
+
+void
+HitTestingTreeNode::SetPrevSibling(HitTestingTreeNode* aSibling)
+{
+  mPrevSibling = aSibling;
+  if (aSibling) {
+    aSibling->mParent = mParent;
+    aSibling->Apzc()->SetParent(mParent ? mParent->Apzc() : nullptr);
+  }
+}
+
+void
+HitTestingTreeNode::MakeRoot()
+{
+  mParent = nullptr;
+  Apzc()->SetParent(nullptr);
+}
+
+HitTestingTreeNode*
+HitTestingTreeNode::GetFirstChild() const
+{
+  HitTestingTreeNode* child = GetLastChild();
+  while (child && child->GetPrevSibling()) {
+    child = child->GetPrevSibling();
+  }
+  return child;
+}
+
+HitTestingTreeNode*
+HitTestingTreeNode::GetLastChild() const
+{
+  return mLastChild;
+}
+
+HitTestingTreeNode*
+HitTestingTreeNode::GetPrevSibling() const
+{
+  return mPrevSibling;
+}
+
+HitTestingTreeNode*
+HitTestingTreeNode::GetParent() const
+{
+  return mParent;
+}
+
+AsyncPanZoomController*
+HitTestingTreeNode::Apzc() const
+{
+  return mApzc;
+}
+
+bool
+HitTestingTreeNode::IsPrimaryHolder() const
+{
+  return mIsPrimaryApzcHolder;
+}
+
+void
+HitTestingTreeNode::Dump(const char* aPrefix) const
+{
+  if (mPrevSibling) {
+    mPrevSibling->Dump(aPrefix);
+  }
+  printf_stderr("%sHitTestingTreeNode (%p) APZC (%p) guid (%s)\n",
+    aPrefix, this, mApzc.get(), Stringify(mApzc->GetGuid()).c_str());
+  if (mLastChild) {
+    mLastChild->Dump(nsPrintfCString("%s  ", aPrefix).get());
+  }
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/HitTestingTreeNode.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_HitTestingTreeNode_h
+#define mozilla_layers_HitTestingTreeNode_h
+
+#include "FrameMetrics.h"
+#include "nsRefPtr.h"
+
+namespace mozilla {
+namespace layers {
+
+class AsyncPanZoomController;
+
+/**
+ * This class represents a node in a tree that is used by the APZCTreeManager
+ * to do hit testing. The tree is roughly a copy of the layer tree but only
+ * contains nodes for layers that have scrollable metrics. There will be
+ * exactly one node per {layer, scrollable metrics} pair (since a layer may have
+ * multiple scrollable metrics). However, multiple HitTestingTreeNode instances
+ * may share the same underlying APZC instance if the layers they represent
+ * share the same scrollable metrics (i.e. are part of the same animated
+ * geometry root). If this happens, exactly one of the HitTestingTreeNode
+ * instances will be designated as the "primary holder" of the APZC. When
+ * this primary holder is destroyed, it will destroy the APZC along with it;
+ * in contrast, destroying non-primary-holder nodes will not destroy the APZC.
+ * Code should not make assumptions about which of the nodes will be the
+ * primary holder, only that that there will be exactly one for each APZC in
+ * the tree.
+ *
+ * Note that every HitTestingTreeNode instance will have a pointer to an APZC,
+ * and that pointer will be non-null. This will NOT be the case after the
+ * HitTestingTreeNode has been Destroy()'d.
+ *
+ * The reason this tree exists at all is so that we can do hit-testing on the
+ * thread that we receive input on (referred to the as the controller thread in
+ * APZ terminology), which may be different from the compositor thread.
+ * Accessing the compositor layer tree can only be done on the compositor
+ * thread, and so it is simpler to make a copy of the hit-testing related
+ * properties into a separate tree.
+ */
+class HitTestingTreeNode {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HitTestingTreeNode);
+
+private:
+  ~HitTestingTreeNode();
+public:
+  HitTestingTreeNode(AsyncPanZoomController* aApzc, bool aIsPrimaryHolder);
+  void Destroy();
+
+  /* Tree construction methods */
+  void SetLastChild(HitTestingTreeNode* aChild);
+  void SetPrevSibling(HitTestingTreeNode* aSibling);
+  void MakeRoot();
+
+  /* Tree walking methods. GetFirstChild is O(n) in the number of children. The
+   * other tree walking methods are all O(1). */
+  HitTestingTreeNode* GetFirstChild() const;
+  HitTestingTreeNode* GetLastChild() const;
+  HitTestingTreeNode* GetPrevSibling() const;
+  HitTestingTreeNode* GetParent() const;
+
+  /* APZC related methods */
+  AsyncPanZoomController* Apzc() const;
+  bool IsPrimaryHolder() const;
+
+  /* Debug helpers */
+  void Dump(const char* aPrefix = "") const;
+
+private:
+  nsRefPtr<HitTestingTreeNode> mLastChild;
+  nsRefPtr<HitTestingTreeNode> mPrevSibling;
+  nsRefPtr<HitTestingTreeNode> mParent;
+
+  nsRefPtr<AsyncPanZoomController> mApzc;
+  bool mIsPrimaryApzcHolder;
+};
+
+}
+}
+
+#endif // mozilla_layers_HitTestingTreeNode_h
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -232,16 +232,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk
             'ipc/FenceUtilsGonk.cpp',
         ]
 
 UNIFIED_SOURCES += [
     'apz/src/APZCTreeManager.cpp',
     'apz/src/AsyncPanZoomController.cpp',
     'apz/src/Axis.cpp',
     'apz/src/GestureEventListener.cpp',
+    'apz/src/HitTestingTreeNode.cpp',
     'apz/src/InputBlockState.cpp',
     'apz/src/InputQueue.cpp',
     'apz/src/OverscrollHandoffState.cpp',
     'apz/src/TaskThrottler.cpp',
     'apz/testutil/APZTestData.cpp',
     'apz/util/ActiveElementManager.cpp',
     'apz/util/APZCCallbackHelper.cpp',
     'apz/util/ChromeProcessController.cpp',
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
 #include "mozilla/layers/GeckoContentController.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/UniquePtr.h"
 #include "apz/src/AsyncPanZoomController.h"
+#include "apz/src/HitTestingTreeNode.h"
 #include "base/task.h"
 #include "Layers.h"
 #include "TestLayers.h"
 #include "gfxPrefs.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
@@ -870,16 +871,18 @@ TEST_F(APZCBasicTester, ComplexTransform
   EXPECT_EQ(ViewTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
   EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
 
   childMetrics.ZoomBy(1.5f);
   childApzc->SetFrameMetrics(childMetrics);
   childApzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
   EXPECT_EQ(ViewTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
   EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
+
+  childApzc->Destroy();
 }
 
 class APZCPanningTester : public APZCBasicTester {
 protected:
   void DoPanTest(bool aShouldTriggerScroll, bool aShouldBeConsumed, uint32_t aBehavior)
   {
     if (aShouldTriggerScroll) {
       EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1));
@@ -2023,26 +2026,31 @@ TEST_F(APZCTreeManagerTester, Scrollable
   EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
 }
 
 TEST_F(APZCTreeManagerTester, Bug1068268) {
   CreatePotentiallyLeakingTree();
   ScopedLayerTreeRegistration registration(0, root, mcc);
 
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
-  EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[0])->GetLastChild());
-  EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[0])->GetFirstChild());
+  nsRefPtr<HitTestingTreeNode> root = manager->GetRootNode();
+  nsRefPtr<HitTestingTreeNode> node2 = root->GetFirstChild();
+  nsRefPtr<HitTestingTreeNode> node5 = root->GetLastChild();
+
+  EXPECT_EQ(ApzcOf(layers[2]), node5->Apzc());
+  EXPECT_EQ(ApzcOf(layers[2]), node2->Apzc());
   EXPECT_EQ(ApzcOf(layers[0]), ApzcOf(layers[2])->GetParent());
   EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[5]));
 
-  EXPECT_EQ(ApzcOf(layers[3]), ApzcOf(layers[2])->GetFirstChild());
-  EXPECT_EQ(ApzcOf(layers[6]), ApzcOf(layers[2])->GetLastChild());
-  EXPECT_EQ(ApzcOf(layers[3]), ApzcOf(layers[6])->GetPrevSibling());
+  EXPECT_EQ(node2->GetFirstChild(), node2->GetLastChild());
+  EXPECT_EQ(ApzcOf(layers[3]), node2->GetLastChild()->Apzc());
+  EXPECT_EQ(node5->GetFirstChild(), node5->GetLastChild());
+  EXPECT_EQ(ApzcOf(layers[6]), node5->GetLastChild()->Apzc());
   EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[3])->GetParent());
-  EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[6])->GetParent());
+  EXPECT_EQ(ApzcOf(layers[5]), ApzcOf(layers[6])->GetParent());
 }
 
 TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
   CreateComplexMultiLayerTree();
   ScopedLayerTreeRegistration registration(0, root, mcc);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
 
   TestAsyncPanZoomController* nullAPZC = nullptr;
@@ -2065,28 +2073,38 @@ TEST_F(APZHitTestingTester, ComplexMulti
   EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[4]));
   EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[7]));
   EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[9]));
   EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[7]));
   EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[9]));
   EXPECT_NE(ApzcOf(layers[7]), ApzcOf(layers[9]));
 
   // Ensure the shape of the APZC tree is as expected
+  nsRefPtr<HitTestingTreeNode> root = manager->GetRootNode();
   TestAsyncPanZoomController* layers1_2 = ApzcOf(layers[1]);
   TestAsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]);
   TestAsyncPanZoomController* layer7 = ApzcOf(layers[7]);
   TestAsyncPanZoomController* layer9 = ApzcOf(layers[9]);
   EXPECT_EQ(nullptr, layers1_2->GetParent());
   EXPECT_EQ(nullptr, layers4_6_8->GetParent());
   EXPECT_EQ(layers4_6_8, layer7->GetParent());
   EXPECT_EQ(nullptr, layer9->GetParent());
-  EXPECT_EQ(nullptr, layers1_2->GetPrevSibling());
-  EXPECT_EQ(layers1_2, layers4_6_8->GetPrevSibling());
-  EXPECT_EQ(nullptr, layer7->GetPrevSibling());
-  EXPECT_EQ(layers4_6_8, layer9->GetPrevSibling());
+  EXPECT_EQ(layer9, root->Apzc());
+  TestAsyncPanZoomController* expected[] = {
+    layer9,
+    layers4_6_8, layers4_6_8, layers4_6_8,
+    layers1_2, layers1_2
+  };
+  int i = 0;
+  for (HitTestingTreeNode *iter = root; iter; iter = iter->GetPrevSibling()) {
+    EXPECT_EQ(expected[i++], iter->Apzc());
+  }
+  HitTestingTreeNode* node6 = root->GetPrevSibling()->GetPrevSibling();
+  EXPECT_EQ(layer7, node6->GetLastChild()->Apzc());
+  EXPECT_EQ(nullptr, node6->GetLastChild()->GetPrevSibling());
 
   nsRefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(25, 25));
   EXPECT_EQ(ApzcOf(layers[1]), hit.get());
   hit = GetTargetAPZC(ScreenPoint(275, 375));
   EXPECT_EQ(ApzcOf(layers[9]), hit.get());
   hit = GetTargetAPZC(ScreenPoint(250, 100));
   EXPECT_EQ(ApzcOf(layers[7]), hit.get());
 }