Bug 1109873 - Implement the expanded HitTestingTree. r=botond
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 08 Jan 2015 09:40:01 -0500
changeset 248602 c8c65c2ef9bf36ef9a1d19f32ea0dc345c259ccb
parent 248601 6f5ff015b5e636091b8627ccde5b6dc48340ecc6
child 248603 5d506eb6fd4c29dbe61d8958a45395799217d6b1
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 - Implement the expanded HitTestingTree. r=botond
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/APZCTreeManager.h
gfx/layers/apz/src/HitTestingTreeNode.cpp
gfx/layers/apz/src/HitTestingTreeNode.h
gfx/tests/gtest/TestAsyncPanZoomController.cpp
layout/base/UnitTransforms.h
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -23,18 +23,24 @@
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
 #include "UnitTransforms.h"             // for ViewAs
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "OverscrollHandoffState.h"     // for OverscrollHandoffState
 #include "LayersLogging.h"              // for Stringify
 
-#define APZCTM_LOG(...)
-// #define APZCTM_LOG(...) printf_stderr("APZCTM: " __VA_ARGS__)
+#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;
 
@@ -63,23 +69,16 @@ struct APZCTreeManager::TreeBuildingStat
   // 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<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
-  // start populating this stack until we first encounter an APZC. At each level
-  // we accumulate the event regions of non-APZC descendants of the layer at
-  // that level.
-  nsTArray<EventRegions> mEventRegions;
 };
 
 /*static*/ const ScreenMargin
 APZCTreeManager::CalculatePendingDisplayPort(
   const FrameMetrics& aFrameMetrics,
   const ParentLayerPoint& aVelocity,
   double aEstimatedPaintDuration)
 {
@@ -194,63 +193,76 @@ APZCTreeManager::UpdateHitTestingTree(Co
   mRootNode = nullptr;
 
   if (aRoot) {
     mApzcTreeLog << "[start]\n";
     LayerMetricsWrapper root(aRoot);
     UpdateHitTestingTree(state, root,
                          // aCompositor is null in gtest scenarios
                          aCompositor ? aCompositor->RootLayerTreeId() : 0,
-                         Matrix4x4(), nullptr, nullptr, nsIntRegion());
+                         Matrix4x4(), nullptr, nullptr);
     mApzcTreeLog << "[end]\n";
   }
-  MOZ_ASSERT(state.mEventRegions.Length() == 0);
 
   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 touch-sensitive region of an APZC. This is used only when
-// event-regions are disabled.
+// 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 nsIntRegion
-ComputeTouchSensitiveRegion(GeckoContentController* aController,
-                            const FrameMetrics& aMetrics,
-                            const nsIntRegion& aObscured)
+ComputeClipRegion(GeckoContentController* aController,
+                  const LayerMetricsWrapper& aLayer)
 {
-  // Use the composition bounds as the hit test region.
+  nsIntRegion clipRegion;
+  if (aLayer.GetClipRect()) {
+    clipRegion = nsIntRegion(*aLayer.GetClipRect());
+  } else {
+    // if there is no clip on this layer (which should only happen for the
+    // root scrollable layer in a process) fall back to using the comp
+    // bounds which should be equivalent.
+    clipRegion = nsIntRegion(ParentLayerIntRect::ToUntyped(
+        RoundedToInt(aLayer.Metrics().mCompositionBounds)));
+  }
+
   // Optionally, the GeckoContentController can provide a touch-sensitive
   // region that constrains all frames associated with the controller.
   // In this case we intersect the composition bounds with that region.
-  ParentLayerRect visible(aMetrics.mCompositionBounds);
   CSSRect touchSensitiveRegion;
   if (aController->GetTouchSensitiveRegion(&touchSensitiveRegion)) {
     // Here we assume 'touchSensitiveRegion' is in the CSS pixels of the
     // parent frame. To convert it to ParentLayer pixels, we therefore need
     // the cumulative resolution of the parent frame. We approximate this as
     // the quotient of our cumulative resolution and our pres shell resolution;
     // this approximation may not be accurate in the presence of a css-driven
     // resolution.
     LayoutDeviceToParentLayerScale parentCumulativeResolution =
-          aMetrics.GetCumulativeResolution()
-        / ParentLayerToLayerScale(aMetrics.mPresShellResolution);
-    visible = visible.Intersect(touchSensitiveRegion
-                                * aMetrics.GetDevPixelsPerCSSPixel()
-                                * parentCumulativeResolution);
+          aLayer.Metrics().GetCumulativeResolution()
+        / ParentLayerToLayerScale(aLayer.Metrics().mPresShellResolution);
+    // Not sure what rounding option is the most correct here, but if we ever
+    // figure it out we can change this. For now I'm rounding in to minimize
+    // the chances of getting a complex region.
+    nsIntRect extraClip = ParentLayerIntRect::ToUntyped(RoundedIn(
+        touchSensitiveRegion
+        * aLayer.Metrics().GetDevPixelsPerCSSPixel()
+        * parentCumulativeResolution));
+    clipRegion.AndWith(extraClip);
   }
 
-  // Not sure what rounding option is the most correct here, but if we ever
-  // figure it out we can change this. For now I'm rounding in to minimize
-  // the chances of getting a complex region.
-  nsIntRegion unobscured;
-  unobscured.Sub(ParentLayerIntRect::ToUntyped(RoundedIn(visible)), aObscured);
-  return unobscured;
+  return clipRegion;
 }
 
 void
 APZCTreeManager::PrintAPZCInfo(const LayerMetricsWrapper& aLayer,
                                const AsyncPanZoomController* apzc)
 {
   const FrameMetrics& metrics = aLayer.Metrics();
   mApzcTreeLog << "APZC " << apzc->GetGuid() << "\tcb=" << metrics.mCompositionBounds
@@ -271,36 +283,73 @@ APZCTreeManager::AttachNodeToTree(HitTes
     aParent->SetLastChild(aNode);
   } else {
     MOZ_ASSERT(!mRootNode);
     mRootNode = aNode;
     aNode->MakeRoot();
   }
 }
 
+static EventRegions
+GetEventRegions(const LayerMetricsWrapper& aLayer)
+{
+  if (gfxPrefs::LayoutEventRegionsEnabled()) {
+    return aLayer.GetEventRegions();
+  }
+  return EventRegions(aLayer.GetVisibleRegion());
+}
+
+already_AddRefed<HitTestingTreeNode>
+APZCTreeManager::RecycleOrCreateNode(TreeBuildingState& aState,
+                                     AsyncPanZoomController* aApzc)
+{
+  // 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++) {
+    nsRefPtr<HitTestingTreeNode> node = aState.mNodesToDestroy[i];
+    if (!node->IsPrimaryHolder()) {
+      aState.mNodesToDestroy.RemoveElement(node);
+      node->RecycleWith(aApzc);
+      return node.forget();
+    }
+  }
+  nsRefPtr<HitTestingTreeNode> node = new HitTestingTreeNode(aApzc, false);
+  return node.forget();
+}
+
 HitTestingTreeNode*
 APZCTreeManager::PrepareNodeForLayer(const LayerMetricsWrapper& aLayer,
                                      const FrameMetrics& aMetrics,
                                      uint64_t aLayersId,
                                      const gfx::Matrix4x4& aAncestorTransform,
-                                     const nsIntRegion& aObscured,
                                      HitTestingTreeNode* aParent,
                                      HitTestingTreeNode* aNextSibling,
                                      TreeBuildingState& aState)
 {
+  bool needsApzc = true;
   if (!aMetrics.IsScrollable()) {
-    return nullptr;
+    needsApzc = false;
   }
   if (gfxPrefs::LayoutEventRegionsEnabled() && aLayer.IsScrollInfoLayer()) {
-    return nullptr;
+    needsApzc = false;
   }
 
   const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId);
   if (!(state && state->mController.get())) {
-    return nullptr;
+    needsApzc = false;
+  }
+
+  nsRefPtr<HitTestingTreeNode> node = nullptr;
+  if (!needsApzc) {
+    node = RecycleOrCreateNode(aState, nullptr);
+    AttachNodeToTree(node, aParent, aNextSibling);
+    node->SetHitTestData(GetEventRegions(aLayer), aLayer.GetTransform(),
+        aLayer.GetClipRect() ? Some(nsIntRegion(*aLayer.GetClipRect())) : Nothing());
+    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
@@ -313,17 +362,16 @@ APZCTreeManager::PrepareNodeForLayer(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)) {
@@ -385,22 +433,18 @@ APZCTreeManager::PrepareNodeForLayer(con
     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->GetApzc() && node->GetApzc()->Matches(guid));
 
-    if (!gfxPrefs::LayoutEventRegionsEnabled()) {
-      nsIntRegion unobscured = ComputeTouchSensitiveRegion(state->mController, aMetrics, aObscured);
-      node->SetHitTestData(EventRegions(), Matrix4x4(), unobscured);
-      APZCTM_LOG("Setting region %s as visible region for APZC %p (node %p)\n",
-        Stringify(unobscured).c_str(), apzc, node.get());
-    }
+    nsIntRegion clipRegion = ComputeClipRegion(state->mController, aLayer);
+    node->SetHitTestData(GetEventRegions(aLayer), aLayer.GetTransform(), Some(clipRegion));
     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
@@ -434,56 +478,49 @@ APZCTreeManager::PrepareNodeForLayer(con
 
     // 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.
 
-    // 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);
+    node = RecycleOrCreateNode(aState, apzc);
     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);
-      node->SetHitTestData(EventRegions(), Matrix4x4(), unobscured);
-      APZCTM_LOG("Adding region %s to visible region of APZC %p (via node %p)\n",
-        Stringify(unobscured).c_str(), apzc, node.get());
-    }
+    nsIntRegion clipRegion = ComputeClipRegion(state->mController, aLayer);
+    node->SetHitTestData(GetEventRegions(aLayer), aLayer.GetTransform(), Some(clipRegion));
   }
 
   return node;
 }
 
 HitTestingTreeNode*
 APZCTreeManager::UpdateHitTestingTree(TreeBuildingState& aState,
                                       const LayerMetricsWrapper& aLayer,
                                       uint64_t aLayersId,
                                       const gfx::Matrix4x4& aAncestorTransform,
                                       HitTestingTreeNode* aParent,
-                                      HitTestingTreeNode* aNextSibling,
-                                      const nsIntRegion& aObscured)
+                                      HitTestingTreeNode* aNextSibling)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
   mApzcTreeLog << aLayer.Name() << '\t';
 
   HitTestingTreeNode* node = PrepareNodeForLayer(aLayer,
         aLayer.Metrics(), aLayersId, aAncestorTransform,
-        aObscured, aParent, aNextSibling, aState);
-  AsyncPanZoomController* apzc = (node ? node->GetApzc() : nullptr);
+        aParent, aNextSibling, aState);
+  MOZ_ASSERT(node);
+  AsyncPanZoomController* apzc = node->GetApzc();
   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
@@ -491,177 +528,30 @@ APZCTreeManager::UpdateHitTestingTree(Tr
   // with an APZC, such as P, then we reset the ancestorTransform to just PC, to start
   // the new accumulation as we go down.
   Matrix4x4 transform = aLayer.GetTransform();
   Matrix4x4 ancestorTransform = transform;
   if (!apzc) {
     ancestorTransform = ancestorTransform * aAncestorTransform;
   }
 
-  uint64_t childLayersId = (aLayer.AsRefLayer() ? aLayer.AsRefLayer()->GetReferentId() : aLayersId);
-
-  nsIntRegion obscuredByUncles;
-  if (aLayersId == childLayersId) {
-    // If the child layer is in the same process, keep a copy of |aObscured|.
-    // This value is in ParentLayerPixels and represents the area that is
-    // obscured by |aLayer|'s younger uncles (i.e. any next-siblings of any
-    // ancestor of |aLayer|) in the same process. We will need this value later.
-
-    // If we cross a process boundary, we assume that we can start with
-    // an empty obscured region because nothing in the parent process will
-    // obscure the child process. This may be false. However, not doing this
-    // definitely runs into a problematic case where the B2G notification
-    // bar and the keyboard get merged into a single layer that obscures
-    // all child processes, even though visually they do not. We'd probably
-    // have to check for mask layers and so on in order to properly handle
-    // that case.
-    obscuredByUncles = aObscured;
-  }
+  // 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;
+  HitTestingTreeNode* next = nullptr;
 
-  // 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.
-  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 |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.
-  if (gfxPrefs::LayoutEventRegionsEnabled() && (apzc || aState.mEventRegions.Length() > 0)) {
-    aState.mEventRegions.AppendElement(EventRegions());
-  }
-
-  // Convert the obscured region into this layer's LayerPixels.
-  nsIntRegion obscured = obscuredByUncles;
-  obscured.Transform(To3DMatrix(transform).Inverse());
+  uint64_t childLayersId = (aLayer.AsRefLayer() ? aLayer.AsRefLayer()->GetReferentId() : aLayersId);
   for (LayerMetricsWrapper child = aLayer.GetLastChild(); child; child = child.GetPrevSibling()) {
     gfx::TreeAutoIndent indent(mApzcTreeLog);
     next = UpdateHitTestingTree(aState, child, childLayersId,
-                                ancestorTransform, aParent, next,
-                                obscured);
-
-    // Each layer obscures its previous siblings, so we augment the obscured
-    // region as we loop backwards through the children.
-    nsIntRegion childRegion;
-    if (gfxPrefs::LayoutEventRegionsEnabled()) {
-      childRegion = child.GetEventRegions().mHitRegion;
-    } else {
-      childRegion = child.GetVisibleRegion();
-    }
-    childRegion.Transform(gfx::To3DMatrix(child.GetTransform()));
-    if (child.GetClipRect()) {
-      childRegion.AndWith(*child.GetClipRect());
-    }
-
-    obscured.OrWith(childRegion);
+                                ancestorTransform, aParent, next);
   }
 
-  if (gfxPrefs::LayoutEventRegionsEnabled() && aState.mEventRegions.Length() > 0) {
-    // At this point in the code, aState.mEventRegions.LastElement() contains
-    // the accumulated regions of the non-APZC descendants of |aLayer|. This
-    // happened in the loop above while we iterated through the descendants of
-    // |aLayer|. Note that it only includes the non-APZC descendants, because
-    // if a layer has an APZC, we simply store the regions from that subtree on
-    // that APZC and don't propagate them upwards in the tree. Because of the
-    // way we do hit-testing (where the deepest matching APZC is used) it should
-    // still be ok if we did propagate those regions upwards and included them
-    // in all the ancestor APZCs.
-
-    // Take the hit region of the |aLayer|'s subtree (which has already been
-    // transformed into the coordinate space of |aLayer|) and...
-    EventRegions subtreeEventRegions = aState.mEventRegions.LastElement();
-    aState.mEventRegions.RemoveElementAt(aState.mEventRegions.Length() - 1);
-    // ... combine it with the event region for this layer.
-    subtreeEventRegions.OrWith(aLayer.GetEventRegions());
-
-    // If we have an APZC at this level, intersect the subtree hit region with
-    // the touch-sensitive region and add it to the APZ's hit test regions.
-    if (node) {
-      // At this point in the code we have two different "obscured" regions.
-      // There is |obscuredByUncles| which represents the hit regions of
-      // |aLayer|'s younger uncles (i.e. the next-sibling chains of |aLayer|'s
-      // ancestors). This obscured region does not move when aLayer is scrolled,
-      // and so is in the same ParentLayerPixel coordinate space as |aLayer|'s
-      // clip rect.
-      // We also have |obscured| which includes the hit regions of |aLayer|'s
-      // descendants. However we don't want to use this because those
-      // descendants (and their hit regions) move as |aLayer| scrolls.
-      // From |aLayer|'s point of view |obscured| is in a nonsensical coordinate
-      // space because it combines the uncle-obscured region and child-obscured
-      // regions.
-      // We combine the |obscuredByUncles| region with the clip rect of |aLayer|
-      // below, to "apply" the obscuration by the uncles.
-      nsIntRegion clipRegion;
-      if (aLayer.GetClipRect()) {
-        clipRegion = nsIntRegion(*aLayer.GetClipRect());
-      } else {
-        // if there is no clip on this layer (which should only happen for the
-        // root scrollable layer in a process) fall back to using the comp
-        // bounds which should be equivalent.
-        clipRegion = nsIntRegion(ParentLayerIntRect::ToUntyped(RoundedToInt(aLayer.Metrics().mCompositionBounds)));
-      }
-      clipRegion.SubOut(obscuredByUncles);
-
-      const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId);
-      MOZ_ASSERT(state);
-      MOZ_ASSERT(state->mController.get());
-      CSSRect touchSensitiveRegion;
-      if (state->mController->GetTouchSensitiveRegion(&touchSensitiveRegion)) {
-        // Here we assume 'touchSensitiveRegion' is in the CSS pixels of the
-        // parent frame. To convert it to ParentLayer pixels, we therefore need
-        // the cumulative resolution of the parent frame. We approximate this as
-        // the quotient of our cumulative resolution and our pres shell
-        // resolution; this approximation may not be accurate in the presence of
-        // a css-driven resolution.
-        LayoutDeviceToParentLayerScale parentCumulativeResolution =
-            aLayer.Metrics().GetCumulativeResolution()
-            / ParentLayerToLayerScale(aLayer.Metrics().mPresShellResolution);
-        clipRegion.AndWith(ParentLayerIntRect::ToUntyped(
-            RoundedIn(touchSensitiveRegion
-                    * aLayer.Metrics().GetDevPixelsPerCSSPixel()
-                    * parentCumulativeResolution)));
-      }
-
-      node->SetHitTestData(subtreeEventRegions, aLayer.GetTransform(), clipRegion);
-      APZCTM_LOG("After processing layer %p the event regions for %p is %s\n",
-        aLayer.GetLayer(), node, Stringify(subtreeEventRegions).c_str());
-    } 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);
-      // transform it up to the parent layer's coordinate space.
-      subtreeEventRegions.Transform(To3DMatrix(aLayer.GetTransform()));
-      if (aLayer.GetClipRect()) {
-        subtreeEventRegions.AndWith(*aLayer.GetClipRect());
-      }
-      aState.mEventRegions.LastElement().OrWith(subtreeEventRegions);
-    }
-  }
-
-  // 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 nodes in the subtree
-  // upwards. Otherwise we fall back to the aNextSibling that was passed in.
-  if (node) {
-    return node;
-  }
-  if (next) {
-    return next;
-  }
-  return aNextSibling;
+  return node;
 }
 
 nsEventStatus
 APZCTreeManager::ReceiveInputEvent(InputData& aEvent,
                                    ScrollableLayerGuid* aOutTargetGuid,
                                    uint64_t* aOutInputBlockId)
 {
   // Initialize aOutInputBlockId to a sane value, and then later we overwrite
@@ -1321,17 +1211,19 @@ APZCTreeManager::GetTargetNode(const Scr
   return target.forget();
 }
 
 already_AddRefed<AsyncPanZoomController>
 APZCTreeManager::GetTargetAPZC(const ScreenPoint& aPoint, HitTestResult* aOutHitResult)
 {
   MonitorAutoLock lock(mTreeLock);
   HitTestResult hitResult = NoApzcHit;
-  nsRefPtr<AsyncPanZoomController> target = GetAPZCAtPoint(mRootNode, aPoint.ToUnknownPoint(), &hitResult);
+  ParentLayerPoint point = ViewAs<ParentLayerPixel>(aPoint,
+    PixelCastJustification::ScreenIsParentLayerForRoot);
+  nsRefPtr<AsyncPanZoomController> target = GetAPZCAtPoint(mRootNode, point, &hitResult);
 
   // If we are in an overscrolled APZC, we should be returning nullptr.
   MOZ_ASSERT(!(target && (hitResult == OverscrolledApzc)));
   if (aOutHitResult) {
     *aOutHitResult = hitResult;
   }
   return target.forget();
 }
@@ -1441,113 +1333,98 @@ APZCTreeManager::FindTargetNode(HitTesti
       return node;
     }
   }
   return nullptr;
 }
 
 AsyncPanZoomController*
 APZCTreeManager::GetAPZCAtPoint(HitTestingTreeNode* aNode,
-                                const Point& aHitTestPoint,
+                                const ParentLayerPoint& aHitTestPoint,
                                 HitTestResult* aOutHitResult)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
   // This walks the tree in depth-first, reverse order, so that it encounters
   // APZCs front-to-back on the screen.
   for (HitTestingTreeNode* node = aNode; node; node = node->GetPrevSibling()) {
     AsyncPanZoomController* apzc = node->GetApzc();
 
-    // 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 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 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;
-    if (apzc) {
-      ancestorUntransform = apzc->GetAncestorTransform().Inverse();
+    if (node->IsOutsideClip(aHitTestPoint)) {
+      // 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",
+        aHitTestPoint.x, aHitTestPoint.y, node);
+      continue;
     }
 
-    // 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 parentlayer coordinates %f %f for hit-testing APZC %p\n",
-        aHitTestPoint.x, aHitTestPoint.y,
-        hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, apzc);
+    AsyncPanZoomController* result = nullptr;
 
-    // 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 asyncUntransform;
-    if (apzc) {
-      asyncUntransform = Matrix4x4(apzc->GetCurrentAsyncTransform()).Inverse();
-    }
-    Matrix4x4 childUntransform = ancestorUntransform * asyncUntransform;
-    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, apzc);
-
-    AsyncPanZoomController* result = nullptr;
-    if (hitTestPointForChildLayers.HasPositiveWCoord()) {
-      result = GetAPZCAtPoint(node->GetLastChild(), hitTestPointForChildLayers.As2DPoint(), aOutHitResult);
+    // First check the subtree rooted at this node, because deeper nodes
+    // are more "in front".
+    Maybe<LayerPoint> hitTestPointForChildLayers = node->Untransform(aHitTestPoint);
+    if (hitTestPointForChildLayers) {
+      ParentLayerPoint childPoint = ViewAs<ParentLayerPixel>(hitTestPointForChildLayers.ref(),
+        PixelCastJustification::MovingDownToChildren);
+      result = GetAPZCAtPoint(node->GetLastChild(), childPoint, aOutHitResult);
       if (*aOutHitResult == OverscrolledApzc) {
         // We matched an overscrolled APZC, abort.
         return nullptr;
       }
     }
-    if (!result && hitTestPointForThisLayer.HasPositiveWCoord()) {
-      ParentLayerPoint point = ParentLayerPoint::FromUnknownPoint(hitTestPointForThisLayer.As2DPoint());
-      HitTestResult hitResult = node->HitTest(point);
+
+    // If we didn't match anything in the subtree, check |node|.
+    if (!result) {
+      APZCTM_LOG("Testing ParentLayer point %f %f (Layer %f %f) against node %p\n",
+          aHitTestPoint.x, aHitTestPoint.y,
+          hitTestPointForChildLayers.x, hitTestPointForChildLayers.y,
+          node);
+      HitTestResult hitResult = node->HitTest(aHitTestPoint);
       if (hitResult != HitTestResult::NoApzcHit) {
-        APZCTM_LOG("Successfully matched untransformed point %f %f to visible region for APZC %p via node %p\n",
-             hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, apzc, node);
-        result = apzc;
+        result = node->GetNearestContainingApzc();
+        APZCTM_LOG("Successfully matched APZC %p via node %p (hit result %d)\n",
+             result, node, hitResult);
         MOZ_ASSERT(hitResult == ApzcHitRegion || hitResult == ApzcContentRegion);
         // If event regions are disabled, *aOutHitResult will be ApzcHitRegion
         *aOutHitResult = hitResult;
       }
     }
 
     // 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 && apzc && apzc->IsOverscrolled()) {
+      APZCTM_LOG("Result is inside overscrolled APZC %p\n", apzc);
       *aOutHitResult = OverscrolledApzc;
       return nullptr;
     }
 
     if (result) {
       if (!gfxPrefs::LayoutEventRegionsEnabled()) {
         // When event-regions are disabled, we treat scrollinfo layers as
         // regular scrollable layers. Unfortunately, their "hit region" (which
         // we create from the composition bounds) is their full area, and they
         // sit on top of their non-scrollinfo siblings. This means they will get
         // a HitTestingTreeNode with a hit region that will aggressively match
         // any input events that might be directed to sub-APZCs of their non-
-        // scrollinfo siblings. This means we need to keep looping through to
+        // scrollinfo siblings. Therefore, we need to keep looping through to
         // see if there are any other non-scrollinfo siblings that have children
         // that match this input. If so, they should take priority. With event-
-        // regions enabled we ignore scrollinfo layers and this is unnecessary.
+        // regions enabled we use the actual regions from the layer, which are
+        // empty, and so this is unnecessary.
         AsyncPanZoomController* prevSiblingApzc = nullptr;
         for (HitTestingTreeNode* n = node->GetPrevSibling(); n; n = n->GetPrevSibling()) {
           if (n->GetApzc()) {
             prevSiblingApzc = n->GetApzc();
             break;
           }
         }
         if (result == prevSiblingApzc) {
+          APZCTM_LOG("Continuing search past probable scrollinfo info layer\n");
           continue;
         }
       }
 
       return result;
     }
   }
 
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -71,18 +71,19 @@ class HitTestingTreeNode;
  * This class manages the tree of AsyncPanZoomController instances. There is one
  * instance of this class owned by each CompositorParent, and it contains as
  * many AsyncPanZoomController instances as there are scrollable container layers.
  * This class generally lives on the compositor thread, although some functions
  * may be called from other threads as noted; thread safety is ensured internally.
  *
  * The bulk of the work of this class happens as part of the UpdateHitTestingTree
  * function, which is when a layer tree update is received by the compositor.
- * This function walks through the layer tree and creates a tree of APZC instances
- * to match the scrollable container layers. APZC instances may be preserved across
+ * This function walks through the layer tree and creates a tree of
+ * HitTestingTreeNode instances to match the layer tree and for use in
+ * hit-testing on the controller thread. APZC instances may be preserved across
  * calls to this function if the corresponding layers are still present in the layer
  * tree.
  *
  * The other functions on this class are used by various pieces of client code to
  * notify the APZC instances of events relevant to them. This includes, for example,
  * user input events that drive panning and zooming, changes to the scroll viewport
  * area, and changes to pan/zoom constraints.
  *
@@ -92,29 +93,29 @@ class HitTestingTreeNode;
  * Behaviour of APZ is controlled by a number of preferences shown \ref APZCPrefs "here".
  */
 class APZCTreeManager {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(APZCTreeManager)
 
   typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior;
   typedef uint32_t TouchBehaviorFlags;
 
-  // Helper struct to hold some state while we build the APZ tree. The
+  // Helper struct to hold some state while we build the hit-testing tree. The
   // sole purpose of this struct is to shorten the argument list to
   // UpdateHitTestingTree. All the state that we don't need to
   // push on the stack during recursion and pop on unwind is stored here.
   struct TreeBuildingState;
 
 public:
   APZCTreeManager();
 
   /**
-   * Rebuild the APZC tree based on the layer update that just came up. Preserve
-   * APZC instances where possible, but retire those whose layers are no longer
-   * in the layer tree.
+   * Rebuild the hit-testing tree based on the layer update that just came up.
+   * Preserve nodes and APZC instances where possible, but retire those whose
+   * layers are no longer in the layer tree.
    *
    * This must be called on the compositor thread as it walks the layer tree.
    *
    * @param aCompositor A pointer to the compositor parent instance that owns
    *                    this APZCTreeManager
    * @param aRoot The root of the (full) layer tree
    * @param aFirstPaintLayersId The layers id of the subtree to which aIsFirstPaint
    *                            applies.
@@ -415,17 +416,17 @@ private:
   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,
+                                         const ParentLayerPoint& 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,
@@ -435,60 +436,53 @@ private:
                                   uint64_t* aOutInputBlockId);
   nsEventStatus ProcessEvent(WidgetInputEvent& inputEvent,
                              ScrollableLayerGuid* aOutTargetGuid,
                              uint64_t* aOutInputBlockId);
   void UpdateZoomConstraintsRecursively(HitTestingTreeNode* aNode,
                                         const ZoomConstraints& aConstraints);
   void FlushRepaintsRecursively(HitTestingTreeNode* aNode);
 
+  already_AddRefed<HitTestingTreeNode> RecycleOrCreateNode(TreeBuildingState& aState,
+                                                           AsyncPanZoomController* aApzc);
   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
-   * code.
+   * Recursive helper function to build the hit-testing tree. See documentation
+   * in HitTestingTreeNode.h for more details on the shape of the tree.
+   * This function walks the layer tree backwards through siblings and
+   * constructs the hit-testing tree also as a last-child-prev-sibling tree
+   * because that simplifies the hit detection code.
    *
-   * @param aState             The current tree building state.
-   * @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 aState The current tree building state.
+   * @param aLayer The (layer, metrics) pair which is the current position in
+   *               the recursive walk of the layer tree. This call builds a
+   *               hit-testing 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 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.
+   * @param aParent The parent of any node built at this level.
+   * @param aNextSibling The next sibling of any node built at this level.
+   * @return The HitTestingTreeNode created at this level. This will always
+   *         be non-null.
    */
   HitTestingTreeNode* UpdateHitTestingTree(TreeBuildingState& aState,
                                            const LayerMetricsWrapper& aLayer,
                                            uint64_t aLayersId,
                                            const gfx::Matrix4x4& aAncestorTransform,
                                            HitTestingTreeNode* aParent,
-                                           HitTestingTreeNode* aNextSibling,
-                                           const nsIntRegion& aObscured);
+                                           HitTestingTreeNode* aNextSibling);
 
   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.
    */
--- a/gfx/layers/apz/src/HitTestingTreeNode.cpp
+++ b/gfx/layers/apz/src/HitTestingTreeNode.cpp
@@ -21,16 +21,26 @@ HitTestingTreeNode::HitTestingTreeNode(A
   : mApzc(aApzc)
   , mIsPrimaryApzcHolder(aIsPrimaryHolder)
 {
   if (mIsPrimaryApzcHolder) {
     MOZ_ASSERT(mApzc);
   }
 }
 
+void
+HitTestingTreeNode::RecycleWith(AsyncPanZoomController* aApzc)
+{
+  MOZ_ASSERT(!mIsPrimaryApzcHolder);
+  Destroy(); // clear out tree pointers
+  mApzc = aApzc;
+  // The caller is expected to call SetHitTestData to repopulate the hit-test
+  // fields.
+}
+
 HitTestingTreeNode::~HitTestingTreeNode()
 {
 }
 
 void
 HitTestingTreeNode::Destroy()
 {
   AsyncPanZoomController::AssertOnCompositorThread();
@@ -139,52 +149,66 @@ bool
 HitTestingTreeNode::IsPrimaryHolder() const
 {
   return mIsPrimaryApzcHolder;
 }
 
 void
 HitTestingTreeNode::SetHitTestData(const EventRegions& aRegions,
                                    const gfx::Matrix4x4& aTransform,
-                                   const nsIntRegion& aClipRegion)
+                                   const Maybe<nsIntRegion>& aClipRegion)
 {
   mEventRegions = aRegions;
   mTransform = aTransform;
   mClipRegion = aClipRegion;
 }
 
-HitTestResult
-HitTestingTreeNode::HitTest(const ParentLayerPoint& aPoint) const
+bool
+HitTestingTreeNode::IsOutsideClip(const ParentLayerPoint& aPoint) const
 {
-  // When event regions are disabled, we are actually storing the
-  // touch-sensitive section of the composition bounds in the clip rect, and we
-  // don't need to use mTransform or mEventRegions.
-  if (!gfxPrefs::LayoutEventRegionsEnabled()) {
-    MOZ_ASSERT(mEventRegions == EventRegions());
-    MOZ_ASSERT(mTransform == gfx::Matrix4x4());
-    return mClipRegion.Contains(aPoint.x, aPoint.y)
-        ? HitTestResult::ApzcHitRegion
-        : HitTestResult::NoApzcHit;
-  }
+  // test against clip rect in ParentLayer coordinate space
+  return (mClipRegion.isSome() && !mClipRegion->Contains(aPoint.x, aPoint.y));
+}
 
-  // test against clip rect in ParentLayer coordinate space
-  if (!mClipRegion.Contains(aPoint.x, aPoint.y)) {
-    return HitTestResult::NoApzcHit;
-  }
-
+Maybe<LayerPoint>
+HitTestingTreeNode::Untransform(const ParentLayerPoint& aPoint) const
+{
   // convert into Layer coordinate space
   gfx::Matrix4x4 localTransform = mTransform;
   if (mApzc) {
     localTransform = localTransform * gfx::Matrix4x4(mApzc->GetCurrentAsyncTransform());
   }
-  gfx::Point4D pointInLayerPixels = localTransform.Inverse().ProjectPoint(aPoint.ToUnknownPoint());
-  if (!pointInLayerPixels.HasPositiveWCoord()) {
+  gfx::Point4D point = localTransform.Inverse().ProjectPoint(aPoint.ToUnknownPoint());
+  return point.HasPositiveWCoord()
+        ? Some(ViewAs<LayerPixel>(point.As2DPoint()))
+        : Nothing();
+}
+
+HitTestResult
+HitTestingTreeNode::HitTest(const ParentLayerPoint& aPoint) const
+{
+  // This should only ever get called if the point is inside the clip region
+  // for this node.
+  MOZ_ASSERT(!IsOutsideClip(aPoint));
+
+  // When event regions are disabled and we have an APZC on this node, we are
+  // actually storing the touch-sensitive section of the composition bounds in
+  // the clip region, and we don't need to check against the mEventRegions.
+  // If there's no APZC, then we do need to check against the mEventRegions
+  // (which contains the layer's visible region) for obscuration purposes.
+  if (!gfxPrefs::LayoutEventRegionsEnabled() && GetApzc()) {
+    return HitTestResult::ApzcHitRegion;
+  }
+
+  // convert into Layer coordinate space
+  Maybe<LayerPoint> pointInLayerPixels = Untransform(aPoint);
+  if (!pointInLayerPixels) {
     return HitTestResult::NoApzcHit;
   }
-  LayerIntPoint point = RoundedToInt(ViewAs<LayerPixel>(pointInLayerPixels.As2DPoint()));
+  LayerIntPoint point = RoundedToInt(pointInLayerPixels.ref());
 
   // test against event regions in Layer coordinate space
   if (!mEventRegions.mHitRegion.Contains(point.x, point.y)) {
     return HitTestResult::NoApzcHit;
   }
   if (mEventRegions.mDispatchToContentHitRegion.Contains(point.x, point.y)) {
     return HitTestResult::ApzcContentRegion;
   }
@@ -195,17 +219,17 @@ void
 HitTestingTreeNode::Dump(const char* aPrefix) const
 {
   if (mPrevSibling) {
     mPrevSibling->Dump(aPrefix);
   }
   printf_stderr("%sHitTestingTreeNode (%p) APZC (%p) g=(%s) r=(%s) t=(%s) c=(%s)\n",
     aPrefix, this, mApzc.get(), mApzc ? Stringify(mApzc->GetGuid()).c_str() : "",
     Stringify(mEventRegions).c_str(), Stringify(mTransform).c_str(),
-    Stringify(mClipRegion).c_str());
+    mClipRegion ? Stringify(mClipRegion.ref()).c_str() : "none");
   if (mLastChild) {
     mLastChild->Dump(nsPrintfCString("%s  ", aPrefix).get());
   }
 }
 
 void
 HitTestingTreeNode::SetApzcParent(AsyncPanZoomController* aParent)
 {
--- a/gfx/layers/apz/src/HitTestingTreeNode.h
+++ b/gfx/layers/apz/src/HitTestingTreeNode.h
@@ -6,35 +6,41 @@
 
 #ifndef mozilla_layers_HitTestingTreeNode_h
 #define mozilla_layers_HitTestingTreeNode_h
 
 #include "APZUtils.h"                       // for HitTestResult
 #include "FrameMetrics.h"                   // for ScrollableLayerGuid
 #include "mozilla/gfx/Matrix.h"             // for Matrix4x4
 #include "mozilla/layers/LayersTypes.h"     // for EventRegions
+#include "mozilla/Maybe.h"                  // for Maybe
 #include "nsRefPtr.h"                       // for nsRefPtr
 
 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.
+ * to do hit testing. The tree is roughly a copy of the layer tree, but will
+ * contain multiple nodes in cases where the layer has multiple FrameMetrics.
+ * In other words, the structure of this tree should be identical to the
+ * LayerMetrics tree (see documentation in LayerMetricsWrapper.h).
+ *
+ * Not all HitTestingTreeNode instances will have an APZC associated with them;
+ * only HitTestingTreeNodes that correspond to layers with scrollable metrics
+ * have APZCs.
+ * 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.
  *
  * 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
@@ -43,77 +49,82 @@ class AsyncPanZoomController;
  */
 class HitTestingTreeNode {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HitTestingTreeNode);
 
 private:
   ~HitTestingTreeNode();
 public:
   HitTestingTreeNode(AsyncPanZoomController* aApzc, bool aIsPrimaryHolder);
+  void RecycleWith(AsyncPanZoomController* aApzc);
   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* GetApzc() const;
   AsyncPanZoomController* GetNearestContainingApzc() const;
   bool IsPrimaryHolder() const;
 
   /* Hit test related methods */
+
   void SetHitTestData(const EventRegions& aRegions,
                       const gfx::Matrix4x4& aTransform,
-                      const nsIntRegion& aClipRegion);
+                      const Maybe<nsIntRegion>& aClipRegion);
+  bool IsOutsideClip(const ParentLayerPoint& aPoint) const;
+  /* Convert aPoint into the LayerPixel space for the layer corresponding to
+   * this node. */
+  Maybe<LayerPoint> Untransform(const ParentLayerPoint& aPoint) const;
+  /* Assuming aPoint is inside the clip region for this node, check which of the
+   * event region spaces it falls inside. */
   HitTestResult HitTest(const ParentLayerPoint& aPoint) const;
 
   /* Debug helpers */
   void Dump(const char* aPrefix = "") const;
 
 private:
   void SetApzcParent(AsyncPanZoomController* aApzc);
 
   nsRefPtr<HitTestingTreeNode> mLastChild;
   nsRefPtr<HitTestingTreeNode> mPrevSibling;
   nsRefPtr<HitTestingTreeNode> mParent;
 
   nsRefPtr<AsyncPanZoomController> mApzc;
   bool mIsPrimaryApzcHolder;
 
   /* Let {L,M} be the {layer, scrollable metrics} pair that this node
-   * corresponds to in the layer tree. Then, mEventRegions contains the union
-   * of the event regions of all layers in L's subtree, excluding those layers
-   * which are contained in a descendant HitTestingTreeNode's mEventRegions.
-   * This value is stored in L's LayerPixel space.
-   * For example, if this HitTestingTreeNode maps to a ContainerLayer with
-   * scrollable metrics and which has two PaintedLayer children, the event
-   * regions stored here will be the union of the three event regions in the
-   * ContainerLayer's layer pixel space. This means the event regions from the
-   * PaintedLayer children will have been transformed and clipped according to
-   * the individual properties on those layers but the ContainerLayer's event
-   * regions will be used "raw". */
+   * corresponds to in the layer tree. mEventRegions contains the event regions
+   * from L, in the case where event-regions are enabled. If event-regions are
+   * disabled, it will contain the visible region of L, which we use as an
+   * approximation to the hit region for the purposes of obscuring other layers.
+   * This value is in L's LayerPixels.
+   */
   EventRegions mEventRegions;
 
-  /* This is the transform that the layer subtree corresponding to this node is
-   * subject to. In the terms of the comment on mEventRegions, it is the
-   * transform from the ContainerLayer. This does NOT include any async
+  /* This is the transform from layer L. This does NOT include any async
    * transforms. */
   gfx::Matrix4x4 mTransform;
 
-  /* This is the clip rect that the layer subtree corresponding to this node
-   * is subject to. In the terms of the comment on mEventRegions, it is the clip
-   * rect of the ContainerLayer, and is in the ContainerLayer's ParentLayerPixel
-   * space. */
-  nsIntRegion mClipRegion;
+  /* This is clip rect for L that we wish to use for hit-testing purposes. Note
+   * that this may not be exactly the same as the clip rect on layer L because
+   * of the touch-sensitive region provided by the GeckoContentController, or
+   * because we may use the composition bounds of the layer if the clip is not
+   * present. This value is in L's ParentLayerPixels. */
+  Maybe<nsIntRegion> mClipRegion;
 };
 
 }
 }
 
 #endif // mozilla_layers_HitTestingTreeNode_h
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -2027,18 +2027,18 @@ TEST_F(APZCTreeManagerTester, Scrollable
 }
 
 TEST_F(APZCTreeManagerTester, Bug1068268) {
   CreatePotentiallyLeakingTree();
   ScopedLayerTreeRegistration registration(0, root, mcc);
 
   manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
   nsRefPtr<HitTestingTreeNode> root = manager->GetRootNode();
-  nsRefPtr<HitTestingTreeNode> node2 = root->GetFirstChild();
-  nsRefPtr<HitTestingTreeNode> node5 = root->GetLastChild();
+  nsRefPtr<HitTestingTreeNode> node2 = root->GetFirstChild()->GetFirstChild();
+  nsRefPtr<HitTestingTreeNode> node5 = root->GetLastChild()->GetLastChild();
 
   EXPECT_EQ(ApzcOf(layers[2]), node5->GetApzc());
   EXPECT_EQ(ApzcOf(layers[2]), node2->GetApzc());
   EXPECT_EQ(ApzcOf(layers[0]), ApzcOf(layers[2])->GetParent());
   EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[5]));
 
   EXPECT_EQ(node2->GetFirstChild(), node2->GetLastChild());
   EXPECT_EQ(ApzcOf(layers[3]), node2->GetLastChild()->GetApzc());
@@ -2048,16 +2048,32 @@ TEST_F(APZCTreeManagerTester, Bug1068268
   EXPECT_EQ(ApzcOf(layers[5]), ApzcOf(layers[6])->GetParent());
 }
 
 TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
   CreateComplexMultiLayerTree();
   ScopedLayerTreeRegistration registration(0, root, mcc);
   manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
 
+  /* The layer tree looks like this:
+
+                0
+        |----|--+--|----|
+        1    2     4    5
+             |         /|\
+             3        6 8 9
+                      |
+                      7
+
+     Layers 1,2 have the same APZC
+     Layers 4,6,8 have the same APZC
+     Layer 7 has an APZC
+     Layer 9 has an APZC
+  */
+
   TestAsyncPanZoomController* nullAPZC = nullptr;
   // Ensure all the scrollable layers have an APZC
   EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
   EXPECT_NE(nullAPZC, ApzcOf(layers[1]));
   EXPECT_NE(nullAPZC, ApzcOf(layers[2]));
   EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics());
   EXPECT_NE(nullAPZC, ApzcOf(layers[4]));
   EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics());
@@ -2071,40 +2087,46 @@ TEST_F(APZHitTestingTester, ComplexMulti
   EXPECT_EQ(ApzcOf(layers[8]), ApzcOf(layers[6]));
   // Ensure those that don't scroll together have different APZCs
   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();
+  // Ensure the APZC parent chains are set up correctly
   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(layer9, root->GetApzc());
-  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->GetApzc());
-  }
-  HitTestingTreeNode* node6 = root->GetPrevSibling()->GetPrevSibling();
-  EXPECT_EQ(layer7, node6->GetLastChild()->GetApzc());
-  EXPECT_EQ(nullptr, node6->GetLastChild()->GetPrevSibling());
+  // Ensure the hit-testing tree looks like the layer tree
+  nsRefPtr<HitTestingTreeNode> root = manager->GetRootNode();
+  nsRefPtr<HitTestingTreeNode> node5 = root->GetLastChild();
+  nsRefPtr<HitTestingTreeNode> node4 = node5->GetPrevSibling();
+  nsRefPtr<HitTestingTreeNode> node2 = node4->GetPrevSibling();
+  nsRefPtr<HitTestingTreeNode> node1 = node2->GetPrevSibling();
+  nsRefPtr<HitTestingTreeNode> node3 = node2->GetLastChild();
+  nsRefPtr<HitTestingTreeNode> node9 = node5->GetLastChild();
+  nsRefPtr<HitTestingTreeNode> node8 = node9->GetPrevSibling();
+  nsRefPtr<HitTestingTreeNode> node6 = node8->GetPrevSibling();
+  nsRefPtr<HitTestingTreeNode> node7 = node6->GetLastChild();
+  EXPECT_EQ(nullptr, node1->GetPrevSibling());
+  EXPECT_EQ(nullptr, node3->GetPrevSibling());
+  EXPECT_EQ(nullptr, node6->GetPrevSibling());
+  EXPECT_EQ(nullptr, node7->GetPrevSibling());
+  EXPECT_EQ(nullptr, node1->GetLastChild());
+  EXPECT_EQ(nullptr, node3->GetLastChild());
+  EXPECT_EQ(nullptr, node4->GetLastChild());
+  EXPECT_EQ(nullptr, node7->GetLastChild());
+  EXPECT_EQ(nullptr, node8->GetLastChild());
+  EXPECT_EQ(nullptr, node9->GetLastChild());
 
   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());
 }
--- a/layout/base/UnitTransforms.h
+++ b/layout/base/UnitTransforms.h
@@ -19,16 +19,19 @@ namespace mozilla {
 // Feel free to add more justifications to PixelCastJustification, along with
 // a comment that explains under what circumstances it is appropriate to use.
 
 MOZ_BEGIN_ENUM_CLASS(PixelCastJustification, uint8_t)
   // For the root layer, Screen Pixel = Parent Layer Pixel.
   ScreenIsParentLayerForRoot,
   // For the root composition size we want to view it as layer pixels in any layer
   ParentLayerToLayerForRootComposition,
+  // The Layer coordinate space for one layer is the ParentLayer coordinate
+  // space for its children
+  MovingDownToChildren,
   // The transform that is usually used to convert between two coordinate
   // systems is not available (for example, because the object that stores it
   // is being destroyed), so fall back to the identity.
   TransformNotAvailable
 MOZ_END_ENUM_CLASS(PixelCastJustification)
 
 template <class TargetUnits, class SourceUnits>
 gfx::SizeTyped<TargetUnits> ViewAs(const gfx::SizeTyped<SourceUnits>& aSize, PixelCastJustification) {